diff --git a/frontend/src/components/shared/Sidebar.tsx b/frontend/src/components/shared/Sidebar.tsx index d82d233..b4e0e36 100644 --- a/frontend/src/components/shared/Sidebar.tsx +++ b/frontend/src/components/shared/Sidebar.tsx @@ -1,7 +1,6 @@ -import { useState, useMemo } from 'react'; +import { useMemo } from 'react'; import { Box, - Collapse, Drawer, IconButton, List, @@ -23,8 +22,6 @@ import { AdminPanelSettings, Settings, Menu as MenuIcon, - ExpandMore, - ExpandLess, LocalShipping, BugReport, BookOnline, @@ -37,31 +34,17 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { useLayout, DRAWER_WIDTH, DRAWER_WIDTH_COLLAPSED } from '../../contexts/LayoutContext'; import { usePermissionContext } from '../../contexts/PermissionContext'; -import { vehiclesApi } from '../../services/vehicles'; import { preferencesApi } from '../../services/settings'; export { DRAWER_WIDTH, DRAWER_WIDTH_COLLAPSED }; -interface SubItem { - text: string; - path: string; -} - interface NavigationItem { text: string; icon: JSX.Element; path: string; - subItems?: SubItem[]; permission?: string; } -const adminSubItems: SubItem[] = [ - { text: 'Information', path: '/admin?tab=0' }, - { text: 'System Mitteilungen', path: '/admin?tab=1' }, - { text: 'Tool Zugriff', path: '/admin?tab=2' }, - { text: 'Daten', path: '/admin?tab=3' }, -]; - const baseNavigationItems: NavigationItem[] = [ { text: 'Dashboard', @@ -125,18 +108,12 @@ const baseNavigationItems: NavigationItem[] = [ text: 'Bestellungen', icon: , path: '/bestellungen', - subItems: [ - { text: 'Übersicht', path: '/bestellungen?tab=0' }, - { text: 'Lieferanten', path: '/bestellungen?tab=1' }, - { text: 'Katalog', path: '/bestellungen?tab=2' }, - ], permission: 'bestellungen:view', }, { text: 'Interne Bestellungen', icon: , path: '/ausruestungsanfrage', - // subItems computed dynamically in navigationItems useMemo permission: 'ausruestungsanfrage:view', }, { @@ -149,19 +126,12 @@ const baseNavigationItems: NavigationItem[] = [ text: 'Buchhaltung', icon: , path: '/buchhaltung', - subItems: [ - { text: 'Übersicht', path: '/buchhaltung?tab=0' }, - { text: 'Transaktionen', path: '/buchhaltung?tab=1' }, - { text: 'Konten', path: '/buchhaltung?tab=2' }, - { text: 'Haushaltspläne', path: '/haushaltsplan' }, - ], permission: 'buchhaltung:view', }, { text: 'Issues', icon: , path: '/issues', - // subItems computed dynamically in navigationItems useMemo permission: 'issues:view_own', }, ]; @@ -170,7 +140,6 @@ const adminItem: NavigationItem = { text: 'Admin', icon: , path: '/admin', - subItems: adminSubItems, }; const adminSettingsItem: NavigationItem = { @@ -190,13 +159,6 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { const { sidebarCollapsed, toggleSidebar } = useLayout(); const { hasPermission } = usePermissionContext(); - // Fetch vehicle list for dynamic dropdown sub-items - const { data: vehicleList } = useQuery({ - queryKey: ['vehicles', 'sidebar'], - queryFn: () => vehiclesApi.getAll(), - staleTime: 2 * 60 * 1000, - }); - const { data: preferences } = useQuery({ queryKey: ['user-preferences'], queryFn: preferencesApi.get, @@ -205,88 +167,22 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { const menuOrder: string[] = (preferences?.menuOrder as string[] | undefined) ?? []; - const vehicleSubItems: SubItem[] = useMemo( - () => { - const items: SubItem[] = [ - { text: 'Übersicht', path: '/fahrzeuge?tab=0' }, - ]; - (vehicleList ?? []).forEach((v) => { - items.push({ - text: v.bezeichnung ?? v.kurzname, - path: `/fahrzeuge/${v.id}`, - }); - }); - if (hasPermission('fahrzeuge:edit')) { - items.push({ text: 'Einstellungen', path: '/fahrzeuge?tab=1' }); - } - return items; - }, - [vehicleList, hasPermission], - ); - const navigationItems = useMemo((): NavigationItem[] => { - const fahrzeugeItem: NavigationItem = { - text: 'Fahrzeuge', - icon: , - path: '/fahrzeuge', - subItems: vehicleSubItems.length > 0 ? vehicleSubItems : undefined, - permission: 'fahrzeuge:view', - }; - - // Build Ausrüstungsanfrage sub-items dynamically based on permissions (tab order must match Ausruestungsanfrage.tsx) - const ausruestungSubItems: SubItem[] = []; - let ausruestungTabIdx = 0; - if (hasPermission('ausruestungsanfrage:create_request')) { ausruestungSubItems.push({ text: 'Meine Anfragen', path: `/ausruestungsanfrage?tab=${ausruestungTabIdx}` }); ausruestungTabIdx++; } - if (hasPermission('ausruestungsanfrage:approve')) { ausruestungSubItems.push({ text: 'Alle Anfragen', path: `/ausruestungsanfrage?tab=${ausruestungTabIdx}` }); ausruestungTabIdx++; } - if (hasPermission('ausruestungsanfrage:view')) { ausruestungSubItems.push({ text: 'Katalog', path: `/ausruestungsanfrage?tab=${ausruestungTabIdx}` }); ausruestungTabIdx++; } - - // Build Issues sub-items dynamically (tab order must match Issues.tsx) - const issuesSubItems: SubItem[] = [ - { text: 'Meine Issues', path: '/issues?tab=0' }, - { text: 'Zugewiesene', path: '/issues?tab=1' }, - ]; - if (hasPermission('issues:view_all')) { - issuesSubItems.push({ text: 'Alle Issues', path: '/issues?tab=2' }); - } - if (hasPermission('issues:edit_settings')) { - issuesSubItems.push({ text: 'Einstellungen', path: `/issues?tab=${issuesSubItems.length}` }); - } - - // Build Checklisten sub-items dynamically (tab order must match Checklisten.tsx) - const checklistenSubItems: SubItem[] = [ - { text: 'Übersicht', path: '/checklisten?tab=0' }, - ]; - if (hasPermission('checklisten:manage_templates')) { - checklistenSubItems.push({ text: 'Vorlagen', path: '/checklisten?tab=1' }); - } - checklistenSubItems.push({ text: 'Historie', path: `/checklisten?tab=${checklistenSubItems.length}` }); - const items = baseNavigationItems .map((item) => { - if (item.path === '/fahrzeuge') return fahrzeugeItem; - if (item.path === '/ausruestung') { - const ausruestungSubs: SubItem[] = [ - { text: 'Übersicht', path: '/ausruestung?tab=0' }, - ]; - if (hasPermission('ausruestung:manage_types')) { - ausruestungSubs.push({ text: 'Einstellungen', path: '/ausruestung?tab=1' }); - } - return ausruestungSubs.length > 0 ? { ...item, subItems: ausruestungSubs } : item; - } + // Ausrüstungsanfrage is visible if user has any of the three relevant permissions if (item.path === '/ausruestungsanfrage') { - const canSeeAusruestung = + const canSee = hasPermission('ausruestungsanfrage:view') || hasPermission('ausruestungsanfrage:create_request') || hasPermission('ausruestungsanfrage:approve'); - return { ...item, subItems: ausruestungSubItems, permission: canSeeAusruestung ? undefined : 'ausruestungsanfrage:view' }; + return { ...item, permission: canSee ? undefined : 'ausruestungsanfrage:view' }; } - if (item.path === '/issues') return { ...item, subItems: issuesSubItems }; - if (item.path === '/checklisten') return { ...item, subItems: checklistenSubItems }; return item; }) .filter((item) => !item.permission || hasPermission(item.permission)); - // Apply custom menu order: items in menuOrder are sorted to their index; rest keep relative order + // Apply custom menu order if (menuOrder.length > 0) { items.sort((a, b) => { const aIdx = menuOrder.indexOf(a.path); @@ -299,20 +195,7 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { } return hasPermission('admin:view') ? [...items, adminItem, adminSettingsItem] : items; - }, [vehicleSubItems, hasPermission, menuOrder]); - - // Expand state for items with sub-items — auto-expand when route matches - const [expandedItems, setExpandedItems] = useState>({}); - - const isExpanded = (item: NavigationItem) => { - if (expandedItems[item.path] !== undefined) return expandedItems[item.path]; - // Auto-expand when the current route matches - return location.pathname === item.path || location.pathname.startsWith(item.path + '/'); - }; - - const toggleExpand = (path: string) => { - setExpandedItems((prev) => ({ ...prev, [path]: !isExpanded({ path } as NavigationItem) })); - }; + }, [hasPermission, menuOrder]); const handleNavigation = (path: string) => { navigate(path); @@ -330,98 +213,50 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { {navigationItems.map((item) => { const isActive = location.pathname === item.path; - const hasSubItems = item.subItems && item.subItems.length > 0; - const expanded = hasSubItems && isExpanded(item); return ( - - - - handleNavigation(item.path)} - aria-label={`Zu ${item.text} navigieren`} - sx={{ - justifyContent: sidebarCollapsed ? 'center' : 'initial', - '&.Mui-selected': { - backgroundColor: 'primary.light', - color: 'primary.contrastText', - '&:hover': { - backgroundColor: 'primary.main', - }, - '& .MuiListItemIcon-root': { - color: 'primary.contrastText', - }, + + + handleNavigation(item.path)} + aria-label={`Zu ${item.text} navigieren`} + sx={{ + justifyContent: sidebarCollapsed ? 'center' : 'initial', + '&.Mui-selected': { + backgroundColor: 'primary.light', + color: 'primary.contrastText', + '&:hover': { + backgroundColor: 'primary.main', }, + '& .MuiListItemIcon-root': { + color: 'primary.contrastText', + }, + }, + }} + > + - - {item.icon} - - - {hasSubItems && !sidebarCollapsed && ( - { - e.stopPropagation(); - toggleExpand(item.path); - }} - sx={{ color: isActive ? 'inherit' : 'text.secondary' }} - > - {expanded ? : } - - )} - - - - {hasSubItems && !sidebarCollapsed && ( - - - {item.subItems!.map((sub) => { - const isSubActive = location.pathname + location.search === sub.path; - return ( - handleNavigation(sub.path)} - selected={isSubActive} - sx={{ - pl: 4, - py: 0.5, - '&.Mui-selected': { - backgroundColor: 'primary.light', - color: 'primary.contrastText', - '&:hover': { - backgroundColor: 'primary.main', - }, - }, - }} - > - - - ); - })} - - - )} - + {item.icon} + + + + + ); })}