import React, { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; import { NavLink, useLocation } from "react-router-dom"; import { getVersionString } from "@/lib/version"; import { BarChart3, Briefcase, Calendar, CheckSquare, Clipboard, ClipboardCheck, Grid3x3, Layers, LayoutDashboard, MessageSquare, Settings, Users, ShieldCheck, Package, Clock, Key, Shield, Bell, FileText, Link, FolderOpen, Loader2, CalendarRange, CalendarClock, Bug, Award, Gauge, BadgeCheck, ListChecks, CalendarDays, MessageCircle, CalendarCheck, BookOpen, ChevronDown, ChevronRight, User, Download, ActivitySquare, Heart, Search, X, CalendarPlus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import Logo from "@/components/ui/logo"; import { Separator } from "@/components/ui/separator"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useAuth } from "@/lib/auth-context"; import { roleFeatureApi } from "@/services/roleFeatureApi"; import { useIsMobile } from "@/hooks/use-mobile"; interface SidebarProps { isOpen: boolean; setIsOpen: (value: boolean) => void; } // Map of feature IDs to their respective icon components const featureIconMap: Record = { 1: , // Dashboard 2: , // Projects 3: , // Deliverables 4: , // Teams 5: , // Employees 6: , // Roles 7: , // Permissions 8: , // Role Features 9: , // Backlog 10: , // Issues 11: , // Tasks 12: , // Time Logs 13: , // Calendar 14: , // Reports 15: , // Messages 16: , // Settings 17: , // Notifications 18: , // Audit Logs 19: , // Integrations 20: , // File Management 21: , // Attendance 22: , // Leaves 23: , // Leaves Admin 24: , // Leave Types 25: , // Todos 26: , // Holidays 27: , // Bugs & Defects 28: , // Appraisals 29: , // Appraisal Cycles 30: , // Assessment Areas 31: , // Competencies 32: , // Manager Dashboard 33: , // Meeting Rooms 34: , 35: , // Communications 36: , // Role Assessment Questions 37: , // HR Remarks 38: , // Management Remarks 39: , // Production Bugs 40: , // Test Cases 41: , // My Profile 42: , // Leave Balances 43: , // Employee Projects 44: , // Health Dashboard 45: , // Asset Management 46: , // System Details 47: , // Asset Assignments 48: , // Asset Requests (Employee) 49: , // Asset Request Management (Admin) 50: , // Gantt Chart 51: , // Resource Allocation 52: , // Feature Management 53: , // Role Permissions 54: , // Work From Home 55: , // Add Comp Off Request 56: , // PMP (Project Management Plan) }; // Map of feature IDs to their respective paths const featurePathMap: Record = { 1: "/dashboard", 2: "/projects", 3: "/deliverables", 4: "/teams", 5: "/employees", 6: "/roles", 7: "/permissions", 8: "/role-features", 9: "/backlog", 10: "/issues", 11: "/tasks", 12: "/time-logs", 13: "/calendar", 14: "/reports", 15: "/messages", 16: "/settings", 17: "/notifications", 18: "/audit-logs", 19: "/integrations", 20: "/file-management", 21: "/attendance", 22: "/leaves", 23: "/leaves-admin", 24: "/leave-types", 25: "/todos", 26: "/holidays", 27: "/bugs", 28: "/appraisals/my-appraisals", 29: "/appraisals/cycles", 30: "/appraisals/assessment-areas", 31: "/appraisals/competencies", 32: "/appraisals/manager-dashboard", 33: "/meeting-room/rooms", 34: "/meeting-room/bookings", 35: "/communications", 36: "/appraisals/role-questions", 37: "/appraisals/hr-remarks", 38: "/appraisals/management-remarks", 39: "/production-bugs", 40: "/test-cases", 41: "/my-profile", 42: "/leave-balances", 43: "/employee-projects", 44: "/health-dashboard", 45: "/assets", 46: "/system-details", 47: "/asset-assignments", 48: "/asset-requests", 49: "/admin/asset-requests", 50: "/gantt-chart", 51: "/resource-allocation", 52: "/features", 53: "/role-permissions", 54: "/leaves?wfh=true", 55: "/leaves?compoff=true", 56: "/pmp", }; // Map of feature IDs to their titles const featureTitleMap: Record = { 1: "Dashboard", 2: "Projects", 3: "Deliverables", 4: "Teams", 5: "Employees", 6: "Roles", 7: "Permissions", 8: "Role Features", 9: "Backlog", 10: "Features", 11: "Tasks", 12: "Time Logs", 13: "Calendar", 14: "Reports", 15: "Messages", 16: "Settings", 17: "Notifications", 18: "Audit Logs", 19: "Integrations", 20: "File Management", 21: "Attendance", 22: "My Leaves", 23: "Leaves Management", 24: "Leave Types", 25: "Todos", 26: "Holidays", 27: "Bugs", 28: "My Appraisals", 29: "Appraisal Cycles", 30: "Assessment Areas", 31: "Competencies", 32: "Manager Appraisals", 33: "Meeting Rooms", 34: "Room Bookings", 35: "Communications", 36: "Role Questions", 37: "HR Remarks", 38: "Management Remarks", 39: "Production Bugs", 40: "Test Cases", 41: "My Profile", 42: "Leave Balances", 43: "Employee Projects", 44: "System Health", 45: "Assets", 46: "System Details", 47: "Asset Assignments", 48: "My Asset Requests", 49: "Asset Request Management", 50: "Gantt Chart", 51: "Resource Allocation", 52: "Feature Management", 53: "Role Permissions", 54: "Work From Home", 55: "Add Comp Off Request", 56: "PMP", }; // Feature categories for grouped sidebar const featureCategories = { dashboard: { title: "Dashboard", icon: , features: [1] }, projects: { title: "Project Management", icon: , features: [2, 3, 4, 9, 10, 50, 56] }, tasks: { title: "Task Management", icon: , features: [11, 12, 25] }, bugs: { title: "QA Management", icon: , features: [27, 39, 40] }, hr: { title: "HR Management", icon: , features: [5, 21, 22, 23, 24, 26, 42, 43, 51, 54, 55] }, appraisal: { title: "Appraisals", icon: , features: [28, 29, 30, 31, 32, 36, 37, 38] }, meeting: { title: "Meetings", icon: , features: [33, 34] }, communication: { title: "Communication", icon: , features: [15, 35] }, assets: { title: "Asset Management", icon: , features: [45, 46, 47, 48, 49] }, admin: { title: "Administration", icon: , features: [6, 7, 8, 16, 17, 18, 19, 20, 44, 52, 53] // Added health dashboard, features, and role permissions to admin category }, reports: { title: "Reports & Analytics", icon: , features: [13, 14] } }; // Function to get the category for a feature const getFeatureCategory = (featureId: number): string => { for (const [category, details] of Object.entries(featureCategories)) { if (details.features.includes(featureId)) { return category; } } return "other"; // Default category }; // Type definitions for grouped navigation items interface NavItem { title: string; icon: React.ReactNode; path: string; featureId: number; } interface NavGroup { title: string; icon: React.ReactNode; items: NavItem[]; } const Sidebar = ({ isOpen, setIsOpen }: SidebarProps) => { const { userData, isAuthenticated } = useAuth(); const [navItems, setNavItems] = useState([]); const [groupedNavItems, setGroupedNavItems] = useState>({}); const [expandedGroups, setExpandedGroups] = useState>({ // Default expanded groups dashboard: true, tasks: true, bugs: true }); const [isLoading, setIsLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const location = useLocation(); const isMobile = useIsMobile(); // Filter nav items based on search query const filteredGroupedNavItems = React.useMemo(() => { if (!searchQuery.trim()) return groupedNavItems; const query = searchQuery.toLowerCase(); const filtered: Record = {}; Object.entries(groupedNavItems).forEach(([category, group]) => { const filteredItems = group.items.filter(item => item.title.toLowerCase().includes(query) ); if (filteredItems.length > 0) { filtered[category] = { ...group, items: filteredItems }; } }); return filtered; }, [groupedNavItems, searchQuery]); // Auto-expand groups when searching useEffect(() => { if (searchQuery.trim()) { const newExpandedGroups: Record = { ...expandedGroups }; Object.keys(filteredGroupedNavItems).forEach(category => { newExpandedGroups[category] = true; }); setExpandedGroups(newExpandedGroups); } }, [searchQuery, filteredGroupedNavItems]); // Check if we're on the download app page const isDownloadPage = location.pathname === "/download-app"; // Initialize expanded state for all categories useEffect(() => { const initialExpandedState: Record = {}; const currentPath = window.location.pathname; // Find the active item's category let activeCategory = ""; Object.entries(featureCategories).forEach(([category, details]) => { // First, mark all categories as collapsed initialExpandedState[category] = false; // Then find which category has the active item if (details.features.some(featureId => { const path = featurePathMap[featureId] || ""; return isNavItemActive(path); })) { activeCategory = category; } }); // Only expand the category containing the active item (and dashboard) initialExpandedState["dashboard"] = true; // Dashboard always open if (activeCategory) { initialExpandedState[activeCategory] = true; } setExpandedGroups(initialExpandedState); }, []); // Monitor path changes to update expanded groups useEffect(() => { const handleRouteChange = () => { const currentPath = window.location.pathname; let activeCategory = ""; // Find which category contains the active item Object.entries(featureCategories).forEach(([category, details]) => { if (details.features.some(featureId => { const path = featurePathMap[featureId] || ""; return isNavItemActive(path); })) { activeCategory = category; } }); // Update expanded state if (activeCategory) { setExpandedGroups(prev => ({ ...prev, [activeCategory]: true })); } }; // Call once to set initial state handleRouteChange(); // Add event listener for navigation changes window.addEventListener('popstate', handleRouteChange); return () => { window.removeEventListener('popstate', handleRouteChange); }; }, [window.location.pathname]); // Effect to fetch role features when authentication state changes useEffect(() => { if (isAuthenticated) { fetchRoleFeatures(); } }, [isAuthenticated, userData?.roleId]); // Toggle group expansion const toggleGroup = (category: string) => { setExpandedGroups(prev => ({ ...prev, [category]: !prev[category] })); }; const fetchRoleFeatures = async () => { try { setIsLoading(true); // Admin (roleId 21) gets access to all features if (userData?.roleId === 21) { const allNavItems = Object.keys(featurePathMap).map(featureId => { const id = parseInt(featureId); return { title: featureTitleMap[id] || "Unknown", icon: featureIconMap[id] || , path: featurePathMap[id] || "/", featureId: id }; }); // Sort navigation items by feature ID allNavItems.sort((a, b) => a.featureId - b.featureId); setNavItems(allNavItems); // Group the nav items by category const grouped = groupNavItems(allNavItems); setGroupedNavItems(grouped); setIsLoading(false); return; } // For client role, only show the Production Bugs module if (userData?.roleName?.toLowerCase() === "client" || userData?.roleId === 7) { // Assuming 7 is the client roleId, but also check roleName const clientNavItems = [ { title: featureTitleMap[39] || "Production Bugs", // Production Bugs feature icon: featureIconMap[39] || , path: featurePathMap[39] || "/production-bugs", featureId: 39 }, // Add Dashboard as well { title: featureTitleMap[1] || "Dashboard", icon: featureIconMap[1] || , path: featurePathMap[1] || "/", featureId: 1 } ]; setNavItems(clientNavItems); // Group the nav items by category const grouped = groupNavItems(clientNavItems); setGroupedNavItems(grouped); setIsLoading(false); return; } // For other roles, fetch their specific features if (userData?.roleId) { const roleFeatures = await roleFeatureApi.getByRoleId(userData.roleId); // Get unique feature IDs (a role might have multiple permissions for the same feature) const uniqueFeatureIds = Array.from(new Set(roleFeatures.map(rf => rf.featureId))); // Make sure we have at least the Dashboard, Issues, Leaves, and Todos const essentialFeatures = [1, 10, 22, 25, 26, 27, 33, 34, 35, 39, 40, 42, 43, 45, 46, 47, 48, 50, 51, 54, 55, 56]; // Dashboard, Issues, Leaves, Todos, Holidays, Bugs, Meeting Rooms, Room Bookings, Communications, Production Bugs, Test Cases, Leave Balances, Employee Projects, Assets, System Details, Asset Assignments, My Asset Requests, Gantt Chart, Resource Allocation, Work From Home, Add Comp Off Request, PMP essentialFeatures.forEach(featId => { if (!uniqueFeatureIds.includes(featId)) { uniqueFeatureIds.push(featId); } }); // Add Asset Request Management only for admin roles const isAssetAdmin = userData.roleId === 1 || userData.roleId === 21 || userData.roleName?.toLowerCase() === "admin"; if (isAssetAdmin && !uniqueFeatureIds.includes(49)) { uniqueFeatureIds.push(49); // Asset Request Management } // Add health dashboard only for CEO, COO, and Admin roles // Assuming roleId 1 = CEO, roleId 2 = COO, roleId 21 = Admin const isExecutive = userData.roleId === 1 || userData.roleId === 2 || userData.roleId === 21 || userData.roleName?.toLowerCase() === "ceo" || userData.roleName?.toLowerCase() === "coo" || userData.roleName?.toLowerCase() === "admin"; if (isExecutive && !uniqueFeatureIds.includes(44)) { uniqueFeatureIds.push(44); // Health Dashboard } // Always include Appraisal features for HR roles (HR and HR Manager) if (userData.roleId === 10 || userData.roleId === 11) { // Assuming roleId 10 and 11 for HR and HR Manager const appraisalFeatures = [28, 29, 30, 31, 32, 36, 37, 38]; // My Appraisals, Cycles, Areas, Competencies, Manager Dashboard, Role Questions, HR Remarks, Management Remarks appraisalFeatures.forEach(featId => { if (!uniqueFeatureIds.includes(featId)) { uniqueFeatureIds.push(featId); } }); } else { // For non-HR roles, at least add My Appraisals if (!uniqueFeatureIds.includes(28)) { uniqueFeatureIds.push(28); // My Appraisals } } // Map feature IDs to nav items const userNavItems = uniqueFeatureIds.map(featureId => ({ title: featureTitleMap[featureId] || "Unknown", icon: featureIconMap[featureId] || , path: featurePathMap[featureId] || "/", featureId: featureId })); // Sort navigation items by featureId userNavItems.sort((a, b) => a.featureId - b.featureId); setNavItems(userNavItems); // Group the nav items by category const grouped = groupNavItems(userNavItems); setGroupedNavItems(grouped); } } catch (error) { console.error("Error fetching role features:", error); // If there's an error in fetching role features, add these essential items const fallbackItems = [ { title: "Dashboard", icon: , path: "/dashboard", featureId: 1 }, { title: "Features", icon: , path: "/issues", featureId: 10 }, { title: "Meeting Rooms", icon: , path: "/meeting-room/rooms", featureId: 33 }, { title: "Room Bookings", icon: , path: "/meeting-room/bookings", featureId: 34 }, { title: "Communications", icon: , path: "/communications", featureId: 35 }, { title: "Bugs", icon: , path: "/bugs", featureId: 27 }, { title: "Production Bugs", icon: , path: "/production-bugs", featureId: 39 }, { title: "Test Cases", icon: , path: "/test-cases", featureId: 40 }, { title: "My Appraisals", icon: , path: "/appraisals/my-appraisals", featureId: 28 }, { title: "My Leaves", icon: , path: "/leaves", featureId: 22 }, { title: "Leave Balances", icon: , path: "/leave-balances", featureId: 42 }, { title: "Assets", icon: , path: "/assets", featureId: 45 }, { title: "System Details", icon: , path: "/system-details", featureId: 46 }, { title: "Asset Assignments", icon: , path: "/asset-assignments", featureId: 47 }, { title: "My Asset Requests", icon: , path: "/asset-requests", featureId: 48 }, { title: "Gantt Chart", icon: , path: "/gantt-chart", featureId: 50 }, { title: "Resource Allocation", icon: , path: "/resource-allocation", featureId: 51 }, { title: "Employee Projects", icon: , path: "/employee-projects", featureId: 43 }, ]; // Add Asset Request Management for admin users if (userData?.roleId === 1 || userData?.roleId === 21 || userData?.roleName?.toLowerCase() === 'admin') { fallbackItems.push({ title: "Asset Request Management", icon: , path: "/admin/asset-requests", featureId: 49 }); } // Add HR-specific appraisal features for HR roles if (userData?.roleId === 10 || userData?.roleId === 11) { // Assuming roleId 10 and 11 for HR and HR Manager // Add essential appraisal management features for HR const hrAppraisalFeatures = [ { title: "Appraisal Cycles", icon: , path: "/appraisals/cycles", featureId: 29 }, { title: "Assessment Areas", icon: , path: "/appraisals/assessment-areas", featureId: 30 }, { title: "Competencies", icon: , path: "/appraisals/competencies", featureId: 31 }, { title: "Role Questions", icon: , path: "/appraisals/role-questions", featureId: 36 }, { title: "HR Remarks", icon: , path: "/appraisals/hr-remarks", featureId: 37 }, { title: "Management Remarks", icon: , path: "/appraisals/management-remarks", featureId: 38 }, { title: "Manager Appraisals", icon: , path: "/appraisals/manager-dashboard", featureId: 32 } ]; fallbackItems.push(...hrAppraisalFeatures); } setNavItems(fallbackItems); // Group the fallback items by category const grouped = groupNavItems(fallbackItems); setGroupedNavItems(grouped); } finally { setIsLoading(false); } }; // Function to group nav items by category const groupNavItems = (items: NavItem[]): Record => { const grouped: Record = {}; // Initialize groups from featureCategories Object.entries(featureCategories).forEach(([category, details]) => { grouped[category] = { title: details.title, icon: details.icon, items: [] }; }); // Add a generic "Other" category for any items that don't fit into predefined categories grouped["other"] = { title: "Other", icon: , items: [] }; // Categorize each nav item items.forEach(item => { const category = getFeatureCategory(item.featureId); if (grouped[category]) { grouped[category].items.push(item); } else { grouped["other"].items.push(item); } }); // Remove empty categories Object.keys(grouped).forEach(category => { if (grouped[category].items.length === 0) { delete grouped[category]; } }); return grouped; }; // Function to check if a nav item is active const isNavItemActive = (path: string): boolean => { const currentPath = window.location.pathname; // Check for special menu highlight override const activeMenu = document.body.getAttribute('data-active-menu'); if (activeMenu === 'production-bugs' && path === "/production-bugs") { return true; } // If we're on production-bugs page and have the dashboard flag set, // the dashboard should be active and production-bugs should not be if (currentPath.includes('production-bugs')) { if (document.body.getAttribute('data-active-tab') === 'dashboard') { // Only highlight Dashboard when the special attribute is set return path === "/"; } else { // Otherwise highlight Production Bugs return path === "/production-bugs"; } } // For dashboard path specifically - only active when actually on the dashboard if (path === "/" && currentPath === "/") { return true; } // Check for exact match if (path === currentPath) return true; // Special cases for various paths - only highlight if we're actually on these paths if (path === "/employees" && currentPath.includes("employee") && !currentPath.includes("production-bugs")) return true; if (path === "/deliverables" && currentPath.includes("deliverable") && !currentPath.includes("production-bugs")) return true; if (path === "/bugs" && currentPath.includes("bugs") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/my-appraisals" && (currentPath === "/appraisals/my-appraisals" || currentPath.includes("/appraisals/view")) && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/cycles" && currentPath.includes("/appraisals/cycles") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/assessment-areas" && currentPath.includes("/appraisals/assessment-areas") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/competencies" && currentPath.includes("/appraisals/competencies") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/manager-dashboard" && currentPath.includes("/appraisals/manager-dashboard") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/role-questions" && currentPath.includes("/appraisals/role-questions") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/hr-remarks" && currentPath.includes("/appraisals/hr-remarks") && !currentPath.includes("production-bugs")) return true; if (path === "/appraisals/management-remarks" && currentPath.includes("/appraisals/management-remarks") && !currentPath.includes("production-bugs")) return true; if (path === "/meeting-room/rooms" && currentPath.includes("/meeting-room/rooms") && !currentPath.includes("production-bugs")) return true; if (path === "/meeting-room/bookings" && currentPath.includes("/meeting-room/bookings") && !currentPath.includes("production-bugs")) return true; if (path === "/communications" && currentPath.includes("/communications") && !currentPath.includes("production-bugs")) return true; return false; }; return ( ); }; export default Sidebar;