'use client' import { useState, useRef } from 'react' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import { useTranslations } from 'next-intl' import { createClient } from '@/lib/supabase/client' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' import { LayoutDashboard, Receipt, Users, ArrowLeftRight, BookOpen, BarChart3, Settings, LogOut, Upload, Inbox, Menu, X, HelpCircle, ChevronDown, Building2, Wallet, TrendingUp, ClipboardCheck, HandCoins, Package, } from 'lucide-react' import { getBranding } from '@/lib/branding/service' import { ENABLED_EXTENSION_IDS as _ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' import { resolveIcon } from '@/lib/extensions/icon-resolver' import { SupportLink } from '@/components/ui/support-link' import CompanySwitcher from '@/components/dashboard/CompanySwitcher' import { useCompany } from '@/contexts/CompanyContext' import { clearRecaptIdentity } from '@/lib/recapt' import type { EntityType } from '@/types' void _ENABLED_EXTENSION_IDS interface ExtensionNavItem { href: string label: string icon: string } interface DashboardNavProps { companyName: string entityType: EntityType uncategorizedTransactionCount?: number pendingOperationsCount?: number isSandbox?: boolean extensionNavItems?: ExtensionNavItem[] } type NavLabelKey = | 'dashboard' | 'kpi' | 'invoice_inbox' | 'invoices' | 'customers' | 'supplier_invoices' | 'suppliers' | 'review' | 'transactions' | 'bookkeeping' | 'assets' | 'reports' | 'import' | 'salary' | 'employees' | 'help' | 'settings' type GroupKey = 'main' | 'försäljning' | 'inköp' | 'redovisning' | 'personal' | 'övrigt' interface NavItem { href: string labelKey: NavLabelKey icon: typeof LayoutDashboard group: GroupKey modes?: EntityType[] hidden?: boolean comingSoon?: boolean devBadge?: boolean betaBadge?: boolean } const navItems: NavItem[] = [ { href: '/', labelKey: 'dashboard', icon: LayoutDashboard, group: 'main' }, { href: '/kpi', labelKey: 'kpi', icon: TrendingUp, group: 'main' }, { href: '/e/general/invoice-inbox', labelKey: 'invoice_inbox', icon: Inbox, group: 'main', betaBadge: true }, { href: '/invoices', labelKey: 'invoices', icon: Receipt, group: 'försäljning' }, { href: '/customers', labelKey: 'customers', icon: Users, group: 'försäljning' }, { href: '/supplier-invoices', labelKey: 'supplier_invoices', icon: Wallet, group: 'inköp' }, { href: '/suppliers', labelKey: 'suppliers', icon: Building2, group: 'inköp', hidden: true }, { href: '/pending', labelKey: 'review', icon: ClipboardCheck, group: 'redovisning' }, { href: '/transactions', labelKey: 'transactions', icon: ArrowLeftRight, group: 'redovisning' }, { href: '/bookkeeping', labelKey: 'bookkeeping', icon: BookOpen, group: 'redovisning' }, { href: '/assets', labelKey: 'assets', icon: Package, group: 'redovisning' }, { href: '/reports', labelKey: 'reports', icon: BarChart3, group: 'redovisning' }, { href: '/import', labelKey: 'import', icon: Upload, group: 'redovisning' }, { href: '/salary', labelKey: 'salary', icon: HandCoins, group: 'personal', modes: ['aktiebolag'], betaBadge: true }, { href: '/salary/employees', labelKey: 'employees', icon: Users, group: 'personal', modes: ['aktiebolag'], betaBadge: true }, { href: '/help', labelKey: 'help', icon: HelpCircle, group: 'övrigt' }, { href: '/settings', labelKey: 'settings', icon: Settings, group: 'övrigt' }, ] // Map known extension hrefs to nav translation keys so sidebar labels translate. // Extensions whose manifest label happens to be English-ready can stay null. function extensionLabelKey(href: string): string | null { if (href === '/e/general/tic') return 'ext_tic' if (href === '/e/general/invoice-inbox') return 'ext_invoice_inbox' return null } const groupLabelKey: Record = { main: 'group_main', försäljning: 'group_sales', inköp: 'group_purchases', redovisning: 'group_accounting', personal: 'group_personnel', övrigt: 'group_other', } export default function DashboardNav({ companyName: _companyName, entityType, uncategorizedTransactionCount = 0, pendingOperationsCount = 0, isSandbox = false, extensionNavItems = [] }: DashboardNavProps) { const pathname = usePathname() const router = useRouter() const supabase = createClient() const { company } = useCompany() const tNav = useTranslations('nav') const tCommon = useTranslations('common') const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const [isClosing, setIsClosing] = useState(false) const closeTimerRef = useRef | null>(null) const hasCompany = !!company const ALWAYS_ENABLED = new Set(['/settings']) const isItemEnabled = (href: string) => hasCompany || ALWAYS_ENABLED.has(href) const isOnOvrigtPage = ['/help', '/settings', '/e/'].some(p => pathname.startsWith(p)) const [manualOvrigtExpanded, setManualOvrigtExpanded] = useState(false) const isOvrigtExpanded = isOnOvrigtPage || manualOvrigtExpanded const openMobileMenu = () => { if (closeTimerRef.current) { clearTimeout(closeTimerRef.current) closeTimerRef.current = null } setIsClosing(false) setIsMobileMenuOpen(true) } const handleLogout = async () => { clearRecaptIdentity() await supabase.auth.signOut() router.push(isSandbox ? '/sandbox' : '/login') } const isActive = (href: string) => { if (href === '/') { return pathname === '/' } if (href === '/salary') { return pathname === '/salary' || pathname.startsWith('/salary/runs') } return pathname.startsWith(href) } const closeMobileMenu = () => { setIsClosing(true) closeTimerRef.current = setTimeout(() => { setIsMobileMenuOpen(false) setIsClosing(false) closeTimerRef.current = null }, 200) } const hiddenNavHrefs = new Set(getBranding().hiddenNavHrefs) const filteredItems = navItems.filter(item => { if (item.hidden) return false if (hiddenNavHrefs.has(item.href)) return false if (item.modes && !item.modes.includes(entityType)) return false if (item.href === '/pending' && pendingOperationsCount === 0) return false return true }) const mainItems = filteredItems.filter(i => i.group === 'main') const övrigtItems = filteredItems.filter(i => i.group === 'övrigt') const sidebarGroups: { key: GroupKey; items: NavItem[]; spacing: string }[] = [ { key: 'försäljning', items: filteredItems.filter(i => i.group === 'försäljning'), spacing: 'mb-4' }, { key: 'inköp', items: filteredItems.filter(i => i.group === 'inköp'), spacing: 'mb-4' }, { key: 'redovisning', items: filteredItems.filter(i => i.group === 'redovisning'), spacing: 'mb-4' }, { key: 'personal', items: filteredItems.filter(i => i.group === 'personal'), spacing: 'mb-6' }, ] const mobileNavItems: { href: string; labelKey: NavLabelKey; icon: typeof LayoutDashboard }[] = [ { href: '/', labelKey: 'dashboard', icon: LayoutDashboard }, { href: '/invoices', labelKey: 'invoices', icon: Receipt }, { href: '/transactions', labelKey: 'transactions', icon: ArrowLeftRight }, ] const renderBadge = (item: NavItem | { comingSoon?: boolean; devBadge?: boolean; betaBadge?: boolean }, position: 'sidebar' | 'mobile') => { const baseClass = position === 'sidebar' ? 'ml-auto rounded-full bg-muted/60 text-muted-foreground/70 text-[9px] font-medium uppercase tracking-wider px-1.5 py-0.5' : 'rounded-full bg-muted/60 text-muted-foreground/70 text-[9px] font-medium uppercase tracking-wider px-1.5 py-0.5' if (item.comingSoon) return {tNav('badge_coming_soon')} if (item.devBadge) return {tNav('badge_dev')} if (item.betaBadge) return {tNav('badge_beta')} return null } return ( <> {/* Desktop sidebar */} {/* Mobile bottom navigation */} {/* Mobile menu — bottom sheet */} {isMobileMenuOpen && ( <> {/* Backdrop */}