import React, { useState, useEffect, useMemo } from 'react'; import { Users, Briefcase, Clock, Building2, Search, Calendar, ChevronDown, ChevronRight, ExternalLink } from 'lucide-react'; import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip } from 'recharts'; import { Card, CardHeader, CardContent } from '../components/ui/Card'; import { useEmployees } from '../hooks/useEmployees'; import { useDepartments } from '../hooks/useDepartments'; import { useWorkAllocations } from '../hooks/useWorkAllocations'; import { useAuth } from '../contexts/AuthContext'; import { api } from '../services/api'; // Types for attendance hierarchy interface AttendanceRecord { id: number; employee_id: number; employee_name: string; work_date: string; check_in_time: string | null; check_out_time: string | null; status: string; department_id: number; department_name: string; sub_department_id?: number; sub_department_name?: string; role: string; contractor_id?: number; contractor_name?: string; remark?: string; } interface HierarchyNode { id: number; name: string; role: string; department: string; subDepartment?: string; activity?: string; status?: string; inTime?: string; outTime?: string; remark?: string; children: HierarchyNode[]; isExpanded?: boolean; } export const DashboardPage: React.FC = () => { const { employees, loading: employeesLoading } = useEmployees(); const { departments, loading: deptLoading } = useDepartments(); const { allocations, loading: allocLoading } = useWorkAllocations(); const { user } = useAuth(); const [attendance, setAttendance] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [expandedNodes, setExpandedNodes] = useState>(new Set()); const [contractorRates, setContractorRates] = useState>({}); const isSuperAdmin = user?.role === 'SuperAdmin'; const isSupervisor = user?.role === 'Supervisor'; const isContractor = user?.role === 'Contractor'; const isEmployee = user?.role === 'Employee'; const filteredDepartments = isSupervisor ? departments.filter(d => d.id === user?.department_id) : departments; // Fetch today's attendance useEffect(() => { const today = new Date().toISOString().split('T')[0]; api.getAttendance({ startDate: today, endDate: today }) .then(setAttendance) .catch(console.error); }, []); // Fetch contractor rates for supervisor view useEffect(() => { if (isSupervisor) { api.getContractorRates() .then((rates: { contractor_id: number; rate: number }[]) => { const rateMap: Record = {}; rates.forEach(r => { // Keep the latest rate for each contractor if (!rateMap[r.contractor_id] || r.rate > rateMap[r.contractor_id]) { rateMap[r.contractor_id] = r.rate; } }); setContractorRates(rateMap); }) .catch(console.error); } }, [isSupervisor]); // Calculate role distribution using useMemo instead of useEffect const roleData = useMemo(() => { if (employees.length === 0) return []; const roleCounts: Record = {}; employees.forEach(e => { roleCounts[e.role] = (roleCounts[e.role] || 0) + 1; }); const colors: Record = { 'SuperAdmin': '#8b5cf6', 'Supervisor': '#3b82f6', 'Contractor': '#f59e0b', 'Employee': '#10b981' }; return Object.entries(roleCounts).map(([role, count]) => ({ name: role, value: count, fill: colors[role] || '#6b7280' })); }, [employees]); const loading = employeesLoading || deptLoading || allocLoading; // Stats calculations (filtered for supervisor's department if applicable) const stats = useMemo(() => { // Filter employees based on role const relevantEmployees = isSupervisor && user?.department_id ? employees.filter(e => e.department_id === user.department_id) : employees; const contractorCount = relevantEmployees.filter(e => e.role === 'Contractor').length; const employeeCount = relevantEmployees.filter(e => e.role === 'Employee').length; // Filter attendance for relevant employees const relevantAttendance = isSupervisor && user?.department_id ? attendance.filter(a => relevantEmployees.some(e => e.id === a.employee_id)) : attendance; const presentCount = relevantAttendance.filter(a => a.status === 'CheckedIn' || a.status === 'CheckedOut').length; const absentCount = employeeCount - presentCount; // Filter allocations for relevant employees const relevantAllocations = isSupervisor && user?.department_id ? allocations.filter(a => relevantEmployees.some(e => e.id === a.employee_id)) : allocations; return { totalUsers: relevantEmployees.length, totalDepartments: filteredDepartments.length, totalAllocations: relevantAllocations.length, pendingAllocations: relevantAllocations.filter(a => a.status === 'Pending').length, completedAllocations: relevantAllocations.filter(a => a.status === 'Completed').length, todayAttendance: relevantAttendance.length, checkedIn: relevantAttendance.filter(a => a.status === 'CheckedIn').length, checkedOut: relevantAttendance.filter(a => a.status === 'CheckedOut').length, contractorCount, employeeCount, presentCount, absentCount: Math.max(0, absentCount), }; }, [employees, attendance, allocations, filteredDepartments, isSupervisor, user]); // Build hierarchy data for SuperAdmin view const hierarchyData = useMemo(() => { if (!isSuperAdmin) return []; const supervisors = employees.filter(e => e.role === 'Supervisor'); return supervisors.map(supervisor => { const deptContractors = employees.filter( e => e.role === 'Contractor' && e.department_id === supervisor.department_id ); const supervisorNode: HierarchyNode = { id: supervisor.id, name: supervisor.name, role: 'Supervisor', department: supervisor.department_name || '', children: deptContractors.map(contractor => { const contractorEmployees = employees.filter( e => e.role === 'Employee' && e.contractor_id === contractor.id ); return { id: contractor.id, name: contractor.name, role: 'Contractor', department: contractor.department_name || '', children: contractorEmployees.map(emp => { const empAttendance = attendance.find(a => a.employee_id === emp.id); const empAllocation = allocations.find(a => a.employee_id === emp.id); return { id: emp.id, name: emp.name, role: 'Employee', department: emp.department_name || '', subDepartment: emp.sub_department_name, activity: empAllocation?.description || 'Loading', status: empAttendance ? (empAttendance.status === 'CheckedIn' || empAttendance.status === 'CheckedOut' ? 'Present' : 'Absent') : undefined, inTime: empAttendance?.check_in_time?.substring(0, 5), outTime: empAttendance?.check_out_time?.substring(0, 5), remark: empAttendance?.remark, children: [], }; }), }; }), }; return supervisorNode; }); }, [isSuperAdmin, employees, attendance, allocations]); // Build hierarchy data for Supervisor view (department-specific) const supervisorHierarchyData = useMemo(() => { if (!isSupervisor || !user?.department_id) return []; // Get contractors in supervisor's department const deptContractors = employees.filter( e => e.role === 'Contractor' && e.department_id === user.department_id ); return deptContractors.map(contractor => { const contractorEmployees = employees.filter( e => e.role === 'Employee' && e.contractor_id === contractor.id ); const contractorNode: HierarchyNode = { id: contractor.id, name: contractor.name, role: 'Contractor', department: contractor.department_name || '', children: contractorEmployees.map(emp => { const empAttendance = attendance.find(a => a.employee_id === emp.id); const empAllocation = allocations.find(a => a.employee_id === emp.id); return { id: emp.id, name: emp.name, role: 'Employee', department: emp.department_name || '', subDepartment: emp.sub_department_name, activity: empAllocation?.description || '-', status: empAttendance ? (empAttendance.status === 'CheckedIn' || empAttendance.status === 'CheckedOut' ? 'Present' : 'Absent') : undefined, inTime: empAttendance?.check_in_time?.substring(0, 5), outTime: empAttendance?.check_out_time?.substring(0, 5), remark: empAttendance?.remark, children: [], }; }), }; return contractorNode; }); }, [isSupervisor, user, employees, attendance, allocations]); // Department presence data for bar chart const departmentPresenceData = useMemo(() => { return filteredDepartments.map(dept => { const deptEmployees = employees.filter(e => e.department_id === dept.id && e.role === 'Employee'); const presentInDept = attendance.filter(a => deptEmployees.some(e => e.id === a.employee_id) && (a.status === 'CheckedIn' || a.status === 'CheckedOut') ).length; return { name: dept.name.substring(0, 10), fullName: dept.name, present: presentInDept, total: deptEmployees.length, }; }); }, [filteredDepartments, employees, attendance]); const toggleNode = (nodeKey: string) => { setExpandedNodes(prev => { const next = new Set(prev); if (next.has(nodeKey)) { next.delete(nodeKey); } else { next.add(nodeKey); } return next; }); }; // Filter hierarchy based on search (for SuperAdmin) const filteredHierarchy = useMemo(() => { if (!searchQuery) return hierarchyData; const query = searchQuery.toLowerCase(); const filterNode = (node: HierarchyNode): HierarchyNode | null => { const matchesSearch = node.name.toLowerCase().includes(query) || node.department.toLowerCase().includes(query) || node.subDepartment?.toLowerCase().includes(query); const filteredChildren = node.children .map(child => filterNode(child)) .filter((child): child is HierarchyNode => child !== null); if (matchesSearch || filteredChildren.length > 0) { return { ...node, children: filteredChildren }; } return null; }; return hierarchyData .map(node => filterNode(node)) .filter((node): node is HierarchyNode => node !== null); }, [hierarchyData, searchQuery]); // Filter hierarchy based on search (for Supervisor) const filteredSupervisorHierarchy = useMemo(() => { if (!searchQuery) return supervisorHierarchyData; const query = searchQuery.toLowerCase(); const filterNode = (node: HierarchyNode): HierarchyNode | null => { const matchesSearch = node.name.toLowerCase().includes(query) || node.department.toLowerCase().includes(query) || node.subDepartment?.toLowerCase().includes(query); const filteredChildren = node.children .map(child => filterNode(child)) .filter((child): child is HierarchyNode => child !== null); if (matchesSearch || filteredChildren.length > 0) { return { ...node, children: filteredChildren }; } return null; }; return supervisorHierarchyData .map(node => filterNode(node)) .filter((node): node is HierarchyNode => node !== null); }, [supervisorHierarchyData, searchQuery]); // Render hierarchy row const renderHierarchyRow = (node: HierarchyNode, level: number = 0, parentKey: string = '') => { const nodeKey = `${parentKey}-${node.id}`; const isExpanded = expandedNodes.has(nodeKey); const hasChildren = node.children.length > 0; const indent = level * 24; const roleColors: Record = { 'Supervisor': { bg: 'bg-blue-500', text: 'text-blue-700', badge: 'bg-blue-100 text-blue-700' }, 'Contractor': { bg: 'bg-orange-500', text: 'text-orange-700', badge: 'bg-orange-100 text-orange-700' }, 'Employee': { bg: 'bg-gray-400', text: 'text-gray-600', badge: 'bg-gray-100 text-gray-600' }, }; const colors = roleColors[node.role] || roleColors['Employee']; return (
{hasChildren ? ( ) : ( )}
{node.name} {node.role}
{node.department} {node.subDepartment || '-'} {node.activity || '-'} {node.status && ( {node.status} )} {node.inTime || '-'} {node.outTime || '-'} {node.remark || '-'} {isExpanded && node.children.map(child => renderHierarchyRow(child, level + 1, nodeKey))} ); }; // SuperAdmin Dashboard View if (isSuperAdmin) { return (
{loading && (
Loading...
)} {/* Daily Attendance Report Header */}

