import React, { useState, useMemo, useEffect, useCallback, useRef } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { UserPlus, Search, Loader2, Eye, Edit, Trash2, Filter, Download, MoreHorizontal, Upload, FileText, Check, X, ChevronDown, Phone, Mail, User, Briefcase } from "lucide-react"; import { toast } from "@/lib/custom-toast"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { jsPDF } from 'jspdf'; import autoTable from 'jspdf-autotable'; import { roleService } from "@/lib/api"; import { Employee as EmployeeType, Role } from "@/types"; import { Employee, employeeApi } from "@/services/employeeApi"; import Header from "@/components/layout/header"; import Sidebar from "@/components/layout/sidebar"; import { useIsMobile } from "@/hooks/use-mobile"; import ImportEmployeesDialog from "@/components/employee/import-employees-dialog"; import DownloadTemplateButton from "@/components/employee/download-template-button"; // Update the type declaration declare module 'jspdf' { interface jsPDF { autoTable: (options: any) => jsPDF; } } // Mobile Employee Card Component interface MobileEmployeeCardProps { employee: Employee; getRoleName: (roleId: number) => string; getRoleBadgeVariant: (roleId: number) => "default" | "secondary" | "outline" | "destructive"; onView: (id: number) => void; onEdit: (id: number) => void; onDelete: (id: number) => void; onStatusChange: (employee: Employee, status: string) => void; onReportingChange: (employeeId: number, reportingToId: number | null) => void; isUpdating: boolean; getInitials: (firstName: string, lastName: string) => string; employees: Employee[]; } const MobileEmployeeCard = ({ employee, getRoleName, getRoleBadgeVariant, onView, onEdit, onDelete, onStatusChange, onReportingChange, isUpdating, getInitials, employees }: MobileEmployeeCardProps) => { const [isActionsOpen, setIsActionsOpen] = useState(false); const actionsRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (actionsRef.current && !actionsRef.current.contains(event.target as Node)) { setIsActionsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const getStatusBadgeVariant = (status: string) => { switch (status) { case 'Active': return 'default'; case 'Inactive': return 'outline'; case 'On Leave': return 'secondary'; case 'Terminated': return 'destructive'; default: return 'outline'; } }; return (
{getInitials(employee.firstName, employee.lastName)}

{employee.firstName} {employee.lastName}

{employee.empCode}

