import { Box, VStack, Text, useColorModeValue, Icon, Link as ChakraLink, Divider, Image, Flex, Spinner } from '@chakra-ui/react';
import { Link as RouterLink, useLocation } from 'react-router-dom';
import { useEffect, useRef, useCallback, useState } from 'react';
import {
FaTachometerAlt,
FaUsers,
FaFutbol,
FaCalendarAlt,
FaNewspaper,
FaHandshake,
FaImage,
FaEnvelope,
FaCog,
FaPalette,
FaHome,
FaSignOutAlt,
FaPaperPlane,
FaAward,
FaSyncAlt,
FaBook,
FaMobileAlt,
FaChartBar,
FaFolder,
FaAddressBook,
FaBars,
FaPoll,
FaPaintBrush,
FaVideo,
FaCamera,
FaTshirt,
FaBullhorn,
FaUserShield,
FaFileAlt
} from 'react-icons/fa';
import { useAuth } from '../../contexts/AuthContext';
import { useQuery } from '@tanstack/react-query';
import { getUpcomingEvents } from '../../services/eventService';
import { getAllNavigationItems, NavigationItem, seedDefaultNavigation } from '../../services/navigation';
interface NavItemProps {
icon: any;
to?: string;
children: React.ReactNode;
onClick?: (e?: React.MouseEvent) => void;
}
const NavItem = ({ icon, to, children, onClick }: NavItemProps) => {
const location = useLocation();
const isActive = to ? location.pathname.startsWith(to) : false;
const activeBg = useColorModeValue('blue.50', 'blue.900');
const activeColor = useColorModeValue('blue.600', 'blue.300');
const handleClick = (e: React.MouseEvent) => {
// Call the onClick handler first
if (onClick) {
onClick(e);
// If onClick called preventDefault, respect it
if (e.isDefaultPrevented()) {
return;
}
}
// Allow RouterLink to handle navigation normally
};
// If onClick is provided without `to`, render as a button-like link
const LinkComponent = to ? RouterLink : 'a';
const linkProps = to ? { to } : { href: '#' };
return (
{children}
);
};
interface AdminSidebarProps {
isOpen: boolean;
onClose: () => void;
bg?: string;
borderRight?: string;
borderColor?: string;
}
// Icon mapping for navigation items
const getIconForPageType = (pageType?: string): any => {
const iconMap: Record = {
dashboard: FaTachometerAlt,
analytics: FaChartBar,
teams: FaUsers,
matches: FaCalendarAlt,
activities: FaCalendarAlt,
players: FaFutbol,
articles: FaNewspaper,
categories: FaFileAlt,
about: FaBook,
videos: FaVideo,
gallery: FaImage,
scoreboard: FaTachometerAlt,
scoreboard_remote: FaMobileAlt,
clothing: FaTshirt,
sponsors: FaHandshake,
banners: FaBullhorn,
messages: FaEnvelope,
contacts: FaAddressBook,
newsletter: FaPaperPlane,
polls: FaPoll,
navigation: FaBars,
competition_aliases: FaAward,
prefetch: FaSyncAlt,
users: FaUserShield,
settings: FaPalette,
files: FaFolder,
docs: FaBook,
};
return iconMap[pageType || ''] || FaFileAlt;
};
const AdminSidebar = ({
isOpen,
onClose,
bg: bgProp,
borderRight = '1px',
borderColor: borderColorProp
}: AdminSidebarProps) => {
const { logout, user } = useAuth();
const isAdmin = (user as any)?.role === 'admin';
const defaultBg = useColorModeValue('white', '#1a1d29');
const defaultBorderColor = useColorModeValue('gray.200', 'rgba(255, 255, 255, 0.12)');
const textColor = useColorModeValue('gray.800', '#e2e8f0');
const bg = bgProp || defaultBg;
const borderColor = borderColorProp || defaultBorderColor;
// Upcoming events count for badge
const { data: upcomingEvents } = useQuery({ queryKey: ['admin-sidebar-upcoming-events'], queryFn: getUpcomingEvents });
const upcomingCount = Array.isArray(upcomingEvents) ? upcomingEvents.length : 0;
const scrollRef = useRef(null);
const location = useLocation();
const STORAGE_KEY = 'admin-sidebar-scroll';
// Dynamic navigation state
const [navItems, setNavItems] = useState([]);
const [navLoading, setNavLoading] = useState(true);
// Restore scroll on mount
useEffect(() => {
const node = scrollRef.current;
if (!node) return;
const saved = sessionStorage.getItem(STORAGE_KEY);
if (saved) {
try {
const top = parseInt(saved, 10);
if (!Number.isNaN(top)) {
node.scrollTop = top;
}
} catch {}
}
}, []);
// Save scroll on scroll
const handleScroll = useCallback(() => {
const node = scrollRef.current;
if (!node) return;
sessionStorage.setItem(STORAGE_KEY, String(node.scrollTop));
}, []);
// Load dynamic navigation from API
useEffect(() => {
let active = true;
(async () => {
try {
const items = await getAllNavigationItems();
if (active && Array.isArray(items)) {
// Filter only admin navigation items
const adminItems = items.filter(item => item.requires_admin);
// Auto-seed if admin navigation is empty and user is admin
if (adminItems.length === 0 && isAdmin) {
try {
console.log('Admin navigation empty, auto-seeding...');
await seedDefaultNavigation();
const newItems = await getAllNavigationItems();
if (active && Array.isArray(newItems)) {
const newAdminItems = newItems.filter(item => item.requires_admin);
setNavItems(newAdminItems);
}
} catch (seedError) {
console.error('Auto-seed failed:', seedError);
// Continue with empty navigation (will show fallback)
setNavItems(adminItems);
}
} else {
setNavItems(adminItems);
}
}
} catch (error) {
console.error('Failed to load admin navigation:', error);
} finally {
if (active) setNavLoading(false);
}
})();
return () => { active = false };
}, [isAdmin]);
// Keep active item in view upon route change - but only if it's not visible
useEffect(() => {
const node = scrollRef.current;
if (!node) return;
const active = node.querySelector('[data-navitem][data-active="true"]') as HTMLElement | null;
if (active) {
// Check if the active item is already visible in the viewport
const containerRect = node.getBoundingClientRect();
const activeRect = active.getBoundingClientRect();
const isVisible = (
activeRect.top >= containerRect.top &&
activeRect.bottom <= containerRect.bottom
);
// Only scroll if the active item is not fully visible
if (!isVisible) {
active.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
}
}
}, [location.pathname]);
return (
My Club
Admin Panel
Zpět na web
{/* Dynamic Navigation */}
{navLoading ? (
) : navItems.length > 0 ? (
// Render dynamic navigation
<>
{navItems.filter(item => item.visible).map((item, index) => {
const itemIcon = getIconForPageType(item.page_type);
const itemUrl = item.url || '#';
// Add badge for activities showing upcoming count
const isActivities = item.page_type === 'activities';
const showBadge = isActivities && upcomingCount > 0;
return (
{item.label}
{showBadge && (
{upcomingCount}
)}
);
})}
{/* MyUIbrix Editor - Special item */}
{
e?.preventDefault();
window.open('/?myuibrix=edit', '_blank');
}}
>
MyUIbrix Editor
>
) : (
// Fallback to hardcoded navigation
<>
Hlavní
Nástěnka
{isAdmin && (
Analytika
)}
Obsah
{/* Core sports entities first */}
Týmy
{/* Add subtle scroller hint */}
Zápasy
scroller
Aktivity
{upcomingCount > 0 && (
{upcomingCount}
)}
Hráči
{/* Other content */}
Články
Kategorie
O klubu
Videa
Galerie (Zonerama)
Tabule (Scoreboard)
Scoreboard Remote
Oblečení
Sponzoři
Bannery
Zprávy
Kontakty
Zpravodaj
Ankety
{isAdmin && (
<>
Nastavení
{
e?.preventDefault();
window.open('/?myuibrix=edit', '_blank');
}}
>
MyUIbrix Editor
Navigace
Alias soutěží
Prefetch & Cache
Uživatelé
Nastavení
Soubory
>
)}
>
)}
Odhlásit se
);
};
export default AdminSidebar;