Daily Attendance Report

{/* Search Bar */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent shadow-sm" />
{/* Hierarchical Attendance Table */}
{filteredHierarchy.length > 0 ? ( filteredHierarchy.map(node => renderHierarchyRow(node, 0, 'root')) ) : ( )}
Level / Name Dept Sub-Dept Activity Status In Time Out Time Remark
{searchQuery ? 'No matching records found' : 'No attendance data available'}
{/* Comparative Analysis Section */}

Comparative Analysis

{/* Total Workforce Card */}

Total Workforce

{stats.contractorCount + stats.employeeCount} Total Personnel
Contractors {stats.contractorCount}
Employees {stats.employeeCount}
{/* Attendance Ratio Donut Chart */}

Attendance Ratio

Absent
Present
{/* Department Presence Bar Chart */}

Department Presence

[`${value} / ${props.payload.total}`, 'Present']} labelFormatter={(label) => departmentPresenceData.find(d => d.name === label)?.fullName || label} /> {departmentPresenceData.map((entry, index) => { const colors = ['#10b981', '#f59e0b', '#3b82f6', '#8b5cf6', '#ef4444']; return ; })}
{/* User Distribution & Recent Allocations Row */}
{/* User Distribution */} {roleData.length > 0 ? (
{roleData.map((entry, index) => ( ))}
{roleData.map((item) => (
{item.name}
{item.value}
))}
) : (
No user data
)}
{/* Recent Work Allocations */} {allocations.length > 0 ? (
{allocations.slice(0, 5).map((alloc) => (
{alloc.employee_name || 'Unknown Employee'}
{alloc.description || 'No description'} • {new Date(alloc.assigned_date).toLocaleDateString()}
{alloc.status}
))}
) : (
No recent allocations
)}
); } // Supervisor Dashboard View if (isSupervisor) { const departmentName = filteredDepartments[0]?.name || 'My Department'; return (
{loading && (
Loading...
)} {/* Department Header */}