{/* Actions Dropdown */}
{isActionsOpen && (
)}
{/* Contact Information */}
{employee.email}
{employee.mobile}
{/* Job Information */}
{employee.type}
{getRoleName(employee.roleId)}
{/* Status */}
Status:
{/* Reporting */}
Reporting To:
); }; // Custom status dropdown component interface StatusDropdownProps { employee: Employee; onStatusChange: (employee: Employee, status: string) => void; isUpdating: boolean; } const StatusDropdown = ({ employee, onStatusChange, isUpdating }: StatusDropdownProps) => { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); const statusOptions = ['Active', 'Inactive', 'On Leave', 'Terminated', 'Unknown']; // Close the dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const getStatusBadgeVariant = (status: string) => { switch (status) { case 'Active': return 'default'; case 'Inactive': return 'outline'; case 'On Leave': return 'secondary'; case 'Terminated': return 'destructive'; default: return 'outline'; } }; return (
{/* Status Badge with custom dropdown */}
!isUpdating && setIsOpen(!isOpen)} > {employee.status || 'Unknown'}
{/* Custom dropdown content */} {isOpen && (
Update Status
{statusOptions.map(status => ( ))}
)} {/* Loading indicator during update */} {isUpdating && (
)}
); }; // Custom reporting dropdown component interface ReportingDropdownProps { employee: Employee; employees: Employee[]; onReportingChange: (employeeId: number, reportingToId: number | null) => void; isUpdating: boolean; } const ReportingDropdown = ({ employee, employees, onReportingChange, isUpdating }: ReportingDropdownProps) => { const [isOpen, setIsOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 }); const dropdownRef = useRef(null); const triggerRef = useRef(null); const searchInputRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsOpen(false); setSearchQuery(''); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); useEffect(() => { if (isOpen && searchInputRef.current) { searchInputRef.current.focus(); } if (isOpen && triggerRef.current) { const rect = triggerRef.current.getBoundingClientRect(); setDropdownPosition({ top: rect.bottom + 4, left: rect.right - 250 // 250px is the dropdown width }); } }, [isOpen]); const filteredEmployees = employees .filter(emp => emp.id !== employee.id) .filter(emp => { if (!searchQuery) return true; const fullName = `${emp.firstName} ${emp.lastName}`.toLowerCase(); return fullName.includes(searchQuery.toLowerCase()); }); const getReportingName = (id: number | null | undefined) => { if (!id) return 'None'; const reportingTo = employees.find(emp => emp.id === id); return reportingTo ? `${reportingTo.firstName} ${reportingTo.lastName}` : 'Unknown'; }; return (
!isUpdating && setIsOpen(!isOpen)} > {getReportingName(employee.reportingToId)}
{isOpen && (
Select Reporting To
setSearchQuery(e.target.value)} className="w-full pl-8 pr-3 py-1.5 text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-primary/20" onClick={(e) => e.stopPropagation()} />
{filteredEmployees.length > 0 ? ( filteredEmployees.map(reporting => ( )) ) : (
No employees found
)}
)} {isUpdating && (
)}
); }; // Custom export modal component interface CustomExportModalProps { isOpen: boolean; onClose: () => void; employees: Employee[]; getRoleName: (roleId: number) => string; } const CustomExportModal = ({ isOpen, onClose, employees, getRoleName }: CustomExportModalProps) => { const [exportFormat, setExportFormat] = useState<'excel' | 'pdf'>('excel'); const [selectedColumns, setSelectedColumns] = useState({ empCode: true, name: true, email: true, mobile: true, type: true, role: true, status: true }); // Export to Excel const exportToExcel = () => { if (!employees || employees.length === 0) { toast.error("No data to export"); return; } try { // Build headers based on selected columns const headers = []; if (selectedColumns.empCode) headers.push("Employee Code"); if (selectedColumns.name) headers.push("Name"); if (selectedColumns.email) headers.push("Email"); if (selectedColumns.mobile) headers.push("Mobile"); if (selectedColumns.type) headers.push("Type"); if (selectedColumns.role) headers.push("Role"); if (selectedColumns.status) headers.push("Status"); // Check if any columns are selected if (headers.length === 0) { toast.error("Please select at least one column to export"); return; } // Create HTML table content for proper Excel formatting let htmlTable = ''; // Headers row htmlTable += ''; headers.forEach(header => { htmlTable += ``; }); htmlTable += ''; // Data rows employees.forEach(employee => { htmlTable += ''; if (selectedColumns.empCode) htmlTable += ``; if (selectedColumns.name) htmlTable += ``; if (selectedColumns.email) htmlTable += ``; if (selectedColumns.mobile) htmlTable += ``; if (selectedColumns.type) htmlTable += ``; if (selectedColumns.role) htmlTable += ``; if (selectedColumns.status) htmlTable += ``; htmlTable += ''; }); htmlTable += '
${header}
${employee.empCode}${employee.firstName} ${employee.middleName || ''} ${employee.lastName}${employee.email}${employee.mobile}${employee.type}${getRoleName(employee.roleId)}${employee.status || 'Unknown'}
'; // Prepare the file const excelTemplate = ` ${htmlTable} `; // Create download const blob = new Blob([excelTemplate], { type: 'application/vnd.ms-excel' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', 'employees.xls'); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); toast.success("Employee data exported to Excel successfully"); onClose(); } catch (error) { console.error("Excel export error:", error); toast.error("Failed to export data"); } }; // Export to PDF const exportToPDF = () => { if (!employees || employees.length === 0) { toast.error("No data to export"); return; } try { const doc = new jsPDF(); // Add title doc.setFontSize(16); doc.text('Employee Directory', 14, 15); doc.setFontSize(10); doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 14, 22); // Prepare columns based on selection const columns = []; if (selectedColumns.empCode) columns.push('Employee Code'); if (selectedColumns.name) columns.push('Name'); if (selectedColumns.email) columns.push('Email'); if (selectedColumns.mobile) columns.push('Mobile'); if (selectedColumns.type) columns.push('Type'); if (selectedColumns.role) columns.push('Role'); if (selectedColumns.status) columns.push('Status'); // Check if any columns are selected if (columns.length === 0) { toast.error("Please select at least one column to export"); return; } // Prepare data rows const tableData = employees.map(employee => { const row = []; if (selectedColumns.empCode) row.push(employee.empCode); if (selectedColumns.name) row.push(`${employee.firstName} ${employee.middleName || ''} ${employee.lastName}`); if (selectedColumns.email) row.push(employee.email); if (selectedColumns.mobile) row.push(employee.mobile); if (selectedColumns.type) row.push(employee.type); if (selectedColumns.role) row.push(getRoleName(employee.roleId)); if (selectedColumns.status) row.push(employee.status || 'Unknown'); return row; }); // Generate table autoTable(doc, { head: [columns], body: tableData, startY: 30, theme: 'grid', styles: { fontSize: 8, cellPadding: 2, overflow: 'linebreak' }, headStyles: { fillColor: [41, 128, 185], textColor: 255, fontSize: 9, fontStyle: 'bold' }, alternateRowStyles: { fillColor: [245, 245, 245] } }); // Save the PDF doc.save('employees.pdf'); toast.success("Employee data exported to PDF successfully"); onClose(); } catch (error) { console.error('PDF export error:', error); toast.error("Failed to export data to PDF"); } }; // Toggle all columns const toggleAllColumns = (checked: boolean) => { setSelectedColumns({ empCode: checked, name: checked, email: checked, mobile: checked, type: checked, role: checked, status: checked }); }; // Check if any columns are selected const hasSelectedColumns = Object.values(selectedColumns).some(v => v); // Prevent export if no columns are selected const handleExport = () => { if (!employees || employees.length === 0) { toast.error("No data to export"); return; } // Validate at least one column is selected if (!hasSelectedColumns) { toast.error("Please select at least one column to export"); return; } if (exportFormat === 'excel') { exportToExcel(); } else { exportToPDF(); } }; if (!isOpen) return null; return (

