253 lines
10 KiB
TypeScript
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>
|
|
)}
|
|
</>
|
|
)
|
|
}
|