{departmentName} Dashboard

Daily Attendance & Work Overview

{/* Search Bar */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent shadow-sm" />
{/* Hierarchical Attendance Table */}
{filteredSupervisorHierarchy.length > 0 ? ( filteredSupervisorHierarchy.map(node => { const rate = contractorRates[node.id]; return ( {/* Contractor Row */} {/* Employee Rows */} {expandedNodes.has(`supervisor-${node.id}`) && node.children.map(emp => ( ))} ); }) ) : ( )}
Contractor / Employee Dept Sub-Dept Activity Rate (₹) Status In Time Out Time Remark
{node.children.length > 0 && ( )} {node.name} Contractor
{node.department} - - {rate ? ( ₹{Number(rate).toFixed(2)} ) : ( Not set )} - - - -
{emp.name} Employee
{emp.department} {emp.subDepartment || '-'} {emp.activity || '-'} - {emp.status && ( {emp.status} )} {emp.inTime || '-'} {emp.outTime || '-'} {emp.remark || '-'}
{searchQuery ? 'No matching records found' : 'No contractors or employees in your department'}
{/* Comparative Analysis Section */}

Department Overview

{/* Total Workforce Card */}

Department Workforce

{stats.contractorCount + stats.employeeCount} Total Personnel
Contractors {stats.contractorCount}
Employees {stats.employeeCount}
{/* Attendance Ratio Donut Chart */}

Attendance Ratio

Present ({stats.presentCount})
Absent ({stats.absentCount})
{/* Work Allocation Status */}

Work Allocations

{stats.totalAllocations} Total
Pending
{stats.pendingAllocations}
Completed
{stats.completedAllocations}
In Progress
{stats.totalAllocations - stats.pendingAllocations - stats.completedAllocations}
{/* Recent Work Allocations */} {allocations.length > 0 ? (
{allocations.slice(0, 5).map((alloc) => (
{alloc.employee_name || 'Unknown Employee'}
{alloc.description || 'No description'} • {new Date(alloc.assigned_date).toLocaleDateString()}
{alloc.status}
))}
) : (
No recent allocations
)}
); } // Employee Dashboard View if (isEmployee) { // Get employee's own data const myAttendance = attendance.find(a => a.employee_id === user?.id); const myAllocations = allocations.filter(a => a.employee_id === user?.id); const myContractor = employees.find(e => e.id === user?.contractor_id); const myDepartment = departments.find(d => d.id === user?.department_id); return (
{loading && (
Loading...
)} {/* Welcome Header */}

