'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useTranslations } from 'next-intl'; import { TesseraLogo } from '@/components/brand/tessera-logo'; import { useSidebarStore } from '@/lib/stores/sidebar-store'; import { useMarketplaceStore } from '@/lib/stores/marketplace-store'; import { SidebarSearch } from '@/components/layout/sidebar-search'; import { AppVersionBadge } from '@/components/layout/app-version-badge'; const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; interface SidebarModule { id: string; slug: string; name: string; category: string; } export function Sidebar() { const t = useTranslations('sidebar'); const tCommon = useTranslations('common'); const { isCollapsed, isMobileOpen, toggle, setMobileOpen } = useSidebarStore(); const [activeModules, setActiveModules] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [openCategories, setOpenCategories] = useState>(new Set()); const pathname = usePathname(); const sidebarRefreshKey = useMarketplaceStore((s) => s.sidebarRefreshKey); const isActive = (href: string) => { if (href === '/') return pathname === '/'; return pathname.startsWith(href); }; const linkClass = (href: string) => `flex items-center gap-3 rounded-md px-2 py-2 text-sm transition-colors ${ isActive(href) ? 'bg-sidebar-accent text-sidebar-accent-foreground font-medium' : 'text-sidebar-foreground hover:bg-muted' }`; const fetchActiveModules = useCallback(async () => { try { const res = await fetch(`${API_URL}/modules/active`, { credentials: 'include', }); if (res.ok) { setActiveModules(await res.json()); } } catch { // silently fail } }, []); // Befund 17 (quick-260921-gof): sidebarRefreshKey ist der Auffrisch- // Ausloeser aus dem Marketplace-Speicher — ohne ihn erscheint ein frisch // aktiviertes Modul in der Seitenleiste erst nach einem Neuladen der // Seite, die Navigation liefe also dem Berechtigungsstand hinterher. // biome-ignore lint/correctness/useExhaustiveDependencies: sidebarRefreshKey ist der Auffrisch-Ausloeser aus dem Marketplace-Speicher - ohne ihn liefe die Navigation dem Berechtigungsstand hinterher. useEffect(() => { fetchActiveModules(); }, [fetchActiveModules, sidebarRefreshKey]); const toggleCategory = (cat: string) => { setOpenCategories((prev) => { const next = new Set(prev); if (next.has(cat)) next.delete(cat); else next.add(cat); return next; }); }; const filteredCategories = useMemo(() => { const categories = new Map(); for (const mod of activeModules) { const cat = mod.category || 'other'; if (!categories.has(cat)) categories.set(cat, []); categories.get(cat)!.push(mod); } if (!searchQuery) return Array.from(categories.entries()); const q = searchQuery.toLowerCase(); const result: [string, SidebarModule[]][] = []; for (const [cat, mods] of categories.entries()) { if (cat.toLowerCase().includes(q)) { result.push([cat, mods]); } else { const filtered = mods.filter((m) => m.name.toLowerCase().includes(q)); if (filtered.length > 0) result.push([cat, filtered]); } } return result; }, [activeModules, searchQuery]); const sidebarContent = (
{/* Versionszeile (quick-260914-ku1): bewusst ohne `hidden md:block`, damit auch die mobile Schublade sie zeigt; eingeklappt nichts (Muster oben). */} {!isCollapsed && (
)}
); return ( <> {isMobileOpen && (
setMobileOpen(false)} aria-hidden="true" />
)} ); }