This commit is contained in:
Tomas Dvorak
2025-10-28 22:38:27 +01:00
parent 3d621e2187
commit 823fabee02
106 changed files with 9011 additions and 3930 deletions
@@ -40,7 +40,7 @@ import {
useColorModeValue,
Image as ChakraImage,
} from '@chakra-ui/react';
import { FiEdit2, FiPlus, FiTrash2 } from 'react-icons/fi';
import { FiEdit2, FiPlus, FiTrash2, FiLink } from 'react-icons/fi';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Event } from '../../types/event';
import { uploadFile } from '../../services/articles';
@@ -60,9 +60,10 @@ import { getCachedYouTube, YouTubeVideo } from '../../services/youtube';
import SaveStatusIndicator from '../../components/common/SaveStatusIndicator';
import DraftRecoveryModal from '../../components/common/DraftRecoveryModal';
import { useAutoSave, loadDraft, getDraftMetadata } from '../../hooks/useAutoSave';
import { FiVideo, FiYoutube, FiLink } from 'react-icons/fi';
import { FiVideo, FiYoutube } from 'react-icons/fi';
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
import { assetUrl } from '../../utils/url';
import { createShortLink } from '../../services/shortlinks';
const types: Array<{ value: Event['type']; label: string }> = [
{ value: 'match', label: 'Zápas' },
@@ -124,6 +125,13 @@ const AdminActivitiesPage: React.FC = () => {
});
const events = data || [];
// Localized label for event type
const typeLabel = (t?: string) => {
const v = String(t || '').trim() as any;
const found = types.find((x) => x.value === v);
return found ? found.label : 'Jiné';
};
// Load club YouTube videos
useEffect(() => {
(async () => {
@@ -266,22 +274,18 @@ const AdminActivitiesPage: React.FC = () => {
const e = editing || {};
// Build a helpful Czech prompt including known fields
const lines: string[] = [];
const clubName = String(settingsQ?.data?.club_name || '').trim();
if (clubName) lines.push(`Klub: ${clubName}`);
if (e.type) lines.push(`Typ: ${e.type}`);
if (e.location) lines.push(`Místo: ${e.location}`);
if (e.start_time) {
try { lines.push(`Začátek: ${new Date(e.start_time as any).toLocaleString('cs-CZ')}`); } catch {}
}
if (e.end_time) {
try { lines.push(`Konec: ${new Date(e.end_time as any).toLocaleString('cs-CZ')}`); } catch {}
}
if (e.description) lines.push(`Poznámky: ${e.description}`);
const base = lines.join('\n');
const toneText = aiTone === 'informative' ? 'informativním a věcným stylem' : aiTone === 'formal' ? 'formálním a profesionálním stylem' : 'přátelským, pozitivním a lákavým stylem';
const safeUserPrompt = (aiPrompt || 'Vytvoř krátké oznámení pro fanoušky o klubové aktivitě.').trim();
const prompt = `${safeUserPrompt}\n\nPiš ${toneText}, česky, s důrazem na jasnost a pozvánku k účasti.\nDetaily:\n${base}`.trim();
const constraints = 'Nevkládej datum ani místo (lokalitu) do textu. Neuváděj konkrétní čas nebo adresu.';
const prompt = `${safeUserPrompt}\n\nPiš ${toneText}, česky, s důrazem na jasnost a pozvánku k účasti. ${constraints}\nDetaily:\n${base}`.trim();
const { data } = await api.post('/ai/blog/generate', {
prompt,
audience: 'Fanoušci klubu, oznámení/pozvánka',
audience: clubName ? `Fanoušci klubu ${clubName}, oznámení/pozvánka` : 'Fanoušci klubu, oznámení/pozvánka',
min_words: 120,
});
@@ -485,7 +489,7 @@ const AdminActivitiesPage: React.FC = () => {
)}
</Td>
<Td>{ev.title}</Td>
<Td>{ev.type}</Td>
<Td>{typeLabel(ev.type as any)}</Td>
<Td>{new Date(ev.start_time).toLocaleString()}</Td>
<Td>{ev.end_time ? new Date(ev.end_time).toLocaleString() : '-'}</Td>
<Td>{ev.location || '-'}</Td>
@@ -494,6 +498,23 @@ const AdminActivitiesPage: React.FC = () => {
<HStack>
<IconButton aria-label="Upravit" size="sm" icon={<FiEdit2 />} onClick={() => openEdit(ev)} />
<IconButton aria-label="Smazat" size="sm" colorScheme="red" icon={<FiTrash2 />} onClick={() => deleteMut.mutate(ev.id)} />
<IconButton
aria-label="Zkrátit odkaz"
size="sm"
icon={<FiLink />}
title="Zkrátit odkaz pro sdílení"
onClick={async () => {
try {
const origin = window.location.origin;
const target = `${origin}/aktivita/${ev.id}`;
const res = await createShortLink({ target_url: target, title: ev.title, source_type: 'event', source_id: ev.id as any });
await navigator.clipboard.writeText(res.short_url);
toast({ title: 'Zkrácený odkaz zkopírován', description: res.short_url, status: 'success', duration: 4000 });
} catch (e: any) {
toast({ title: 'Vytvoření odkazu selhalo', description: e?.message || 'Zkuste to znovu', status: 'error' });
}
}}
/>
</HStack>
</Td>
</Tr>