(Feat-Fix): New Reporting system, more seeded data, fixed subdepartments and activity inversion, login page changes, etc etc
This commit is contained in:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user