Welcome, {user?.name || 'Employee'}

Your personal dashboard

{/* Employee Info Cards */}
{/* Department Info */}
Department
{myDepartment?.name || 'Not Assigned'}
{/* Contractor Info */}
Assigned To
{myContractor?.name || 'Not Assigned'}
{/* Today's Status */}
Today's Status
{myAttendance?.status === 'CheckedIn' ? 'Checked In' : myAttendance?.status === 'CheckedOut' ? 'Checked Out' : myAttendance?.status || 'Not Checked In'}
{myAttendance && (
{myAttendance.check_in_time && (
In: {new Date(myAttendance.check_in_time).toLocaleTimeString()}
)} {myAttendance.check_out_time && (
Out: {new Date(myAttendance.check_out_time).toLocaleTimeString()}
)}
)}
{/* My Work Allocations */} {myAllocations.length > 0 ? (
{myAllocations.map((alloc) => (
{alloc.description || 'Work Assignment'}
{alloc.status}
Assigned: {new Date(alloc.assigned_date).toLocaleDateString()}
{alloc.sub_department_name &&
Sub-Dept: {alloc.sub_department_name}
} {alloc.supervisor_name &&
Supervisor: {alloc.supervisor_name}
}
))}
) : (

No work allocations assigned yet

)}
); } // Contractor Dashboard View if (isContractor) { // Get contractor's employees const myEmployees = employees.filter(e => e.contractor_id === user?.id); const myDepartment = departments.find(d => d.id === user?.department_id); const myEmployeeIds = myEmployees.map(e => e.id); const myEmployeesAttendance = attendance.filter(a => myEmployeeIds.includes(a.employee_id)); const myEmployeesAllocations = allocations.filter(a => myEmployeeIds.includes(a.employee_id)); const presentCount = myEmployeesAttendance.filter( a => a.status === 'CheckedIn' || a.status === 'CheckedOut' ).length; return (
{loading && (
Loading...
)} {/* Welcome Header */}

