'use client'; import { useState } from 'react'; import { usePathname } from 'next/navigation'; import Link from 'next/link'; import { useSession } from 'next-auth/react'; import { useTranslations } from 'next-intl'; import { motion, AnimatePresence } from 'framer-motion'; import { LayoutDashboard, Target, Users, Plug, Shield, Settings, ChevronLeft, ChevronRight, ChevronDown, ClipboardList, CalendarClock, GraduationCap, UserCog, Clock, CalendarDays, Network, Building2, UserPlus, type LucideIcon, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { useSidebarStore } from '@/stores/sidebar-store'; import type { UserRole } from '@/types'; /** Navigation item configuration with role-based access */ interface NavItem { key: string; href: string; icon: LucideIcon; requiredRoles?: UserRole[]; children?: NavItem[]; exactMatch?: boolean; } /** Main navigation structure with role requirements */ const mainNavItems: NavItem[] = [ { key: 'dashboard', href: '/dashboard', icon: LayoutDashboard, // No requiredRoles = available to all authenticated users }, { key: 'lean', href: '/lean', icon: Target, requiredRoles: ['employee', 'department_head', 'manager', 'admin'], children: [ { key: 's3Planning', href: '/lean/s3-planning', icon: ClipboardList, }, { key: 'morningMeeting', href: '/lean/morning-meeting', icon: CalendarClock, }, { key: 'skillMatrix', href: '/lean/skill-matrix', icon: GraduationCap, }, ], }, { key: 'hr', href: '/hr', icon: Users, requiredRoles: ['department_head', 'manager', 'admin'], children: [ { key: 'employees', href: '/hr/employees', icon: UserCog, }, { key: 'timeTracking', href: '/hr/time-tracking', icon: Clock, }, { key: 'absences', href: '/hr/absences', icon: CalendarDays, }, { key: 'orgChart', href: '/hr/org-chart', icon: Network, }, ], }, ]; const bottomNavItems: NavItem[] = [ { key: 'admin', href: '/admin', icon: Shield, requiredRoles: ['admin'], children: [ { key: 'users', href: '/admin/users', icon: UserPlus, }, { key: 'departments', href: '/admin/departments', icon: Building2, }, { key: 'integrations', href: '/admin/integrations', icon: Plug, }, { key: 'systemSettings', href: '/admin/settings', icon: Settings, }, ], }, { key: 'settings', href: '/settings', icon: Settings, // Available to all users }, ]; interface SidebarProps { locale: string; } /** * Check if user has access to a navigation item based on roles */ function hasAccess(item: NavItem, userRoles: string[]): boolean { // If no roles required, everyone has access if (!item.requiredRoles || item.requiredRoles.length === 0) { return true; } // Check if user has any of the required roles return item.requiredRoles.some((role) => userRoles.includes(role)); } /** * Filter navigation items based on user roles */ function filterNavItems(items: NavItem[], userRoles: string[]): NavItem[] { return items .filter((item) => hasAccess(item, userRoles)) .map((item) => ({ ...item, children: item.children?.filter((child) => hasAccess(child, userRoles)), })); } /** * Collapsible sidebar component with role-based navigation * - 240px when expanded * - 64px when collapsed (icons only) * - Sub-navigation for nested items */ export function Sidebar({ locale }: SidebarProps) { const pathname = usePathname(); const { data: session } = useSession(); const t = useTranslations('navigation'); const { isExpanded, toggleSidebar } = useSidebarStore(); // Get user roles, defaulting to empty array const userRoles = session?.user?.roles || []; // Filter navigation based on user roles const filteredMainNav = filterNavItems(mainNavItems, userRoles); const filteredBottomNav = filterNavItems(bottomNavItems, userRoles); const isActive = (href: string, exactMatch?: boolean) => { const localePath = `/${locale}${href}`; if (exactMatch) return pathname === localePath; return pathname === localePath || pathname.startsWith(`${localePath}/`); }; const isParentActive = (item: NavItem) => { if (isActive(item.href)) return true; return item.children?.some((child) => isActive(child.href, child.exactMatch)) || false; }; return ( {/* Logo */}
t
{isExpanded && ( OS )}
{/* Main Navigation */} {/* Bottom Navigation */} {/* Collapse Button */}
); } interface NavItemComponentProps { item: NavItem; locale: string; isExpanded: boolean; isActive: (href: string, exactMatch?: boolean) => boolean; isParentActive: (item: NavItem) => boolean; t: (key: string) => string; } function NavItemComponent({ item, locale, isExpanded, isActive, isParentActive, t, }: NavItemComponentProps) { const Icon = item.icon; const active = isActive(item.href, item.exactMatch); const parentActive = isParentActive(item); const hasChildren = item.children && item.children.length > 0; const [isOpen, setIsOpen] = useState(parentActive); // Simple link (no children) if (!hasChildren) { const linkContent = ( {isExpanded && {t(item.key)}} ); if (!isExpanded) { return ( {linkContent} {t(item.key)} ); } return linkContent; } // Collapsible item with children if (!isExpanded) { // When collapsed, show tooltip with sub-navigation return ( {t(item.key)} {item.children?.map((child) => ( {t(child.key)} ))} ); } // Expanded collapsible return ( {item.children?.map((child) => { const ChildIcon = child.icon; const childActive = isActive(child.href, child.exactMatch); return ( {t(child.key)} ); })} ); }