Export Employee Data

Export Format

Columns to Include

{/* Group 1: Identity */}
Identity Information
{/* Group 2: Contact */}
Contact Information
{/* Group 3: Job */}
Job Information
); }; // Custom mobile actions dropdown component interface MobileActionsDropdownProps { employee: Employee; onView: (id: number) => void; onEdit: (id: number) => void; onDelete: (id: number) => void; } const MobileActionsDropdown = ({ employee, onView, onEdit, onDelete }: MobileActionsDropdownProps) => { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); // Close the dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); return (
{isOpen && (
)}
); }; // Custom filter dropdown component - reusable base interface FilterDropdownProps { isOpen: boolean; title: string; children: React.ReactNode; dropdownRef?: React.RefObject; } const FilterDropdown = ({ isOpen, title, children, dropdownRef }: FilterDropdownProps) => { if (!isOpen) return null; return (
{title}
{children}
); }; const Employees = () => { const navigate = useNavigate(); const location = useLocation(); const queryClient = useQueryClient(); const isMobile = useIsMobile(); const [isSidebarOpen, setIsSidebarOpen] = useState(!isMobile); const [searchQuery, setSearchQuery] = useState(""); const [roleFilter, setRoleFilter] = useState(null); const [statusFilter, setStatusFilter] = useState(null); const [isExportModalOpen, setIsExportModalOpen] = useState(false); const [isRoleFilterOpen, setIsRoleFilterOpen] = useState(false); const [isStatusFilterOpen, setIsStatusFilterOpen] = useState(false); const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); // Status update popup state const [statusPopupOpen, setStatusPopupOpen] = useState(null); const statusBadgeRefs = useRef<{[key: number]: HTMLDivElement | null}>({}); // References for filter buttons and popups const roleFilterButtonRef = useRef(null); const statusFilterButtonRef = useRef(null); const roleFilterDropdownRef = useRef(null); const statusFilterDropdownRef = useRef(null); // Status options for filter const statusOptions = ['Active', 'Inactive', 'On Leave', 'Terminated', 'Unknown']; // Auto-switch to cards view on mobile useEffect(() => { if (isMobile && viewMode === 'table') { setViewMode('cards'); } else if (!isMobile && viewMode === 'cards') { setViewMode('table'); } }, [isMobile, viewMode]); // Setup status update mutation const statusUpdateMutation = useMutation({ mutationFn: ({ id, status }: { id: number, status: string }) => { return employeeApi.updateStatus(id, status); }, onSuccess: (data) => { if (data.success) { toast.success(data.message || "Status updated successfully"); // Refetch employee data to reflect the changes refetch(); } else { toast.error(data.message || "Failed to update status"); } }, onError: (error) => { console.error("Status update error:", error); toast.error("An error occurred while updating the status"); } }); // Handle clicks outside the filter popups useEffect(() => { const handleClickOutside = (event: MouseEvent) => { // Close role filter if clicked outside both button and dropdown if ( isRoleFilterOpen && roleFilterButtonRef.current && !roleFilterButtonRef.current.contains(event.target as Node) && roleFilterDropdownRef.current && !roleFilterDropdownRef.current.contains(event.target as Node) ) { setIsRoleFilterOpen(false); } // Close status filter if clicked outside both button and dropdown if ( isStatusFilterOpen && statusFilterButtonRef.current && !statusFilterButtonRef.current.contains(event.target as Node) && statusFilterDropdownRef.current && !statusFilterDropdownRef.current.contains(event.target as Node) ) { setIsStatusFilterOpen(false); } }; // Add event listener document.addEventListener('mousedown', handleClickOutside); // Clean up return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isRoleFilterOpen, isStatusFilterOpen]); // Update sidebar state when screen size changes useEffect(() => { setIsSidebarOpen(!isMobile); }, [isMobile]); // Close sidebar on mobile when route changes useEffect(() => { if (isMobile) { setIsSidebarOpen(false); } }, [location, isMobile]); // Fetch employees const { data: employees, isLoading, error, refetch } = useQuery({ queryKey: ["employees"], queryFn: () => employeeApi.getAllForAdmin(), }); // Debug logging to see employee data structure useEffect(() => { if (employees && employees.length > 0) { console.log("Employee data structure:", employees[0]); console.log("All employees:", employees); // Ensure all employees have status values const hasStatusField = employees.some(emp => emp.status !== undefined); if (!hasStatusField) { console.log("Employees missing status field, normalizing data..."); // Add status field to all employees if missing const normalizedEmployees = employees.map(emp => ({ ...emp, status: emp.status || 'Unknown' })); // Update query cache with normalized employees queryClient.setQueryData(['employees'], normalizedEmployees); } } }, [employees, queryClient]); // Fetch roles const { data: rolesData, isLoading: rolesLoading } = useQuery({ queryKey: ["roles"], queryFn: roleService.getAll, }); // Normalize roles data to ensure consistent structure const roles = useMemo(() => { if (!rolesData) return null; return rolesData.map((role: any) => { // Ensure both id and roleId are available if (role.id === undefined && role.roleId !== undefined) { return { ...role, id: role.roleId }; } if (role.roleId === undefined && role.id !== undefined) { return { ...role, roleId: role.id }; } return role; }); }, [rolesData]); // Toggle sidebar const toggleSidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; // Get role name from role ID using fetched roles const getRoleName = (roleId: number) => { if (!roles) return "Loading..."; // Find role by ID (should work with normalized data) const role = roles.find(r => r.roleId === roleId || r.id === roleId); if (role) { return role.roleName; } console.log(`Role with ID ${roleId} not found in`, roles); return `Role ${roleId}`; }; // Get role badge color based on role ID const getRoleBadgeVariant = (roleId: number) => { if (!roles) return "outline"; // Create a mapping of roleId to variants const roleVariants: Record = {}; // Assign variants based on role index roles.forEach((role: any, index: number) => { const variants: Array<"default" | "secondary" | "outline" | "destructive"> = ["default", "secondary", "outline"]; // Support both roleId and id property names const id = role.roleId !== undefined ? role.roleId : role.id; if (id !== undefined) { roleVariants[id] = variants[index % variants.length]; } }); return roleVariants[roleId] || "outline"; }; // Delete employee handler const handleDeleteEmployee = async (id: number) => { if (window.confirm("Are you sure you want to delete this employee?")) { try { // Actual API call would go here // await employeeService.delete(id); toast.success("Employee has been successfully deleted"); // Refresh the employee list refetch(); } catch (error) { toast.error("Failed to delete employee"); } } }; // Helper function to get role ID regardless of property name const getRoleId = (role: any): number => { return role.roleId !== undefined ? role.roleId : (role.id !== undefined ? role.id : 0); }; // Filter employees based on search query, role filter, and status filter const filteredEmployees = useMemo(() => { if (!employees) return []; console.log("Filtering employees: ", { total: employees.length, roleFilter, statusFilter, searchQuery }); return employees.filter((employee: Employee) => { // Apply role filter if (roleFilter !== null && employee.roleId !== roleFilter) { return false; } // Apply status filter with better handling of undefined values if (statusFilter !== null) { const employeeStatus = employee.status || 'Unknown'; console.log(`Employee ${employee.id} (${employee.firstName}): comparing status '${employeeStatus}' with filter '${statusFilter}'`); if (employeeStatus !== statusFilter) { return false; } } // Apply search filter if (!searchQuery) return true; const fullName = `${employee.firstName} ${employee.middleName || ''} ${employee.lastName}`.toLowerCase(); const query = searchQuery.toLowerCase(); return ( fullName.includes(query) || employee.email.toLowerCase().includes(query) || employee.empCode.toLowerCase().includes(query) || employee.mobile.includes(query) || employee.type.toLowerCase().includes(query) ); }); }, [employees, searchQuery, roleFilter, statusFilter]); // Get initials for avatar const getInitials = (firstName: string, lastName: string) => { return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase(); }; // Reset all filters const resetFilters = useCallback(() => { console.log("Resetting all filters"); setSearchQuery(""); setRoleFilter(null); setStatusFilter(null); setIsRoleFilterOpen(false); setIsStatusFilterOpen(false); }, []); // Add debug information for filtered results useEffect(() => { if (filteredEmployees && filteredEmployees.length > 0) { console.log(`Filter results: ${filteredEmployees.length} employees matched criteria`); console.log("First filtered employee:", filteredEmployees[0]); } else if (filteredEmployees && filteredEmployees.length === 0 && employees && employees.length > 0) { console.log("Filter returned zero results. Current filters:", { roleFilter, statusFilter, searchQuery }); } }, [filteredEmployees, employees, roleFilter, statusFilter, searchQuery]); // Set role filter with callback for reliability const updateRoleFilter = useCallback((roleId: number | null) => { console.log(`Setting role filter to: ${roleId === null ? 'null' : roleId}`); setRoleFilter(roleId); setIsRoleFilterOpen(false); }, []); // Set status filter with callback for reliability const updateStatusFilter = useCallback((status: string | null) => { console.log(`Setting status filter to: ${status === null ? 'null' : `"${status}"`}`); setStatusFilter(status); setIsStatusFilterOpen(false); }, []); // Handler for status update with confirmation const handleStatusUpdate = useCallback((employee: Employee, newStatus: string) => { // Skip if status is already the same if (employee.status === newStatus) return; // Ask for confirmation for Terminated status if (newStatus === 'Terminated') { if (window.confirm(`Are you sure you want to mark ${employee.firstName} ${employee.lastName} as Terminated? This action may affect their access to systems.`)) { statusUpdateMutation.mutate({ id: employee.id, status: newStatus }); } } else { // For other statuses, no confirmation needed statusUpdateMutation.mutate({ id: employee.id, status: newStatus }); } }, [statusUpdateMutation]); // Reporting update mutation const reportingUpdateMutation = useMutation({ mutationFn: ({ id, reportingToId }: { id: number, reportingToId: number | null }) => { return employeeApi.updateEmployeeReporting(id, reportingToId); }, onSuccess: (data) => { if (data.success) { toast.success(data.message || "Reporting updated successfully"); // Refetch employee data to reflect the changes refetch(); } else { toast.error(data.message || "Failed to update reporting"); } }, onError: (error) => { console.error("Reporting update error:", error); toast.error("An error occurred while updating the reporting"); } }); // Handler for reporting update const handleReportingUpdate = useCallback( (employeeId: number, reportingToId: number | null) => { reportingUpdateMutation.mutate({ id: employeeId, reportingToId }); }, [reportingUpdateMutation] ); return (