Welcome, {user?.name || 'Contractor'}

Manage your assigned employees

{/* Stats Cards */}

MY EMPLOYEES

{myEmployees.length}
Assigned to you

DEPARTMENT

{myDepartment?.name || 'N/A'}
Your department

PRESENT TODAY

{presentCount}
of {myEmployees.length} employees

WORK TASKS

{myEmployeesAllocations.length}
Active allocations
{/* My Employees Table */} {myEmployees.length > 0 ? ( {myEmployees.map(emp => { const empAttendance = myEmployeesAttendance.find(a => a.employee_id === emp.id); const empAllocation = myEmployeesAllocations.find(a => a.employee_id === emp.id); const isPresent = empAttendance?.status === 'CheckedIn' || empAttendance?.status === 'CheckedOut'; return ( ); })}
Employee Today's Status Check In Check Out Current Work
{emp.name.charAt(0).toUpperCase()}
{emp.name}
{emp.username}
{empAttendance?.status === 'CheckedIn' ? 'Checked In' : empAttendance?.status === 'CheckedOut' ? 'Checked Out' : empAttendance?.status || 'Absent'} {empAttendance?.check_in_time ? new Date(empAttendance.check_in_time).toLocaleTimeString() : '-'} {empAttendance?.check_out_time ? new Date(empAttendance.check_out_time).toLocaleTimeString() : '-'} {empAllocation?.description || 'No work assigned'}
) : (

No employees assigned to you yet

)}
); } // Fallback - should not reach here return (

Dashboard not available for your role

); };