feat(frontend): Linksseitige Navigation, Seitenanimation, ADHS-freundliche Gruppierung

Ersetzt die horizontale Kopfzeilen-Navigation durch eine feste
Sidebar links: gruppiert nach Betrieb / Arbeit / Ernte & Lager /
Verwaltung mit Icon+Label je Eintrag, um kognitive Last zu
reduzieren. Aktiver Zustand klar farblich hervorgehoben. Mobile
Ansicht (Bottom-Nav + FAB) bleibt unveraendert bestehen.

Sanfte, kurze Seitenuebergangsanimation (PageTransition, respektiert
prefers-reduced-motion ueber motion-safe:) fuer Orientierung ohne
Ablenkung.

Ausserdem: wiederkehrenden Anzeigefehler in Status-Filtern behoben
(Select zeigte den rohen Sentinel-Wert "ALL" statt "Alle Status" an)
auf Voelker-, Honigchargen-, Aufgaben-, Koeniginnen- und
Bienenstand-Listen. Live in Desktop-, Tablet- und Mobilbreite
verifiziert, kein horizontales Overflow.
This commit is contained in:
Nick Szaunig committed 2026-08-23 06:18:35 +02:00
1 parent fb2b09a929
commit ecb3afe1f5
9 files changed
+195 -100

No files matched your search

+134
View File
@@ -0,0 +1,134 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
CalendarDays,
Crown,
Droplet,
FileClock,
Hexagon,
History,
LayoutDashboard,
Layers,
ListTodo,
type LucideIcon,
MapPin,
Package,
Search,
ShoppingBag,
Users,
Warehouse,
Wrench,
} from "lucide-react";
import { cn } from "@/lib/utils";
import type { AuthUser } from "@/lib/auth/auth-context";
interface NavItem {
label: string;
href: string;
icon: LucideIcon;
}
interface NavGroup {
label: string | null;
items: NavItem[];
}
const NAV_GROUPS: NavGroup[] = [
{
label: null,
items: [
{ label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
{ label: "Suche", href: "/search", icon: Search },
],
},
{
label: "Betrieb",
items: [
{ label: "Bienenstände", href: "/apiaries", icon: MapPin },
{ label: "Völker", href: "/colonies", icon: Layers },
{ label: "Königinnen", href: "/queens", icon: Crown },
],
},
{
label: "Arbeit",
items: [
{ label: "Aufgaben", href: "/tasks", icon: ListTodo },
{ label: "Kalender", href: "/calendar", icon: CalendarDays },
],
},
{
label: "Ernte & Lager",
items: [
{ label: "Honigchargen", href: "/honey-batches", icon: Droplet },
{ label: "Abfüllungen", href: "/bottling/new", icon: Package },
{ label: "Produkte", href: "/products", icon: ShoppingBag },
{ label: "Lager", href: "/storage", icon: Warehouse },
{ label: "Inventar", href: "/equipment", icon: Wrench },
],
},
];
const ADMIN_GROUP: NavGroup = {
label: "Verwaltung",
items: [
{ label: "Benutzer", href: "/settings/users", icon: Users },
{ label: "Audit-Log", href: "/settings/audit-log", icon: History },
],
};
function SidebarLink({ item, pathname }: { item: NavItem; pathname: string }) {
const Icon = item.icon;
const isActive = pathname.startsWith(item.href);
return (
<Link
href={item.href}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-150",
isActive
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
)}
>
<Icon className="size-4.5 shrink-0" />
<span className="truncate">{item.label}</span>
</Link>
);
}
export function Sidebar({ user }: { user: AuthUser }) {
const pathname = usePathname();
const groups = user.role === "ADMIN" ? [...NAV_GROUPS, ADMIN_GROUP] : NAV_GROUPS;
return (
<aside className="hidden w-60 shrink-0 flex-col gap-1 overflow-y-auto border-r bg-card px-3 py-4 sm:flex">
<Link
href="/dashboard"
className="mb-4 flex items-center gap-2 px-2 font-heading text-lg font-semibold"
>
<Hexagon className="size-5.5 fill-primary/15 text-primary" strokeWidth={2.25} />
ApiCore
</Link>
<nav className="flex flex-1 flex-col gap-4">
{groups.map((group, index) => (
<div key={group.label ?? `group-${index}`} className="flex flex-col gap-0.5">
{group.label ? (
<p className="px-3 pb-1 text-xs font-medium tracking-wide text-muted-foreground uppercase">
{group.label}
</p>
) : null}
{group.items.map((item) => (
<SidebarLink key={item.href} item={item} pathname={pathname} />
))}
</div>
))}
</nav>
<div className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-muted-foreground">
<FileClock className="size-3.5 shrink-0" />
<span className="truncate">v1.x</span>
</div>
</aside>
);
}