'use client'; /** * Einklappbare Sidebar fuer angemeldete Bereiche. * * Der Inhalt passt sich dynamisch an: * - eingeklappt: nur Icons (mit Tooltip via title) * - ausgeklappt: Icons + Beschriftung * - Rolle: ADMIN sieht zusaetzlich "Benutzerverwaltung" * * Der Zustand wird in localStorage persistiert, damit die Wahl * ueber Seitenwechsel hinweg erhalten bleibt. */ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; import { ThemeToggle } from '@/components/layout/ThemeToggle'; import { ProfileModal } from '@/components/profile/ProfileModal'; const STORAGE_KEY = 'calendar-sidebar-collapsed'; /** Flache 2D-Icons (Outline-Stil, Faerbung via currentColor). */ function NavIcon({ name }: { name: 'dashboard' | 'plus' | 'users' }) { const common = { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const, 'aria-hidden': true, }; switch (name) { case 'dashboard': return ( ); case 'plus': return ( ); case 'users': return ( ); } } interface NavItem { href: string; label: string; icon: 'dashboard' | 'plus' | 'users'; adminOnly?: boolean; } const NAV_ITEMS: NavItem[] = [ { href: '/dashboard', label: 'Dashboard', icon: 'dashboard' }, { href: '/admin/calendars', label: 'Kalender verwalten', icon: 'plus', adminOnly: true }, { href: '/admin/users', label: 'Benutzerverwaltung', icon: 'users', adminOnly: true }, ]; export function Sidebar({ role, userEmail, username, }: { role: 'ADMIN' | 'USER'; /** E-Mail des angemeldeten Benutzers (Fallback fuer das Profil-Badge). */ userEmail: string; /** Frei waehlbarer Anzeigename; wird im Badge bevorzugt. */ username?: string | null; }) { const [isCollapsed, setIsCollapsed] = useState(false); const [isInitialized, setIsInitialized] = useState(false); const [isLoggingOut, setIsLoggingOut] = useState(false); /** Profil-Modal offen (geoeffnet via Klick auf das Profil-Badge). */ const [isProfileOpen, setIsProfileOpen] = useState(false); const pathname = usePathname(); const router = useRouter(); // Gespeicherten Zustand wiederherstellen (nur clientseitig, kein // Hydration-Mismatch, da der erste Render konsistent eingeklappt-los ist). useEffect(() => { const stored = window.localStorage.getItem(STORAGE_KEY); if (stored === 'true') { setIsCollapsed(true); } setIsInitialized(true); }, []); function toggleCollapsed() { setIsCollapsed((previous) => { const next = !previous; window.localStorage.setItem(STORAGE_KEY, String(next)); return next; }); } async function logout() { setIsLoggingOut(true); try { await fetch('/api/auth/logout', { method: 'POST' }); router.push('/login'); router.refresh(); } finally { setIsLoggingOut(false); } } const visibleItems = NAV_ITEMS.filter( (item) => !item.adminOnly || role === 'ADMIN', ); return ( ); }