(Feat-Fix): New Reporting system, more seeded data, fixed subdepartments and activity inversion, login page changes, etc etc

This commit is contained in:
2025-12-18 08:15:31 +00:00
parent 916ee19677
commit ac29bb2882
24 changed files with 3306 additions and 207 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo } from 'react';
import { Users, Briefcase, Clock, Building2, Search, Calendar, ChevronDown, ChevronRight, ExternalLink } from 'lucide-react';
import { Users, Briefcase, Clock, Building2, Search, Calendar, ChevronDown, ChevronRight, ExternalLink, RefreshCw } 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';
@@ -43,15 +43,25 @@ interface HierarchyNode {
}
export const DashboardPage: React.FC = () => {
const { employees, loading: employeesLoading } = useEmployees();
const { employees, loading: employeesLoading, refresh: refreshEmployees } = useEmployees();
const { departments, loading: deptLoading } = useDepartments();
const { allocations, loading: allocLoading } = useWorkAllocations();
const { allocations, loading: allocLoading, refresh: refreshAllocations } = useWorkAllocations();
const { user } = useAuth();
const [attendance, setAttendance] = useState<AttendanceRecord[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
const [contractorRates, setContractorRates] = useState<Record<number, number>>({});
// Refresh all data function
const refreshAllData = () => {
refreshEmployees();
refreshAllocations();
const today = new Date().toISOString().split('T')[0];
api.getAttendance({ startDate: today, endDate: today })
.then(setAttendance)
.catch(console.error);
};
const isSuperAdmin = user?.role === 'SuperAdmin';
const isSupervisor = user?.role === 'Supervisor';
const isContractor = user?.role === 'Contractor';
@@ -161,41 +171,78 @@ export const DashboardPage: React.FC = () => {
e => e.role === 'Contractor' && e.department_id === supervisor.department_id
);
// Get employees without a contractor but in this department (e.g., swapped employees)
const unassignedEmployees = employees.filter(
e => e.role === 'Employee' &&
e.department_id === supervisor.department_id &&
!e.contractor_id
);
const contractorNodes = 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 && a.status !== 'Completed');
return {
id: emp.id,
name: emp.name,
role: 'Employee',
department: emp.department_name || '',
subDepartment: empAllocation?.sub_department_name || '-',
activity: empAllocation?.description || empAllocation?.activity || '-',
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: [],
};
}),
};
});
// Add unassigned employees node if there are any
if (unassignedEmployees.length > 0) {
contractorNodes.push({
id: -supervisor.department_id!, // Negative ID to avoid conflicts
name: 'Unassigned (Swapped)',
role: 'Contractor',
department: supervisor.department_name || '',
children: unassignedEmployees.map(emp => {
const empAttendance = attendance.find(a => a.employee_id === emp.id);
const empAllocation = allocations.find(a => a.employee_id === emp.id && a.status !== 'Completed');
return {
id: emp.id,
name: emp.name,
role: 'Employee',
department: emp.department_name || '',
subDepartment: empAllocation?.sub_department_name || '-',
activity: empAllocation?.description || empAllocation?.activity || 'Swapped',
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: [],
};
}),
});
}
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: [],
};
}),
};
}),
children: contractorNodes,
};
return supervisorNode;
@@ -211,27 +258,34 @@ export const DashboardPage: React.FC = () => {
e => e.role === 'Contractor' && e.department_id === user.department_id
);
return deptContractors.map(contractor => {
// Get employees without a contractor but in this department (e.g., swapped employees)
const unassignedEmployees = employees.filter(
e => e.role === 'Employee' &&
e.department_id === user.department_id &&
!e.contractor_id
);
const contractorNodes: HierarchyNode[] = deptContractors.map(contractor => {
const contractorEmployees = employees.filter(
e => e.role === 'Employee' && e.contractor_id === contractor.id
);
const contractorNode: HierarchyNode = {
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);
const empAllocation = allocations.find(a => a.employee_id === emp.id && a.status !== 'Completed');
return {
id: emp.id,
name: emp.name,
role: 'Employee',
department: emp.department_name || '',
subDepartment: emp.sub_department_name,
activity: empAllocation?.description || '-',
subDepartment: empAllocation?.sub_department_name || '-',
activity: empAllocation?.description || empAllocation?.activity || '-',
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),
@@ -240,10 +294,38 @@ export const DashboardPage: React.FC = () => {
};
}),
};
return contractorNode;
});
}, [isSupervisor, user, employees, attendance, allocations]);
// Add unassigned employees node if there are any
if (unassignedEmployees.length > 0) {
contractorNodes.push({
id: -user.department_id, // Negative ID to avoid conflicts
name: 'Unassigned (Swapped)',
role: 'Contractor',
department: filteredDepartments[0]?.name || '',
children: unassignedEmployees.map(emp => {
const empAttendance = attendance.find(a => a.employee_id === emp.id);
const empAllocation = allocations.find(a => a.employee_id === emp.id && a.status !== 'Completed');
return {
id: emp.id,
name: emp.name,
role: 'Employee',
department: emp.department_name || '',
subDepartment: empAllocation?.sub_department_name || '-',
activity: empAllocation?.description || empAllocation?.activity || 'Swapped',
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 contractorNodes;
}, [isSupervisor, user, employees, attendance, allocations, filteredDepartments]);
// Department presence data for bar chart
const departmentPresenceData = useMemo(() => {
@@ -405,10 +487,19 @@ export const DashboardPage: React.FC = () => {
{/* Daily Attendance Report Header */}
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-800">Daily Attendance Report</h1>
<button className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
<Calendar size={18} />
Date Range
</button>
<div className="flex items-center gap-2">
<button
onClick={refreshAllData}
className="flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
>
<RefreshCw size={18} />
Refresh
</button>
<button className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
<Calendar size={18} />
Date Range
</button>
</div>
</div>
{/* Search Bar */}
@@ -647,10 +738,19 @@ export const DashboardPage: React.FC = () => {
<h1 className="text-2xl font-bold text-gray-800">{departmentName} Dashboard</h1>
<p className="text-gray-500 mt-1">Daily Attendance & Work Overview</p>
</div>
<button className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
<Calendar size={18} />
Date Range
</button>
<div className="flex items-center gap-2">
<button
onClick={refreshAllData}
className="flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
>
<RefreshCw size={18} />
Refresh
</button>
<button className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
<Calendar size={18} />
Date Range
</button>
</div>
</div>
{/* Search Bar */}