"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useAuth } from "./auth-provider"; import { useTheme } from "next-themes"; import type { LucideIcon } from "lucide-react"; import { Archive, BarChart3, BookOpen, Bot, Boxes, Building2, CheckCircle, ChevronDown, ClipboardList, FileText, LayoutDashboard, LogOut, MapPin, Menu, Moon, Package, Plus, QrCode, Settings as SettingsIcon, ShoppingCart, Sun, Tag, Truck, Users, Wrench, Zap, } from "lucide-react";; import { Button } from "@/components/ui/button"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { BrandStripe } from "@/components/brand-stripe"; import { Separator } from "@/components/ui/separator"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; /* ============================================================================ * Sidebar nav structure * * Flat sections — every nav group renders the same. The active state is * the theme's single accent. No per-category brand colours so the eye * scans label + icon, not colour. * ========================================================================== */ type NavEntry = { href: string; label: string; icon: LucideIcon; }; const dashboardItems: NavEntry[] = [ { href: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, ]; const managementItems: NavEntry[] = [ { href: "/companies", label: "Companies", icon: Building2 }, { href: "/customers", label: "Customers", icon: Users }, // Office pin tool + (future) visit log. The page itself is a // launcher right now — the full visit-history view is blocked on // PB schema + FleetAI credentials per the rep-visit-check-in // design doc. { href: "/visits", label: "Visits", icon: MapPin }, { href: "/suppliers", label: "Suppliers", icon: Truck }, ]; const quoteItems: NavEntry[] = [ { href: "/quotes", label: "Quote List", icon: FileText }, // ── TEMPORARILY HIDDEN ────────────────────────────────────────────── // Quote sprint board — disabled pending a rebuild. Operators should // process quotes through /quotes/[id]/process until this is // re-enabled. Revert by uncommenting the line below. // { href: "/sprint", label: "Sprint", icon: Zap }, { href: "/quotes/approvals", label: "Pending Approvals", icon: CheckCircle }, ]; // Assign Drivers used to live here as its own sidebar entry, but the // driver-assignment workflow is now fully embedded in the Collection // Pool page (per-row Assign + per-bucket bulk Assign). Keeping a // redundant top-level link only invited confusion, so it's gone. // The full /orders/assign route is still reachable by URL for the // older admin flow but is no longer surfaced in the nav. const orderItems: NavEntry[] = [ { href: "/orders/new", label: "New Order", icon: Plus }, { href: "/orders", label: "Order List", icon: ShoppingCart }, // ── TEMPORARILY HIDDEN ────────────────────────────────────────────── // Order sprint board — disabled pending a rebuild. Operators should // work orders through /orders/[id] until this is re-enabled. // Revert by uncommenting the line below. // { href: "/orders/sprint", label: "Order Sprint", icon: Truck }, { href: "/orders/tasks", label: "Buyer Tasks", icon: ClipboardList }, ]; // Office-side view of the driver / collection workflow. The driver app // does the actual pickups — these pages are how the office sees and // manages that activity. Split out of Operations so the menu reflects // the actual lifecycle: orders originate in Orders, become physical // work here in Logistics, then close out in Operations (invoicing). const logisticsItems: NavEntry[] = [ { href: "/collection-pool", label: "Collection Pool", icon: Package }, { href: "/collection-map", label: "Collection Map", icon: MapPin }, { href: "/collection-confirm", label: "Collection Confirm", icon: CheckCircle }, ]; const operationsItems: NavEntry[] = [ { href: "/invoicing", label: "Invoicing", icon: FileText }, { href: "/delivery-notes", label: "Delivery Notes", icon: ClipboardList }, // Post-invoice physical-delivery queue. Tracks the // invoiced → with_driver → checked_in lifecycle. Picked // up from the Invoicing page once the office hits // "Mark invoiced", lives here until the driver brings // back the signed paper and the operator types the // reference number. n8n will eventually auto-complete // these (docs/deliveries-n8n-workflow.md). { href: "/deliveries", label: "Deliveries", icon: Truck }, // Counter-side scanner for the printed delivery-note QR. Separate // standalone PWA at /scan (it declares its own manifest), linked // here so the office can find it from the CRM too. { href: "/scan", label: "DN Scanner", icon: QrCode }, ]; // Stock is its own top-level group (not nested under Operations) so the // stock-orders / pricelist / stock-catalog trio lives together. Pricelist // was previously the lone "Other" item; promoting it here keeps related // inventory-adjacent surfaces together without inventing a new // catch-all bucket just for one entry. const stockNavItems: NavEntry[] = [ { href: "/stock", label: "Stock", icon: Boxes }, { href: "/stock-orders", label: "Stock Orders", icon: Archive }, { href: "/pricelist", label: "Pricelist", icon: Tag }, ]; // Repairs lives here for now — it doesn't fit Logistics (it's not a // driver / collection workflow) and it doesn't fit Operations (which // is reserved for the order-close-out loop). Move it once it has a // clearer home. const otherItems: NavEntry[] = [ { href: "/repairs", label: "Repairs", icon: Wrench }, { href: "/giellie", label: "Giellie Agent", icon: Bot }, // Standard Operating Procedures for the operator roles. Markdown // source of truth lives in docs/sops/ — the /sops route renders // it. See src/lib/sops.ts. { href: "/sops", label: "SOPs", icon: BookOpen }, // Monthly per-rep quotes/orders roll-up. Reuses the same // getBossDashboardData aggregation as the /m phone dashboard's // month tab — see /api/v1/reports/rep-performance. { href: "/reports/rep-performance", label: "Rep Performance", icon: BarChart3 }, ]; /* ============================================================================ * Active-route resolution * The naïve "exact OR starts-with-prefix" rule double-highlights * `/orders/new` and `/orders`. We fix it by demanding the item is the * most-specific match within its group. * ========================================================================== */ function isActive(itemHref: string, siblings: NavEntry[], pathname: string): boolean { if (pathname === itemHref) return true; if (!pathname.startsWith(itemHref + "/")) return false; return !siblings.some( (other) => other.href !== itemHref && other.href.length > itemHref.length && (pathname === other.href || pathname.startsWith(other.href + "/")) ); } /* ============================================================================ * NavSection — collapsible group with a small uppercase label * ========================================================================== */ function NavSection({ label, items, pathname, defaultOpen = false, onLinkClick, }: { label: string; items: NavEntry[]; pathname: string; defaultOpen?: boolean; onLinkClick?: () => void; }) { const sectionActive = items.some( (i) => pathname === i.href || pathname.startsWith(i.href + "/") ); const [open, setOpen] = useState(defaultOpen || sectionActive); useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect if (sectionActive) setOpen(true); }, [sectionActive]); return (
{user?.name || "User"}
{user?.email}