2025-09-09 13:12:57 +02:00

253 lines
10 KiB
TypeScript

'use client'
import { useState, useMemo } from 'react'
import { useRouter, usePathname } from 'next/navigation'
import Button from './Button'
import SidebarFooter from './SidebarFooter'
type Submenu = 'teams' | 'players' | null
export default function Sidebar() {
const router = useRouter()
const pathname = usePathname()
const [isOpen, setIsOpen] = useState(false) // mobile drawer
const [openSubmenu, setOpenSubmenu] = useState<Submenu>(null)
const isActive = (path: string) => pathname === path
const navBtnBase =
'w-full flex items-center gap-x-3.5 py-2 px-2.5 text-sm rounded-lg transition-colors'
const activeClasses =
'bg-gray-100 dark:bg-neutral-700 text-gray-900 dark:text-white'
const idleClasses =
'text-gray-800 hover:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-700'
const toggleSubmenu = (key: Exclude<Submenu, null>) =>
setOpenSubmenu(prev => (prev === key ? null : key))
// Gemeinsamer Inhalt (wird in Desktop-Aside und im Mobile-Drawer benutzt)
const SidebarInner = useMemo(
() => (
<div className="flex flex-col h-full min-h-0">
<header className="p-4 flex items-center justify-between">
<span className="font-semibold text-xl text-black dark:text-white">Iron:e</span>
{/* Close-Button nur im mobilen Drawer sichtbar */}
<button
onClick={() => setIsOpen(false)}
className="sm:hidden inline-flex items-center justify-center rounded-md p-2 text-gray-500 hover:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700"
aria-label="Close sidebar"
>
<svg className="size-5" viewBox="0 0 24 24" stroke="currentColor" fill="none">
<path d="M18 6L6 18M6 6l12 12" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</header>
<nav className="flex-1 min-h-0 overflow-y-auto px-2">
<ul className="space-y-1">
{/* Dashboard */}
<li>
<Button
onClick={() => { router.push('/dashboard'); setIsOpen(false) }}
size="sm"
variant="link"
className={`${navBtnBase} ${isActive('/dashboard') ? activeClasses : idleClasses}`}
>
<svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
<path d="M9 22V12h6v10" />
</svg>
Dashboard
</Button>
</li>
{/* Teams (mit Submenu) */}
<li>
<Button
onClick={() => toggleSubmenu('teams')}
size="sm"
variant="link"
className={`${navBtnBase} ${idleClasses} justify-between`}
>
<span className="flex items-center gap-x-3.5">
<svg className="size-5" viewBox="0 0 640 640" fill="currentColor">
<path d="M320 64c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm96 312c0 25-12.7 47-32 59.9V528c0 26.5-21.5 48-48 48h-32c-26.5 0-48-21.5-48-48v-92.1c-19.3-12.9-32-34.9-32-59.9v-40c0-53 43-96 96-96s96 43 96 96v40zM160 96c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56zm16 240v32c0 32.5 12.1 62.1 32 84.7V528c0 1.2 0 2.5.1 3.7-8.6 7.6-19.8 12.3-31.1 12.3H144c-26.5 0-48-21.5-48-48v-56.6C76.9 428.4 64 407.7 64 384v-32c0-53 43-96 96-96 12.7 0 24.8 2.5 35.9 6.9-12.6 21.4-19.9 46.8-19.9 73.1zM480 96c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56zm-48 432v-75.3c19.9-22.5 32-52.2 32-84.7v-32c0-26.7-7.3-52.1-19.9-73.1 11.1-4.4 23.2-6.9 35.9-6.9 53 0 96 43 96 96v32c0 23.7-12.9 44.4-32 55.4V496c0 26.5-21.5 48-48 48h-32c-10.8 0-21-3.6-29.1-9.7.1-1.2.1-2.5.1-3.7z" />
</svg>
Teams
</span>
<svg
className={`size-4 transition-transform ${openSubmenu === 'teams' ? 'rotate-180' : ''}`}
viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
>
<path d="M6 9l6 6 6-6" />
</svg>
</Button>
{openSubmenu === 'teams' && (
<ul className="pl-6 space-y-1 mt-1">
<li>
<Button
onClick={() => { router.push('/teams'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Übersicht
</Button>
</li>
<li>
<Button
onClick={() => { router.push('/teams/manage'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Teamverwaltung
</Button>
</li>
</ul>
)}
</li>
{/* Spieler (mit Submenu) */}
<li>
<Button
onClick={() => toggleSubmenu('players')}
size="sm"
variant="link"
className={`${navBtnBase} ${idleClasses} justify-between`}
>
<span className="flex items-center gap-x-3.5">
<svg className="size-4" viewBox="0 0 24 24" fill="currentColor">
<path
fillRule="evenodd"
d="M12 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm-2 9a4 4 0 0 0-4 4v1a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-1a4 4 0 0 0-4-4h-4Z"
clipRule="evenodd"
/>
</svg>
Spieler
</span>
<svg
className={`size-4 transition-transform ${openSubmenu === 'players' ? 'rotate-180' : ''}`}
viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
>
<path d="M6 9l6 6 6-6" />
</svg>
</Button>
{openSubmenu === 'players' && (
<ul className="pl-6 space-y-1 mt-1">
<li>
<Button
onClick={() => { router.push('/players'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Übersicht
</Button>
</li>
<li>
<Button
onClick={() => { router.push('/players/stats'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Statistiken
</Button>
</li>
</ul>
)}
</li>
{/* Spielplan */}
<li>
<Button
onClick={() => { router.push('/schedule'); setIsOpen(false) }}
size="sm"
variant="link"
className={`${navBtnBase} ${isActive('/schedule') ? activeClasses : idleClasses}`}
>
<svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
<line x1="16" x2="16" y1="2" y2="6" />
<line x1="8" x2="8" y1="2" y2="6" />
<line x1="3" x2="21" y1="10" y2="10" />
<path d="M8 14h.01M12 14h.01M16 14h.01M8 18h.01M12 18h.01M16 18h.01" />
</svg>
Spielplan
</Button>
</li>
</ul>
</nav>
<footer className="mt-auto border-t border-gray-200 dark:border-neutral-700">
<SidebarFooter />
</footer>
</div>
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[pathname, openSubmenu]
)
return (
<>
{/* Mobile Toggle Button (nur < sm) */}
<button
onClick={() => setIsOpen(true)}
className="sm:hidden fixed z-50 top-3 left-3 inline-flex items-center justify-center p-2 rounded-md text-gray-500 hover:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700"
aria-label="Open sidebar"
>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path
clipRule="evenodd" fillRule="evenodd"
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"
/>
</svg>
</button>
{/* Desktop Sidebar (sticky) */}
<aside
className="
hidden sm:flex sm:flex-col
w-64 shrink-0
border-e border-gray-200 dark:border-neutral-700
bg-white dark:bg-neutral-800
sticky top-0 h-dvh
"
aria-label="Sidebar"
>
{SidebarInner}
</aside>
{/* Mobile Drawer */}
{isOpen && (
<div className="sm:hidden fixed inset-0 z-50" role="dialog" aria-modal="true">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/40"
onClick={() => setIsOpen(false)}
/>
{/* Panel */}
<div
className="
absolute inset-y-0 left-0 w-64
bg-white dark:bg-neutral-800
border-e border-gray-200 dark:border-neutral-700
shadow-xl
transform transition-transform duration-200 ease-out
"
>
{SidebarInner}
</div>
</div>
)}
</>
)
}