import { Activity, BarChart3, Boxes, CalendarDays, Crown, Database, Droplet, Factory, FlaskConical, History, Layers, LayoutDashboard, Lightbulb, ListTodo, type LucideIcon, MapPin, Package, Scissors, Search, ShieldCheck, ShoppingBag, Snowflake, Users, Warehouse, Wrench, Zap, } from "lucide-react"; import type { AuthUser } from "@/lib/auth/auth-context"; /** * The single source of truth for every area of the app: the desktop sidebar, * the phone bottom nav and its „Mehr" sheet all read from here, so an area * can never be reachable on one screen size and missing on another. */ export interface NavItem { label: string; /** Short name for the cramped bottom nav. */ shortLabel?: string; href: string; icon: LucideIcon; // Any one of these grants visibility - used for hub pages that fold // together several previously-separate settings pages, each gated by its // own permission. permission?: string | string[]; /** A number shown next to the label; the data comes from the matching hook. */ counter?: "suggestions"; } export interface NavGroup { label: string | null; items: NavItem[]; } export const NAV_GROUPS: NavGroup[] = [ { label: null, items: [ { label: "Dashboard", shortLabel: "Start", 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: "Prüfbereitschaft", href: "/compliance", icon: ShieldCheck, permission: "compliance:read", }, { label: "Überwinterung", href: "/winter", icon: Snowflake, permission: "winter-checks:read", }, ], }, { label: "Arbeit", items: [ { label: "Aufgaben", href: "/tasks", icon: ListTodo }, { label: "Vorschläge", href: "/suggestions", icon: Lightbulb, permission: "tasks:read", counter: "suggestions", }, { label: "Kalender", href: "/calendar", icon: CalendarDays }, ], }, { label: "Ernte & Lager", items: [ { label: "Ernten", href: "/harvests", icon: Scissors }, { label: "Schleuderungen", href: "/extractions", icon: FlaskConical }, { label: "Honigchargen", href: "/honey-batches", icon: Droplet }, { label: "Abfüllungen", href: "/bottling", icon: Package }, { label: "Rohstoffe", href: "/raw-materials", icon: Boxes, permission: "raw-material:read", }, { label: "Herstellungschargen", href: "/production-batches", icon: Factory, permission: "production-batch:read", }, { label: "Produkte", href: "/products", icon: ShoppingBag }, { label: "Lager", href: "/storage", icon: Warehouse }, { label: "Inventar", href: "/equipment", icon: Wrench }, ], }, { label: "Auswertung", items: [{ label: "Statistik", href: "/statistics", icon: BarChart3 }], }, ]; export const ADMIN_GROUP: NavGroup = { label: "Verwaltung", items: [ { label: "Benutzer & Rollen", href: "/settings/users-roles", icon: Users, permission: ["users:read", "roles:read"], }, { label: "Automatisierung", href: "/settings/automation", icon: Zap, permission: [ "webhooks:read", "automation:read", "task-templates:read", "label-templates:read", "site-settings:read", "calendar:configure", "api-keys:manage", ], }, { label: "Backup", href: "/settings/backup", icon: Database, permission: ["backup:export", "backup:import", "import:execute"], }, { label: "Audit-Log", href: "/settings/audit-log", icon: History, permission: "audit:read", }, { label: "System", href: "/settings/system", icon: Activity, permission: "system:read", }, ], }; /** The four favourites on the phone; everything else sits behind „Mehr". */ export const BOTTOM_NAV_HREFS = [ "/dashboard", "/colonies", "/tasks", "/search", ] as const; function isVisible( user: Pick, permission: NavItem["permission"], ): boolean { if (!permission) { return true; } const required = Array.isArray(permission) ? permission : [permission]; return required.some((key) => user.permissions.includes(key)); } /** All groups the user may see, without empty ones; admin group last. */ export function visibleNavGroups(user: Pick): NavGroup[] { return [...NAV_GROUPS, ADMIN_GROUP] .map((group) => ({ ...group, items: group.items.filter((item) => isVisible(user, item.permission)), })) .filter((group) => group.items.length > 0); } /** The bottom nav entries, resolved from the config in their fixed order. */ export function bottomNavItems(): NavItem[] { const all = NAV_GROUPS.flatMap((group) => group.items); return BOTTOM_NAV_HREFS.flatMap((href) => all.filter((item) => item.href === href)); } /** * Whether a nav entry is the current area. Matches on a path boundary, so * `/queens` does not light up for `/queens-archive`. */ export function isNavActive(pathname: string, href: string): boolean { return pathname === href || pathname.startsWith(`${href}/`); }