From ecb3afe1f5c760466977eeae9c12c72744f4c084 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Sun, 23 Aug 2026 06:18:35 +0200 Subject: [PATCH] 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. --- .../src/app/(app)/apiaries/page.tsx | 2 +- .../src/app/(app)/colonies/page.tsx | 2 +- .../src/app/(app)/honey-batches/page.tsx | 2 +- ApiCore-Frontend/src/app/(app)/layout.tsx | 124 ++++------------ .../src/app/(app)/page-transition.tsx | 13 ++ .../src/app/(app)/queens/page.tsx | 2 +- ApiCore-Frontend/src/app/(app)/sidebar.tsx | 134 ++++++++++++++++++ ApiCore-Frontend/src/app/(app)/tasks/page.tsx | 4 +- ApiCore-Frontend/src/app/globals.css | 12 ++ 9 files changed, 195 insertions(+), 100 deletions(-) create mode 100644 ApiCore-Frontend/src/app/(app)/page-transition.tsx create mode 100644 ApiCore-Frontend/src/app/(app)/sidebar.tsx diff --git a/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx b/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx index c59ccbf..6ba3fed 100644 --- a/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx @@ -48,7 +48,7 @@ export default function ApiariesPage() { onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as ApiaryStatus))} > - + {status || "Alle Status"} Alle Status diff --git a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx index 2f779c2..4c5693e 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx @@ -69,7 +69,7 @@ export default function ColoniesPage() { onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as ColonyStatus))} > - + {status || "Alle Status"} Alle Status diff --git a/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx b/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx index 15ebda7..42b9893 100644 --- a/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx @@ -70,7 +70,7 @@ export default function HoneyBatchesPage() { } > - + {status || "Alle Status"} Alle Status diff --git a/ApiCore-Frontend/src/app/(app)/layout.tsx b/ApiCore-Frontend/src/app/(app)/layout.tsx index 0c7241e..faeb296 100644 --- a/ApiCore-Frontend/src/app/(app)/layout.tsx +++ b/ApiCore-Frontend/src/app/(app)/layout.tsx @@ -2,61 +2,18 @@ import { ReactNode, useEffect } from "react"; import Link from "next/link"; -import { usePathname, useRouter } from "next/navigation"; -import { Hexagon, MoreHorizontal } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { Hexagon } from "lucide-react"; import { useAuth } from "@/lib/auth/auth-context"; import { NotificationBell } from "@/components/notification-bell"; import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +import { Sidebar } from "./sidebar"; import { BottomNav } from "./bottom-nav"; import { QuickAddMenu } from "./quick-add-menu"; - -const NAV_LINK_CLASS = - "shrink-0 rounded-md px-2.5 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary"; - -const PRIMARY_LINKS = [ - { label: "Dashboard", href: "/dashboard" }, - { label: "Bienenstände", href: "/apiaries" }, - { label: "Völker", href: "/colonies" }, - { label: "Königinnen", href: "/queens" }, - { label: "Aufgaben", href: "/tasks" }, - { label: "Kalender", href: "/calendar" }, - { label: "Honigchargen", href: "/honey-batches" }, - { label: "Lager", href: "/storage" }, - { label: "Suche", href: "/search" }, -]; - -const MORE_LINKS = [ - { label: "Produkte", href: "/products" }, - { label: "Abfüllungen", href: "/bottling/new" }, - { label: "Inventar", href: "/equipment" }, -]; - -const ADMIN_LINKS = [ - { label: "Benutzer", href: "/settings/users" }, - { label: "Audit-Log", href: "/settings/audit-log" }, -]; - -function NavLink({ href, label, pathname }: { href: string; label: string; pathname: string }) { - return ( - - {label} - - ); -} +import { PageTransition } from "./page-transition"; export default function AppLayout({ children }: { children: ReactNode }) { const router = useRouter(); - const pathname = usePathname(); const { user, isLoading, logout } = useAuth(); useEffect(() => { @@ -70,58 +27,37 @@ export default function AppLayout({ children }: { children: ReactNode }) { } return ( -
-
- -
- - - {user.email} - - +
+
+ {children}
- -
{children}
+
diff --git a/ApiCore-Frontend/src/app/(app)/page-transition.tsx b/ApiCore-Frontend/src/app/(app)/page-transition.tsx new file mode 100644 index 0000000..351fdfd --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/page-transition.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { ReactNode } from "react"; +import { usePathname } from "next/navigation"; + +export function PageTransition({ children }: { children: ReactNode }) { + const pathname = usePathname(); + return ( +
+ {children} +
+ ); +} diff --git a/ApiCore-Frontend/src/app/(app)/queens/page.tsx b/ApiCore-Frontend/src/app/(app)/queens/page.tsx index 87582b2..6ef29d8 100644 --- a/ApiCore-Frontend/src/app/(app)/queens/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/queens/page.tsx @@ -56,7 +56,7 @@ export default function QueensPage() { onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as QueenStatus))} > - + {status || "Alle Status"} Alle Status diff --git a/ApiCore-Frontend/src/app/(app)/sidebar.tsx b/ApiCore-Frontend/src/app/(app)/sidebar.tsx new file mode 100644 index 0000000..e63c763 --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/sidebar.tsx @@ -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 ( + + + {item.label} + + ); +} + +export function Sidebar({ user }: { user: AuthUser }) { + const pathname = usePathname(); + const groups = user.role === "ADMIN" ? [...NAV_GROUPS, ADMIN_GROUP] : NAV_GROUPS; + + return ( + + ); +} diff --git a/ApiCore-Frontend/src/app/(app)/tasks/page.tsx b/ApiCore-Frontend/src/app/(app)/tasks/page.tsx index fbb5f4b..1be583a 100644 --- a/ApiCore-Frontend/src/app/(app)/tasks/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/tasks/page.tsx @@ -69,7 +69,7 @@ export default function TasksPage() { onValueChange={(value) => setStatus(value === "ALL" ? "" : ((value ?? "") as TaskStatus))} > - + {status || "Alle Status"} Alle Status @@ -87,7 +87,7 @@ export default function TasksPage() { } > - + {priority || "Alle Prioritäten"} Alle Prioritäten diff --git a/ApiCore-Frontend/src/app/globals.css b/ApiCore-Frontend/src/app/globals.css index 7df2050..82e3e0d 100644 --- a/ApiCore-Frontend/src/app/globals.css +++ b/ApiCore-Frontend/src/app/globals.css @@ -50,6 +50,18 @@ --radius-2xl: calc(var(--radius) * 1.8); --radius-3xl: calc(var(--radius) * 2.2); --radius-4xl: calc(var(--radius) * 2.6); + --animate-page-in: page-in 260ms ease-out; +} + +@keyframes page-in { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } } :root {