Files
MyClub/frontend/src/pages/admin/AdminDocsPage_Old.tsx
T
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

405 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useMemo, useState } from 'react';
import { Box, Heading, Text, List, ListItem, Link, Divider, Code, OrderedList, HStack, IconButton, useColorModeValue, useToast } from '@chakra-ui/react';
import { FaLink, FaArrowUp } from 'react-icons/fa';
import AdminLayout from '../../layouts/AdminLayout';
const AdminDocsPage: React.FC = () => {
const sections = useMemo(() => [
{ id: 'nastaveni', label: 'Nastavení (branding + SMTP)' },
{ id: 'clanky', label: 'Články a kategorie' },
{ id: 'zapasy', label: 'Zápasy (FAČR)' },
{ id: 'hraci-tymy', label: 'Hráči a týmy' },
{ id: 'media', label: 'Média' },
{ id: 'sponzori-bannery', label: 'Sponzoři a bannery' },
{ id: 'newsletter', label: 'Newsletter' },
{ id: 'aliasy', label: 'Alias soutěží' },
{ id: 'prefetch', label: 'Prefetch' },
{ id: 'videa', label: 'Videa' },
{ id: 'aktivity', label: 'Aktivity' },
{ id: 'merch', label: 'Oblečení' },
{ id: 'zpravy', label: 'Zprávy' },
{ id: 'reset-admin', label: 'Reset hesla' },
{ id: 'uzivatele', label: 'Uživatelé' },
{ id: 'seo-analytics', label: 'SEO a Analytics' },
{ id: 'troubleshooting', label: 'Troubleshooting' },
], []);
const [activeId, setActiveId] = useState<string>('');
const toast = useToast();
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
// Pick the entry closest to the top that is intersecting
const visible = entries
.filter((e) => e.isIntersecting)
.sort((a, b) => (a.boundingClientRect.top > b.boundingClientRect.top ? 1 : -1));
if (visible[0]) {
setActiveId(visible[0].target.id);
}
},
{ rootMargin: '-40% 0px -50% 0px', threshold: [0, 0.25, 0.5, 0.75, 1] }
);
const els = sections.map((s) => document.getElementById(s.id)).filter(Boolean) as Element[];
els.forEach((el) => observer.observe(el));
return () => observer.disconnect();
}, [sections]);
// Persist active section and restore on reload
useEffect(() => {
if (activeId) {
try { localStorage.setItem('adminDocs:lastAnchor', activeId); } catch {}
}
}, [activeId]);
useEffect(() => {
// Run after paint so layout is ready
const t = setTimeout(() => {
const hash = (window.location.hash || '').replace('#', '').trim();
let targetId = hash;
if (!targetId) {
try { targetId = localStorage.getItem('adminDocs:lastAnchor') || ''; } catch {}
}
if (targetId) {
const el = document.getElementById(targetId);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
setActiveId(targetId);
}
}
}, 50);
return () => clearTimeout(t);
}, []);
const copyDeepLink = async (id: string) => {
try {
const url = `${window.location.origin}${window.location.pathname}#${id}`;
await navigator.clipboard.writeText(url);
toast({ title: 'Odkaz zkopírován', status: 'success', duration: 1500, isClosable: true });
} catch {
toast({ title: 'Nelze zkopírovat odkaz', status: 'error', duration: 2000, isClosable: true });
}
};
const tocActiveColor = useColorModeValue('blue.700', 'blue.300');
const tocActiveBg = useColorModeValue('blue.50', 'blue.900');
return (
<AdminLayout>
<Box
display={{ base: 'block', md: 'grid' }}
gridTemplateColumns={{ base: '1fr', md: '260px 1fr' }}
gap={{ base: 0, md: 6 }}
>
{/* Sticky TOC */}
<Box display={{ base: 'none', md: 'block' }}>
<Box position="sticky" top="84px">
<Box bg="white" p={4} borderRadius="lg" borderWidth="1px" boxShadow="sm">
<Heading size="sm" mb={3}>Rychlá navigace</Heading>
<OrderedList spacing={2} pl={5}>
{sections.map((s) => (
<ListItem key={s.id}>
<Link
href={`#${s.id}`}
display="block"
px={2}
py={1}
borderRadius="md"
bg={activeId === s.id ? tocActiveBg : 'transparent'}
color={activeId === s.id ? tocActiveColor : 'inherit'}
onClick={() => {
try { localStorage.setItem('adminDocs:lastAnchor', s.id); } catch {}
}}
>
{s.label}
</Link>
</ListItem>
))}
</OrderedList>
</Box>
</Box>
</Box>
{/* Main content */}
<Box bg="white" p={6} borderRadius="lg" borderWidth="1px" boxShadow="sm">
<Box id="top" />
<Heading size="lg" mb={1}>Dokumentace</Heading>
<Text color="gray.600" mb={4}>Přehled funkcí, postupů a řešení problémů</Text>
<Heading size="md" mb={3}>Začínáme</Heading>
<Text mb={4}>
Tato stránka shrnuje hlavní možnosti administrace webu. Pro rychlý start doporučujeme nejprve vyplnit
<Link href="/admin/nastaveni" ml={1} color="blue.600">Nastavení</Link>, poté vytvořit první článek
a ověřit funkčnost emailů.
</Text>
<Heading size="sm" mt={6} mb={2}>Obsah</Heading>
<OrderedList spacing={1} mb={4} pl={5}>
<ListItem><Link href="#nastaveni">Nastavení klubu, branding a SMTP</Link></ListItem>
<ListItem><Link href="#clanky">Články a kategorie</Link></ListItem>
<ListItem><Link href="#zapasy">Zápasy a výsledky (FAČR)</Link></ListItem>
<ListItem><Link href="#hraci-tymy">Hráči a týmy</Link></ListItem>
<ListItem><Link href="#media">Média (soubory a obrázky)</Link></ListItem>
<ListItem><Link href="#sponzori-bannery">Sponzoři a bannery</Link></ListItem>
<ListItem><Link href="#newsletter">Newsletter</Link></ListItem>
<ListItem><Link href="#aliasy">Alias názvů soutěží</Link></ListItem>
<ListItem><Link href="#prefetch">Prefetch (YouTube a další data)</Link></ListItem>
<ListItem><Link href="#uzivatele">Uživatelé a přístupy</Link></ListItem>
<ListItem><Link href="#seo-analytics">SEO a Analytics</Link></ListItem>
<ListItem><Link href="#troubleshooting">Troubleshooting (řešení problémů)</Link></ListItem>
</OrderedList>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="nastaveni" size="md">Nastavení klubu, branding a SMTP</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('nastaveni')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem><strong>Branding</strong>: Nastavte název klubu, logo a barvy. Tyto hodnoty se propsají do emailů i vzhledu webu.</ListItem>
<ListItem><strong>SMTP</strong>: Vyplňte host, port, uživatele a heslo. U portu 465 se používá implicitní SSL, jinak STARTTLS.</ListItem>
<ListItem><strong>Test</strong>: V sekci Newsletter Test odešlete zkušební email a ověřte doručení.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="clanky" size="md">Články a kategorie</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('clanky')} />
</HStack>
<OrderedList spacing={2} pl={5}>
<ListItem>Vytvořte kategorii (pokud ještě neexistuje) a poté nový článek.</ListItem>
<ListItem>Vyplňte SEO název a popis zlepší to výsledky ve vyhledávání.</ListItem>
<ListItem>Obrázek nahrávejte do Média a vložte URL do článku.</ListItem>
</OrderedList>
<Text mt={3} fontSize="sm" color="gray.600">
Další správa kategorií je v sekci <Link href="/admin/kategorie">Kategorie</Link>.
</Text>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="zapasy" size="md">Zápasy a výsledky (FAČR)</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('zapasy')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Napojení na FAČR umožňuje zobrazit soutěže, tabulky a nadcházející utkání.</ListItem>
<ListItem>Přes <Link href="/admin/aliasy-soutezi">Alias názvů soutěží</Link> si můžete upravit názvy pro frontend.</ListItem>
<ListItem>Pokud FAČR data nevidíte, spusťte <Link href="/admin/prefetch">Prefetch</Link> pro načtení cache.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="hraci-tymy" size="md">Hráči a týmy</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('hraci-tymy')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Přidávejte týmy a hráče, udržujte je aktuální pro přehled na webu.</ListItem>
<ListItem>Loga týmů lze přepsat v sekci <Link href="/admin/aliasy-soutezi">Alias/Overrides</Link>, pokud se nenačtou správně.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="media" size="md">Média (soubory a obrázky)</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('media')} />
</HStack>
<OrderedList spacing={2} pl={5}>
<ListItem>Nahrajte soubor v sekci Média, zkopírujte URL a použijte ji v článcích nebo bannerech.</ListItem>
<ListItem>Podporované formáty: JPEG/PNG/SVG/ dle konfigurace serveru.</ListItem>
</OrderedList>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="sponzori-bannery" size="md">Sponzoři a bannery</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('sponzori-bannery')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Přidávejte sponzory s logem a odkazem. Můžete je zobrazit na vybraných sekcích webu.</ListItem>
<ListItem>Pro reklamní plochy používejte sekci Bannery a nastavte cílové URL.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="newsletter" size="md">Newsletter</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('newsletter')} />
</HStack>
<OrderedList spacing={2} pl={5}>
<ListItem>Nastavte SMTP a identitu odesílatele (jméno + adresa).</ListItem>
<ListItem>Odešlete test na svou adresu, ověřte zobrazení a doručení.</ListItem>
<ListItem>Hromadné rozesílky plánujte s ohledem na limity poskytovatele SMTP.</ListItem>
</OrderedList>
<Heading size="sm" mt={4} mb={2}>Postup: konfigurace SMTP (krok za krokem)</Heading>
<OrderedList spacing={1} pl={5}>
<ListItem>Otevřete <Link href="/admin/nastaveni">Nastavení</Link> a vyplňte Host, Port, Uživatelské jméno, Heslo a From adresu.</ListItem>
<ListItem>Pro port <Code>465</Code> použijte SSL. Pro port <Code>587</Code> použijte STARTTLS (Use TLS ano).</ListItem>
<ListItem>Uložte změny.</ListItem>
<ListItem>Přejděte do <Link href="/admin/newsletter">Newsletter</Link> a odešlete testovací email na vaši adresu.</ListItem>
<ListItem>Zkontrolujte doručení v inboxu a případně složku spam. Pokud nedorazí, viz Troubleshooting níže.</ListItem>
</OrderedList>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="aliasy" size="md">Alias názvů soutěží</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('aliasy')} />
</HStack>
<Text mb={2}>Upravit zobrazení soutěží (přejmenování, sjednocení názvů) můžete v sekci Alias. Změny se projeví na frontendu.</Text>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="prefetch" size="md">Prefetch (YouTube a další data)</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('prefetch')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Prefetch vytváří rychlou cache pro veřejné části webu (YouTube, články).</ListItem>
<ListItem>Při problémech s rychlostí načtení spusťte ručně Prefetch a zkontrolujte stav.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="videa" size="md">Videa</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('videa')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Správa videí a playlistů zobrazovaných na webu.</ListItem>
<ListItem>Pro rychlé načítání videí použijte <Link href="/admin/prefetch">Prefetch</Link> (YouTube cache).</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="aktivity" size="md">Aktivity</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('aktivity')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Plánujte a publikujte klubové akce mimo soutěžní zápasy.</ListItem>
<ListItem>Aktivity se mohou zobrazovat na nástěnce administrace i na veřejném webu.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="merch" size="md">Oblečení (Merch)</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('merch')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Správa položek klubového merche a jejich zobrazení pro fanoušky.</ListItem>
<ListItem>Pro každou položku nastavte název, popis, cenu a obrázek (z Média).</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="zpravy" size="md">Zprávy</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('zpravy')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Seznam zpráv odeslaných přes kontaktní formulář a systémové notifikace.</ListItem>
<ListItem>Odpovídejte uživatelům přímo z vaší schránky, systém ukládá pouze záznamy.</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="reset-admin" size="md">Reset hesla (admin nástroj)</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('reset-admin')} />
</HStack>
<OrderedList spacing={2} pl={5}>
<ListItem>Přejděte na <Link href="/admin/users/send-reset">Nástroj pro reset hesla</Link>.</ListItem>
<ListItem>Zadejte email uživatele a odešlete ověřovací kód/odkaz pro reset.</ListItem>
<ListItem>Uživatel dokončí změnu hesla přes veřejnou stránku pro reset.</ListItem>
</OrderedList>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="uzivatele" size="md">Uživatelé a přístupy</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('uzivatele')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Rozlišujeme role <Code>admin</Code> a <Code>user</Code>. Admin přístup do všech sekcí.</ListItem>
<ListItem>Hesla mají minimální délku 8 znaků a nesmí obsahovat mezery.</ListItem>
<ListItem>Zapomenuté heslo lze obnovit přes stránku Zapomenuté heslo (ověřovací kód emailem).</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="seo-analytics" size="md">SEO a Analytics</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('seo-analytics')} />
</HStack>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>U článků vyplňujte <strong>SEO titulek</strong> a <strong>SEO popis</strong>.</ListItem>
<ListItem>V administraci je k dispozici přehled návštěvnosti a interakcí (sekce Analytics).</ListItem>
</List>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
<Divider my={6} />
<HStack align="center" justify="space-between" mb={2}>
<Heading id="troubleshooting" size="md">Troubleshooting (řešení problémů)</Heading>
<IconButton aria-label="Zkopírovat odkaz" variant="ghost" size="sm" icon={<FaLink />} onClick={() => copyDeepLink('troubleshooting')} />
</HStack>
<Heading size="sm" mb={2}>Emaily se neodesílají</Heading>
<List spacing={2} styleType="disc" pl={5} mb={4}>
<ListItem>Zkontrolujte <Link href="/admin/nastaveni">SMTP nastavení</Link> (host, port, uživatel, heslo, šifrování).</ListItem>
<ListItem>Pro port 465 použijte SSL, pro 587 STARTTLS. Využijte tlačítko Otestovat SMTP v průvodci či v newsletteru.</ListItem>
<ListItem>Mrkněte do logů serveru na případné chyby SMTP.</ListItem>
</List>
<Heading size="sm" mb={2}>Obrázky/Logo se nezobrazuje</Heading>
<List spacing={2} styleType="disc" pl={5} mb={4}>
<ListItem>Ověřte, že URL je správná a veřejně dostupná. V případě externích zdrojů lze využít proxy `/api/v1/proxy/image`.</ListItem>
<ListItem>Nahrajte logo do sekce Média a použijte relativní cestu (např. <Code>/uploads/2025/01/logo.png</Code>).</ListItem>
</List>
<Heading size="sm" mb={2}>FAČR data nejsou aktuální</Heading>
<List spacing={2} styleType="disc" pl={5} mb={4}>
<ListItem>Spusťte <Link href="/admin/prefetch">Prefetch</Link> a porovnejte čas posledního běhu.</ListItem>
<ListItem>Zkontrolujte internetové připojení serveru a limity volání.</ListItem>
</List>
<Heading size="sm" mb={2}>Přihlášení nefunguje</Heading>
<List spacing={2} styleType="disc" pl={5} mb={4}>
<ListItem>Ověřte email/heslo a zda účet roli <Code>admin</Code>.</ListItem>
<ListItem>Pokud jste zapomněli heslo, použijte Zapomenuté heslo a kód z emailu.</ListItem>
</List>
<Heading size="sm" mb={2}>Newsletter padá do spamu</Heading>
<List spacing={2} styleType="disc" pl={5}>
<ListItem>Nastavte správně DNS záznamy <Code>SPF</Code>, <Code>DKIM</Code> a pokud možno <Code>DMARC</Code>.</ListItem>
<ListItem>Ověřte, že From doména odpovídá doméně vašeho SMTP odesílatele.</ListItem>
</List>
<Divider my={6} />
<Heading size="md" mb={3}>API základ</Heading>
<Text mb={2}>Veřejná API základna: <Code>/api/v1</Code></Text>
<Text fontSize="sm" color="gray.600">(Rozšířená dokumentace API bude doplněna v budoucnu.)</Text>
<Box mt={2}><Link href="#top" color="blue.600"><HStack as="span" spacing={2}><FaArrowUp /><Text>Na začátek</Text></HStack></Link></Box>
</Box>
</Box>
</AdminLayout>
);
};
export default AdminDocsPage;