'use client' import { useState, useRef } from 'react' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' 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, Calendar, Menu, X, HelpCircle, ChevronDown, Building2, FileInput, Wallet, TrendingUp, ClipboardCheck, HandCoins, } from 'lucide-react' 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 type { EntityType } from '@/types' interface ExtensionNavItem { href: string label: string icon: string } interface DashboardNavProps { companyName: string entityType: EntityType uncategorizedTransactionCount?: number pendingOperationsCount?: number isSandbox?: boolean extensionNavItems?: ExtensionNavItem[] } interface NavItem { href: string label: string icon: typeof LayoutDashboard group: string modes?: EntityType[] // If set, only visible for these entity types. If not set, visible to all. hidden?: boolean // Temporarily hide from sidebar comingSoon?: boolean // Visible but disabled; shows "Kommer snart" badge } // All nav items for sidebar and mobile drawer const navItems: NavItem[] = [ { href: '/', label: 'Översikt', icon: LayoutDashboard, group: 'main' }, { href: '/kpi', label: 'Nyckeltal', icon: TrendingUp, group: 'main' }, { href: '/deadlines', label: 'Deadlines', icon: Calendar, group: 'main' }, // AR — Accounts Receivable { href: '/invoices', label: 'Fakturor', icon: Receipt, group: 'försäljning' }, { href: '/customers', label: 'Kunder', icon: Users, group: 'försäljning' }, // AP — Accounts Payable { href: '/expenses', label: 'Leverantörsfakturor', icon: Wallet, group: 'inköp' }, // Temporarily hidden pending module rework (see feedback #49) { href: '/suppliers', label: 'Leverantörer', icon: Building2, group: 'inköp', hidden: true }, { href: '/supplier-invoices', label: 'Leverantörsfakturor', icon: FileInput, group: 'inköp', hidden: true }, // General accounting { href: '/pending', label: 'Granskning', icon: ClipboardCheck, group: 'redovisning' }, { href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight, group: 'redovisning' }, { href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'redovisning' }, { href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'redovisning' }, { href: '/import', label: 'Importera', icon: Upload, group: 'redovisning' }, // Personal — temporarily disabled in production pending feature completion. // Still clickable in local dev (NODE_ENV === 'development') so we can test. { href: '/salary', label: 'Löner', icon: HandCoins, group: 'personal', modes: ['aktiebolag'], comingSoon: process.env.NODE_ENV !== 'development' }, { href: '/salary/employees', label: 'Anställda', icon: Users, group: 'personal', modes: ['aktiebolag'], comingSoon: process.env.NODE_ENV !== 'development' }, { href: '/help', label: 'Hjälp', icon: HelpCircle, group: 'övrigt' }, { href: '/settings', label: 'Inställningar', icon: Settings, group: 'övrigt' }, ] const groupLabels: Record = { main: 'Huvudmeny', försäljning: 'Försäljning', inköp: 'Inköp', personal: 'Personal', redovisning: 'Redovisning', övrigt: 'Övrigt', } 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 [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const [isClosing, setIsClosing] = useState(false) const closeTimerRef = useRef | null>(null) // When the user has no active company (e.g. just archived their last // one), every company-scoped route is unreachable. We keep them visible // so the sidebar doesn't collapse, but render them as disabled. // Only /settings remains navigable — from there the user can either // create a new company or delete their account. const hasCompany = !!company const ALWAYS_ENABLED = new Set(['/settings']) const isItemEnabled = (href: string) => hasCompany || ALWAYS_ENABLED.has(href) // Auto-expand Övrigt when the user is on one of its pages, or when manually toggled 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 () => { await supabase.auth.signOut() router.push(isSandbox ? '/sandbox' : '/login') } const isActive = (href: string) => { if (href === '/') { return pathname === '/' } // For parent routes that have a sibling sub-route in the nav (e.g. /salary vs /salary/employees), // only match the parent for exact or non-overlapping sub-paths 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) } // Filter nav items by entity type, hidden flag, and conditional visibility const filteredItems = navItems.filter(item => { if (item.hidden) return false if (item.modes && !item.modes.includes(entityType)) return false // Only show Granskning when there are pending operations 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') // Groups rendered as distinct sidebar sections (AR, AP, Accounting) const sidebarGroups = [ { 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' }, ] as const const mobileNavItems = [ { href: '/', label: 'Översikt', icon: LayoutDashboard }, { href: '/invoices', label: 'Fakturor', icon: Receipt }, { href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight }, ] return ( <> {/* Desktop sidebar */} {/* Mobile bottom navigation */} {/* Mobile menu — bottom sheet */} {isMobileMenuOpen && ( <> {/* Backdrop */}