mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
222 lines
8.4 KiB
TypeScript
222 lines
8.4 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import { Link as RouterLink, useLocation } from 'react-router-dom';
|
|
import '../../styles/sparta-styles.css';
|
|
import { usePublicSettings } from '../../hooks/usePublicSettings';
|
|
import { useClubTheme } from '../../contexts/ClubThemeContext';
|
|
import { getNavigationItems, NavigationItem, seedDefaultNavigation } from '../../services/navigation';
|
|
import { getCategories, Category } from '../../services/public';
|
|
import { assetUrl } from '../../utils/url';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
// Minimal NavLink type used to render items
|
|
type NavLink = { label: string; to?: string; items?: { label: string; to: string }[]; external?: boolean };
|
|
|
|
const SpartaNavbar: React.FC = () => {
|
|
const { t } = useTranslation();
|
|
const { data: settings } = usePublicSettings();
|
|
const theme = useClubTheme();
|
|
const location = useLocation();
|
|
|
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
const [dynamicNavItems, setDynamicNavItems] = useState<NavigationItem[]>([]);
|
|
const [navLoading, setNavLoading] = useState(true);
|
|
const [navCategories, setNavCategories] = useState<Category[] | null>(null);
|
|
|
|
// Load dynamic navigation
|
|
useEffect(() => {
|
|
let active = true;
|
|
(async () => {
|
|
try {
|
|
const items = await getNavigationItems();
|
|
if (active && Array.isArray(items)) {
|
|
const publicItems = items.filter(item => !item.requires_admin);
|
|
if (publicItems.length === 0) {
|
|
try {
|
|
await seedDefaultNavigation();
|
|
const newItems = await getNavigationItems();
|
|
if (active && Array.isArray(newItems)) {
|
|
const publicNewItems = newItems.filter(item => !item.requires_admin);
|
|
setDynamicNavItems(publicNewItems);
|
|
}
|
|
} catch {
|
|
setDynamicNavItems([]);
|
|
}
|
|
} else {
|
|
setDynamicNavItems(publicItems);
|
|
}
|
|
}
|
|
} catch {
|
|
// leave empty, fallback will handle
|
|
} finally {
|
|
if (active) setNavLoading(false);
|
|
}
|
|
})();
|
|
return () => { active = false };
|
|
}, []);
|
|
|
|
// Load categories (for Blog dropdown fallback)
|
|
useEffect(() => {
|
|
let active = true;
|
|
(async () => {
|
|
try {
|
|
const cats = await getCategories();
|
|
if (active && Array.isArray(cats) && cats.length > 0) {
|
|
setNavCategories(cats);
|
|
} else if (active && Array.isArray(settings?.categories)) {
|
|
setNavCategories(settings!.categories as any);
|
|
}
|
|
} catch {
|
|
if (active && Array.isArray(settings?.categories)) {
|
|
setNavCategories(settings!.categories as any);
|
|
}
|
|
}
|
|
})();
|
|
return () => { active = false };
|
|
}, [settings?.categories]);
|
|
|
|
const isPathActive = (to?: string) => {
|
|
if (!to) return false;
|
|
return location.pathname === to || location.pathname.startsWith((to || '') + '/');
|
|
};
|
|
|
|
const convertToNavLink = (item: NavigationItem): NavLink => {
|
|
// Map known Czech labels to translation keys
|
|
const getTranslatedLabel = (label: string) => {
|
|
const labelMap: Record<string, string> = {
|
|
'Domů': 'nav.home',
|
|
'Aktuality': 'nav.news',
|
|
'Zápasy': 'nav.matches',
|
|
'Hráči': 'nav.players',
|
|
'Fotogalerie': 'nav.gallery',
|
|
'Videa': 'nav.videos',
|
|
'Kontakt': 'nav.contact',
|
|
'O klubu': 'nav.about',
|
|
'Aktivity': 'nav.activities',
|
|
'Sponzoři': 'nav.sponsors',
|
|
'Články': 'nav.news',
|
|
'Blog': 'nav.news',
|
|
'Kalendář': 'nav.calendar',
|
|
'Tabulky': 'nav.table'
|
|
};
|
|
const translationKey = labelMap[label];
|
|
return translationKey ? t(translationKey) : label;
|
|
};
|
|
|
|
const link: NavLink = {
|
|
label: getTranslatedLabel(item.label),
|
|
to: item.url || '#',
|
|
external: item.type === 'external',
|
|
};
|
|
if (item.type === 'dropdown' && item.children && item.children.length > 0) {
|
|
link.items = item.children.map(child => ({
|
|
label: getTranslatedLabel(child.label),
|
|
to: child.url || '#'
|
|
}));
|
|
}
|
|
return link;
|
|
};
|
|
|
|
const categoryItems = useMemo(() => {
|
|
const source = Array.isArray(navCategories) && navCategories.length > 0 ? navCategories : [];
|
|
return source.map((cat: any) => ({ label: cat.name, to: cat.url || (cat.id ? `/blog?category_id=${cat.id}` : (cat.slug ? `/blog?category=${encodeURIComponent(cat.slug)}` : '/blog')) }));
|
|
}, [navCategories]);
|
|
|
|
const NAV_LINKS: NavLink[] = useMemo(() => {
|
|
if (!navLoading && dynamicNavItems.length > 0) {
|
|
const navLinks = dynamicNavItems.map(convertToNavLink);
|
|
if (categoryItems.length > 0) {
|
|
const idx = navLinks.findIndex(l => l.label === 'Články' || l.label === 'Blog' || l.to === '/blog');
|
|
if (idx !== -1) navLinks[idx] = { ...navLinks[idx], items: categoryItems };
|
|
}
|
|
return navLinks;
|
|
}
|
|
|
|
// Fallback minimal menu
|
|
const links: NavLink[] = [
|
|
{ label: 'Domů', to: '/' },
|
|
...(settings?.show_about_in_nav === false ? [] : [{ label: 'O klubu', to: '/o-klubu' } as NavLink]),
|
|
{ label: 'Kalendář', to: '/kalendar' },
|
|
{ label: 'Zápasy', to: '/zapasy' },
|
|
{ label: 'Aktivity', to: '/aktivity' },
|
|
{ label: 'Hráči', to: '/hraci' },
|
|
categoryItems.length > 0 ? { label: 'Články', to: '/blog', items: categoryItems } : { label: 'Články', to: '/blog' },
|
|
{ label: 'Videa', to: '/videa' },
|
|
{ label: settings?.gallery_label || t('nav.gallery'), to: '/galerie' },
|
|
...(settings?.shop_url ? [{ label: 'Fanshop', to: settings.shop_url, external: true } as NavLink] : []),
|
|
{ label: 'Sponzoři', to: '/sponzori' },
|
|
{ label: 'Kontakt', to: '/kontakt' },
|
|
];
|
|
return links;
|
|
}, [navLoading, dynamicNavItems, settings?.show_about_in_nav, settings?.shop_url, settings?.gallery_label, categoryItems]);
|
|
|
|
const logoUrl = (assetUrl(settings?.club_logo_url || theme.logoUrl) || settings?.club_logo_url || theme.logoUrl) || '/dist/img/logo-club-empty.svg';
|
|
const clubName = settings?.club_name || theme.name || 'Klub';
|
|
|
|
return (
|
|
<div className="sparta-navbar-container">
|
|
<div className="sparta-navbar">
|
|
{/* Burger toggle for mobile */}
|
|
<button
|
|
aria-label="Menu"
|
|
className="sparta-navbar-toggle"
|
|
onClick={() => setMobileOpen(o => !o)}
|
|
>
|
|
<div className="sparta-burger-icon" aria-hidden>
|
|
<div className="sparta-burger-line" />
|
|
<div className="sparta-burger-line" />
|
|
<div className="sparta-burger-line" />
|
|
</div>
|
|
</button>
|
|
|
|
{/* Brand */}
|
|
<RouterLink to="/" className="sparta-navbar-brand" onClick={() => setMobileOpen(false)}>
|
|
<img src={logoUrl} alt={clubName} />
|
|
</RouterLink>
|
|
|
|
{/* Links */}
|
|
<nav
|
|
className="sparta-navbar-links"
|
|
style={{ display: mobileOpen ? 'flex' : undefined, flexWrap: 'wrap' }}
|
|
>
|
|
{NAV_LINKS.map((nav) => {
|
|
const isActive = isPathActive(nav.to);
|
|
const className = isActive ? 'sparta-button-tertiary' : 'sparta-button-tertiary';
|
|
|
|
// When categories are present under Články/Blog, render clickable parent + category links
|
|
if (nav.items && nav.items.length > 0 && (nav.label === 'Články' || nav.label === 'Blog' || (nav.to || '').startsWith('/blog'))) {
|
|
return (
|
|
<React.Fragment key={nav.label}>
|
|
<RouterLink to={nav.to || '/blog'} className={className} onClick={() => setMobileOpen(false)}>
|
|
{nav.label}
|
|
</RouterLink>
|
|
{nav.items.map((it) => (
|
|
<RouterLink key={`${nav.label}-${it.to}`} to={it.to} className={className} onClick={() => setMobileOpen(false)}>
|
|
{it.label}
|
|
</RouterLink>
|
|
))}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
|
|
if (nav.external && nav.to) {
|
|
return (
|
|
<a key={nav.label} href={nav.to} target="_blank" rel="noreferrer" className={className} onClick={() => setMobileOpen(false)}>
|
|
{nav.label}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<RouterLink key={nav.label} to={nav.to || '#'} className={className} onClick={() => setMobileOpen(false)}>
|
|
{nav.label}
|
|
</RouterLink>
|
|
);
|
|
})}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SpartaNavbar;
|