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:
1 parent
fb2b09a929
commit
ecb3afe1f5
9 files changed
+195
-100
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user