Employees

Manage your organization's employees

{/* Import/Export actions - Compact design */}
{filteredEmployees?.length || 0} employees
{ refetch(); toast.success("Employee list refreshed"); }} /> setIsExportModalOpen(false)} employees={filteredEmployees || []} getRoleName={getRoleName} />
{/* Single Row Header with Title, Search, and Filters */}
{/* Title Section */}
Employee Directory {filteredEmployees?.length || 0} of {employees?.length || 0}
{/* Search Bar */}
setSearchQuery(e.target.value)} />
{/* Filters and View Toggle */}
{/* Role Filter */}
{/* Custom Role Filter Popup */} {isRoleFilterOpen && ( {!rolesLoading && roles?.map((role: any) => ( ))} } /> )}
{/* Status Filter */}
{/* Custom Status Filter Popup */} {isStatusFilterOpen && ( {statusOptions.map((status) => ( ))} } /> )}
{/* Reset Button */} {(searchQuery || roleFilter !== null || statusFilter !== null) && ( )}
{isLoading || rolesLoading ? (
) : error ? (

Error loading employees. Please try again later.

) : filteredEmployees?.length === 0 ? (

No employees found matching your filters.

{(searchQuery || roleFilter !== null) && ( )}
) : ( <> {/* Mobile/Cards View */} {(isMobile || viewMode === 'cards') && (
{filteredEmployees?.map((employee: Employee) => ( navigate(`/employee/${id}`)} onEdit={(id) => navigate(`/employee-edit/${id}`)} onDelete={(id) => handleDeleteEmployee(id)} onStatusChange={handleStatusUpdate} onReportingChange={handleReportingUpdate} isUpdating={statusUpdateMutation.isPending && statusUpdateMutation.variables?.id === employee.id} getInitials={getInitials} employees={employees || []} /> ))}
)} {/* Desktop Table View */} {!isMobile && viewMode === 'table' && (
Employee Contact Role Status Reporting To Actions {filteredEmployees?.map((employee: Employee) => (
{getInitials(employee.firstName, employee.lastName)}
{employee.firstName} {employee.lastName}
{employee.empCode}
{employee.email}
{employee.mobile}
{getRoleName(employee.roleId)}
navigate(`/employee/${id}`)} onEdit={(id) => navigate(`/employee-edit/${id}`)} onDelete={(id) => handleDeleteEmployee(id)} />
))}
)} )}
{/* Mobile Add Employee FAB */}
{/* Overlay for mobile sidebar */} {isSidebarOpen && isMobile && (
setIsSidebarOpen(false)} /> )}
); }; export default Employees;