(Feat-Fix): Lots of fixes.
This commit is contained in:
@@ -36,12 +36,20 @@ router.get(
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
COALESCE(oc.name, c.name) as contractor_name,
|
||||
es.id as active_swap_id,
|
||||
es.original_contractor_id,
|
||||
es.target_contractor_id,
|
||||
oc.name as original_contractor_name,
|
||||
tc.name as target_contractor_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
JOIN users s ON a.supervisor_id = s.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN users c ON e.contractor_id = c.id
|
||||
LEFT JOIN employee_swaps es ON es.employee_id = e.id AND es.status = 'Active' AND es.swap_date <= a.work_date
|
||||
LEFT JOIN users oc ON es.original_contractor_id = oc.id
|
||||
LEFT JOIN users tc ON es.target_contractor_id = tc.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const queryParams: unknown[] = [];
|
||||
@@ -97,12 +105,18 @@ router.get("/:id", authenticateToken, async (ctx) => {
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
COALESCE(oc.name, c.name) as contractor_name,
|
||||
es.id as active_swap_id,
|
||||
oc.name as original_contractor_name,
|
||||
tc.name as target_contractor_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
JOIN users s ON a.supervisor_id = s.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN users c ON e.contractor_id = c.id
|
||||
LEFT JOIN employee_swaps es ON es.employee_id = e.id AND es.status = 'Active' AND es.swap_date <= a.work_date
|
||||
LEFT JOIN users oc ON es.original_contractor_id = oc.id
|
||||
LEFT JOIN users tc ON es.target_contractor_id = tc.id
|
||||
WHERE a.id = ?`,
|
||||
[attendanceId],
|
||||
);
|
||||
|
||||
@@ -29,11 +29,14 @@ router.get(
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
e.phone_number as employee_phone,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
COALESCE(oc.name, c.name) as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name,
|
||||
d.id as department_id,
|
||||
sr.rate as standard_rate
|
||||
sr.rate as standard_rate,
|
||||
es.id as active_swap_id,
|
||||
oc.name as original_contractor_name,
|
||||
tc.name as target_contractor_name
|
||||
FROM work_allocations wa
|
||||
JOIN users e ON wa.employee_id = e.id
|
||||
JOIN users s ON wa.supervisor_id = s.id
|
||||
@@ -42,6 +45,9 @@ router.get(
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN standard_rates sr ON wa.sub_department_id = sr.sub_department_id
|
||||
AND wa.activity = sr.activity
|
||||
LEFT JOIN employee_swaps es ON es.employee_id = e.id AND es.status = 'Active' AND es.swap_date <= wa.assigned_date
|
||||
LEFT JOIN users oc ON es.original_contractor_id = oc.id
|
||||
LEFT JOIN users tc ON es.target_contractor_id = tc.id
|
||||
WHERE wa.status = 'Completed'
|
||||
`;
|
||||
const queryParams: unknown[] = [];
|
||||
|
||||
@@ -76,6 +76,13 @@ export const DashboardPage: React.FC = () => {
|
||||
const [contractorRates, setContractorRates] = useState<
|
||||
Record<number, number>
|
||||
>({});
|
||||
const [activeSwaps, setActiveSwaps] = useState<{
|
||||
employee_id: number;
|
||||
original_department_id: number;
|
||||
target_department_id: number;
|
||||
original_contractor_id?: number;
|
||||
target_contractor_id?: number;
|
||||
}[]>([]);
|
||||
|
||||
const refreshAllData = () => {
|
||||
refreshEmployees();
|
||||
@@ -84,6 +91,12 @@ export const DashboardPage: React.FC = () => {
|
||||
api.getAttendance({ startDate: today, endDate: today })
|
||||
.then(setAttendance)
|
||||
.catch(console.error);
|
||||
// Refresh active swaps for SuperAdmin
|
||||
if (isSuperAdmin) {
|
||||
api.getEmployeeSwaps({ status: "Active" })
|
||||
.then(setActiveSwaps)
|
||||
.catch(console.error);
|
||||
}
|
||||
};
|
||||
|
||||
const isSuperAdmin = user?.role === "SuperAdmin";
|
||||
@@ -102,6 +115,15 @@ export const DashboardPage: React.FC = () => {
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
// Fetch active swaps for SuperAdmin
|
||||
useEffect(() => {
|
||||
if (isSuperAdmin) {
|
||||
api.getEmployeeSwaps({ status: "Active" })
|
||||
.then(setActiveSwaps)
|
||||
.catch(console.error);
|
||||
}
|
||||
}, [isSuperAdmin]);
|
||||
|
||||
// Fetch contractor rates for supervisor view
|
||||
useEffect(() => {
|
||||
if (isSupervisor) {
|
||||
@@ -213,6 +235,26 @@ export const DashboardPage: React.FC = () => {
|
||||
|
||||
const supervisors = employees.filter((e) => e.role === "Supervisor");
|
||||
|
||||
// Helper function to get the effective contractor ID for an employee
|
||||
// If employee has an active swap, return the original contractor ID
|
||||
const getEffectiveContractorId = (employeeId: number, currentContractorId?: number) => {
|
||||
const swap = activeSwaps.find((s) => s.employee_id === employeeId);
|
||||
if (swap && swap.original_contractor_id) {
|
||||
return swap.original_contractor_id;
|
||||
}
|
||||
return currentContractorId;
|
||||
};
|
||||
|
||||
// Helper function to get the effective department ID for an employee
|
||||
// If employee has an active swap, return the original department ID
|
||||
const getEffectiveDepartmentId = (employeeId: number, currentDepartmentId?: number) => {
|
||||
const swap = activeSwaps.find((s) => s.employee_id === employeeId);
|
||||
if (swap) {
|
||||
return swap.original_department_id;
|
||||
}
|
||||
return currentDepartmentId;
|
||||
};
|
||||
|
||||
return supervisors.map((supervisor) => {
|
||||
const deptContractors = employees.filter(
|
||||
(e) =>
|
||||
@@ -221,30 +263,49 @@ export const DashboardPage: React.FC = () => {
|
||||
);
|
||||
|
||||
// Get employees without a contractor but in this department (e.g., swapped employees)
|
||||
// But exclude employees who have an active swap with an original contractor
|
||||
const unassignedEmployees = employees.filter(
|
||||
(e) =>
|
||||
e.role === "Employee" &&
|
||||
e.department_id === supervisor.department_id &&
|
||||
!e.contractor_id,
|
||||
getEffectiveDepartmentId(e.id, e.department_id) === supervisor.department_id &&
|
||||
!e.contractor_id &&
|
||||
!activeSwaps.find((s) => s.employee_id === e.id && s.original_contractor_id),
|
||||
);
|
||||
|
||||
const contractorNodes = deptContractors.map((contractor) => {
|
||||
// Include employees whose original contractor (from swap) is this one
|
||||
// OR whose current contractor is this one AND they're not swapped away
|
||||
const contractorEmployees = employees.filter(
|
||||
(e) => e.role === "Employee" && e.contractor_id === contractor.id,
|
||||
(e) => e.role === "Employee" && getEffectiveContractorId(e.id, e.contractor_id) === contractor.id,
|
||||
);
|
||||
|
||||
return {
|
||||
id: contractor.id,
|
||||
name: contractor.name,
|
||||
role: "Contractor",
|
||||
department: contractor.department_name || "",
|
||||
children: contractorEmployees.map((emp) => {
|
||||
// Find employees swapped INTO this contractor (target_contractor_id matches)
|
||||
const swappedInEmployees = activeSwaps
|
||||
.filter((s) => s.target_contractor_id === contractor.id)
|
||||
.map((swap) => employees.find((e) => e.id === swap.employee_id))
|
||||
.filter((e): e is NonNullable<typeof e> => e !== undefined);
|
||||
|
||||
// Build children: regular employees + swapped-in employees as nested
|
||||
const employeeNodes = 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"
|
||||
);
|
||||
const empSwap = activeSwaps.find((s) => s.employee_id === emp.id);
|
||||
|
||||
// If employee is swapped OUT, show swap info in activity
|
||||
let activityText = empAllocation?.description || empAllocation?.activity || "-";
|
||||
if (empSwap) {
|
||||
// Find target contractor/department name
|
||||
const targetContractor = employees.find((e) => e.id === empSwap.target_contractor_id);
|
||||
const targetDept = departments.find((d) => d.id === empSwap.target_department_id);
|
||||
activityText = `Swapped to ${targetContractor?.name || targetDept?.name || "another dept"}`;
|
||||
}
|
||||
|
||||
// Swapped-in employees are added at contractor level, not as children of individual employees
|
||||
const swappedInChildren: HierarchyNode[] = [];
|
||||
|
||||
return {
|
||||
id: emp.id,
|
||||
@@ -252,8 +313,41 @@ export const DashboardPage: React.FC = () => {
|
||||
role: "Employee",
|
||||
department: emp.department_name || "",
|
||||
subDepartment: empAllocation?.sub_department_name || "-",
|
||||
activity: empAllocation?.description || empAllocation?.activity ||
|
||||
"-",
|
||||
activity: activityText,
|
||||
status: empAttendance
|
||||
? (empAttendance.status === "CheckedIn" ||
|
||||
empAttendance.status === "CheckedOut"
|
||||
? "Present"
|
||||
: "Absent")
|
||||
: undefined,
|
||||
inTime: empAttendance?.check_in_time
|
||||
? new Date(empAttendance.check_in_time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
: undefined,
|
||||
outTime: empAttendance?.check_out_time
|
||||
? new Date(empAttendance.check_out_time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
: undefined,
|
||||
remark: empAttendance?.remark,
|
||||
children: swappedInChildren,
|
||||
};
|
||||
});
|
||||
|
||||
// Add swapped-in employees as a separate group under this contractor
|
||||
const swappedInNodes = swappedInEmployees.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"
|
||||
);
|
||||
const empSwap = activeSwaps.find((s) => s.employee_id === emp.id);
|
||||
const originalContractor = employees.find((e) => e.id === empSwap?.original_contractor_id);
|
||||
const originalDept = departments.find((d) => d.id === empSwap?.original_department_id);
|
||||
|
||||
return {
|
||||
id: emp.id,
|
||||
name: `${emp.name} (from ${originalContractor?.name || originalDept?.name || "other"})`,
|
||||
role: "Employee",
|
||||
department: emp.department_name || "",
|
||||
subDepartment: empAllocation?.sub_department_name || "-",
|
||||
activity: empAllocation?.description || empAllocation?.activity || "-",
|
||||
status: empAttendance
|
||||
? (empAttendance.status === "CheckedIn" ||
|
||||
empAttendance.status === "CheckedOut"
|
||||
@@ -269,7 +363,14 @@ export const DashboardPage: React.FC = () => {
|
||||
remark: empAttendance?.remark,
|
||||
children: [],
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
id: contractor.id,
|
||||
name: contractor.name,
|
||||
role: "Contractor",
|
||||
department: contractor.department_name || "",
|
||||
children: [...employeeNodes, ...swappedInNodes],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -325,7 +426,7 @@ export const DashboardPage: React.FC = () => {
|
||||
|
||||
return supervisorNode;
|
||||
});
|
||||
}, [isSuperAdmin, employees, attendance, allocations]);
|
||||
}, [isSuperAdmin, employees, attendance, allocations, activeSwaps, departments]);
|
||||
|
||||
// Build hierarchy data for Supervisor view (department-specific)
|
||||
const supervisorHierarchyData = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user