'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 */}
{/* 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)}
);
})}
);
}