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}
+
+
+
+
+
);
})}