mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #75
This commit is contained in:
@@ -45,6 +45,7 @@ import {
|
||||
Flex,
|
||||
Textarea,
|
||||
Collapse,
|
||||
Icon,
|
||||
} from '@chakra-ui/react';
|
||||
import AdminLayout from '../../layouts/AdminLayout';
|
||||
import {
|
||||
@@ -68,6 +69,26 @@ import {
|
||||
FaLinkedin,
|
||||
FaDiscord,
|
||||
FaTwitch,
|
||||
FaHome,
|
||||
FaInfoCircle,
|
||||
FaCalendarAlt,
|
||||
FaFutbol,
|
||||
FaUsers,
|
||||
FaTable,
|
||||
FaNewspaper,
|
||||
FaVideo,
|
||||
FaCamera,
|
||||
FaSearch,
|
||||
FaBars,
|
||||
FaCog,
|
||||
FaHandshake,
|
||||
FaEnvelope,
|
||||
FaUserShield,
|
||||
FaFolder,
|
||||
FaBook,
|
||||
FaTshirt,
|
||||
FaLink,
|
||||
FaPoll,
|
||||
} from 'react-icons/fa';
|
||||
// Using simple up/down buttons instead of drag-drop for better compatibility
|
||||
import {
|
||||
@@ -137,6 +158,31 @@ const SOCIAL_PLATFORMS = [
|
||||
{ value: 'twitch', label: 'Twitch', icon: FaTwitch },
|
||||
];
|
||||
|
||||
const NAV_ICON_OPTIONS = [
|
||||
{ value: 'FaHome', label: 'Domů', icon: FaHome },
|
||||
{ value: 'FaInfoCircle', label: 'O klubu', icon: FaInfoCircle },
|
||||
{ value: 'FaCalendarAlt', label: 'Kalendář', icon: FaCalendarAlt },
|
||||
{ value: 'FaFutbol', label: 'Hráči', icon: FaFutbol },
|
||||
{ value: 'FaUsers', label: 'Týmy', icon: FaUsers },
|
||||
{ value: 'FaTable', label: 'Tabulky', icon: FaTable },
|
||||
{ value: 'FaNewspaper', label: 'Články', icon: FaNewspaper },
|
||||
{ value: 'FaVideo', label: 'Videa', icon: FaVideo },
|
||||
{ value: 'FaCamera', label: 'Galerie', icon: FaCamera },
|
||||
{ value: 'FaHandshake', label: 'Sponzoři', icon: FaHandshake },
|
||||
{ value: 'FaEnvelope', label: 'Kontakt', icon: FaEnvelope },
|
||||
{ value: 'FaSearch', label: 'Hledat', icon: FaSearch },
|
||||
{ value: 'FaBars', label: 'Menu', icon: FaBars },
|
||||
{ value: 'FaLink', label: 'Odkaz', icon: FaLink },
|
||||
{ value: 'FaCog', label: 'Nastavení', icon: FaCog },
|
||||
{ value: 'FaPoll', label: 'Ankety', icon: FaPoll },
|
||||
{ value: 'FaUserShield', label: 'Uživatelé', icon: FaUserShield },
|
||||
{ value: 'FaFolder', label: 'Soubory', icon: FaFolder },
|
||||
{ value: 'FaBook', label: 'Stránka', icon: FaBook },
|
||||
{ value: 'FaTshirt', label: 'Oblečení', icon: FaTshirt },
|
||||
];
|
||||
|
||||
const ICON_COMPONENTS: Record<string, any> = Object.fromEntries(NAV_ICON_OPTIONS.map(opt => [opt.value, opt.icon]));
|
||||
|
||||
// NavItemCard component for hierarchical display
|
||||
interface NavItemCardProps {
|
||||
item: NavigationItem;
|
||||
@@ -153,6 +199,8 @@ interface NavItemCardProps {
|
||||
borderColor: string;
|
||||
hoverBg: string;
|
||||
level?: number;
|
||||
onChildMoveUp?: (parentId: number, index: number) => void;
|
||||
onChildMoveDown?: (parentId: number, index: number) => void;
|
||||
}
|
||||
|
||||
const NavItemCard: React.FC<NavItemCardProps> = ({
|
||||
@@ -170,6 +218,8 @@ const NavItemCard: React.FC<NavItemCardProps> = ({
|
||||
borderColor,
|
||||
hoverBg,
|
||||
level = 0,
|
||||
onChildMoveUp,
|
||||
onChildMoveDown,
|
||||
}) => {
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
const indentPx = level * 32;
|
||||
@@ -299,14 +349,14 @@ const NavItemCard: React.FC<NavItemCardProps> = ({
|
||||
{/* Render children if expanded */}
|
||||
{hasChildren && isExpanded && (
|
||||
<VStack spacing={2} align="stretch" mt={2}>
|
||||
{item.children!.map((child) => (
|
||||
{item.children!.map((child, childIndex) => (
|
||||
<NavItemCard
|
||||
key={child.id}
|
||||
item={child}
|
||||
index={0}
|
||||
total={1}
|
||||
onMoveUp={() => {}}
|
||||
onMoveDown={() => {}}
|
||||
index={childIndex}
|
||||
total={item.children!.length}
|
||||
onMoveUp={() => onChildMoveUp && onChildMoveUp(item.id!, childIndex)}
|
||||
onMoveDown={() => onChildMoveDown && onChildMoveDown(item.id!, childIndex)}
|
||||
onEdit={() => onEdit()}
|
||||
onDelete={() => onDelete()}
|
||||
onAddChild={() => {}}
|
||||
@@ -316,6 +366,8 @@ const NavItemCard: React.FC<NavItemCardProps> = ({
|
||||
borderColor={borderColor}
|
||||
hoverBg={hoverBg}
|
||||
level={level + 1}
|
||||
onChildMoveUp={onChildMoveUp}
|
||||
onChildMoveDown={onChildMoveDown}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
@@ -352,13 +404,9 @@ const NavigationAdminPage = () => {
|
||||
getAllNavigationItems(),
|
||||
getAllSocialLinks(),
|
||||
]);
|
||||
|
||||
console.log('Načtená navigace:', navData);
|
||||
console.log('Načtené sociální odkazy:', socialData);
|
||||
|
||||
|
||||
// Auto-seed if navigation is empty
|
||||
if (!navData || navData.length === 0) {
|
||||
console.log('Navigace je prázdná, automaticky vytváříme výchozí navigaci...');
|
||||
try {
|
||||
const seedResult = await seedDefaultNavigation();
|
||||
if (seedResult.seeded) {
|
||||
@@ -408,6 +456,43 @@ const NavigationAdminPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const moveChildNavItem = async (parentId: number, index: number, direction: 'up' | 'down') => {
|
||||
const moveWithin = async (
|
||||
list: NavigationItem[],
|
||||
setList: React.Dispatch<React.SetStateAction<NavigationItem[]>>
|
||||
): Promise<boolean> => {
|
||||
const parentIdx = list.findIndex((it) => it.id === parentId);
|
||||
if (parentIdx === -1) return false;
|
||||
const parent = list[parentIdx];
|
||||
const children = Array.isArray(parent.children) ? [...parent.children] : [];
|
||||
if (children.length === 0) return true;
|
||||
if (direction === 'up' && index === 0) return true;
|
||||
if (direction === 'down' && index === children.length - 1) return true;
|
||||
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
||||
[children[index], children[targetIndex]] = [children[targetIndex], children[index]];
|
||||
|
||||
const updatedParent: NavigationItem = { ...parent, children };
|
||||
const updated = [...list];
|
||||
updated[parentIdx] = updatedParent;
|
||||
setList(updated);
|
||||
|
||||
const orders = children.map((c, idx) => ({ id: c.id!, display_order: idx }));
|
||||
try {
|
||||
await reorderNavigationItems(orders);
|
||||
toast({ title: 'Pořadí aktualizováno', status: 'success', duration: 2000 });
|
||||
} catch (err) {
|
||||
toast({ title: 'Chyba při aktualizaci pořadí', status: 'error', duration: 3000 });
|
||||
loadData();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const doneFront = await moveWithin(navItems, setNavItems);
|
||||
if (!doneFront) {
|
||||
await moveWithin(adminNavItems, setAdminNavItems);
|
||||
}
|
||||
};
|
||||
|
||||
const moveNavItem = async (index: number, direction: 'up' | 'down') => {
|
||||
if (direction === 'up' && index === 0) return;
|
||||
if (direction === 'down' && index === navItems.length - 1) return;
|
||||
@@ -811,6 +896,8 @@ const NavigationAdminPage = () => {
|
||||
cardBg={cardBg}
|
||||
borderColor={borderColor}
|
||||
hoverBg={hoverBg}
|
||||
onChildMoveUp={(parentId, childIdx) => moveChildNavItem(parentId, childIdx, 'up')}
|
||||
onChildMoveDown={(parentId, childIdx) => moveChildNavItem(parentId, childIdx, 'down')}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
@@ -860,6 +947,8 @@ const NavigationAdminPage = () => {
|
||||
cardBg={cardBg}
|
||||
borderColor={borderColor}
|
||||
hoverBg={hoverBg}
|
||||
onChildMoveUp={(parentId, childIdx) => moveChildNavItem(parentId, childIdx, 'up')}
|
||||
onChildMoveDown={(parentId, childIdx) => moveChildNavItem(parentId, childIdx, 'down')}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1026,6 +1115,25 @@ const NavigationAdminPage = () => {
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Ikona</FormLabel>
|
||||
<Select
|
||||
value={editingNav?.icon || ''}
|
||||
onChange={(e) => setEditingNav({ ...editingNav!, icon: e.target.value || undefined })}
|
||||
>
|
||||
<option value="">Bez ikony</option>
|
||||
{NAV_ICON_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</Select>
|
||||
{editingNav?.icon && (
|
||||
<HStack mt={2} spacing={2} align="center">
|
||||
<Icon as={ICON_COMPONENTS[editingNav.icon]} boxSize={5} />
|
||||
<Text fontSize="sm">{editingNav.icon}</Text>
|
||||
</HStack>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{editingNav?.parent_id && (
|
||||
<Alert status="warning" fontSize="sm">
|
||||
<AlertIcon />
|
||||
@@ -1043,14 +1151,7 @@ const NavigationAdminPage = () => {
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>CSS třída (volitelné)</FormLabel>
|
||||
<Input
|
||||
value={editingNav?.icon || ''}
|
||||
onChange={(e) => setEditingNav({ ...editingNav!, icon: e.target.value })}
|
||||
placeholder="custom-class"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
|
||||
{editingNav?.type === 'external' && (
|
||||
<FormControl>
|
||||
|
||||
Reference in New Issue
Block a user