diff --git a/backend-deno/routes/reports.ts b/backend-deno/routes/reports.ts index 41f4eaa..87488b7 100644 --- a/backend-deno/routes/reports.ts +++ b/backend-deno/routes/reports.ts @@ -32,13 +32,16 @@ router.get( c.name as contractor_name, sd.name as sub_department_name, d.name as department_name, - d.id as department_id + d.id as department_id, + sr.rate as standard_rate FROM work_allocations wa JOIN users e ON wa.employee_id = e.id JOIN users s ON wa.supervisor_id = s.id JOIN users c ON wa.contractor_id = c.id LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id 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 WHERE wa.status = 'Completed' `; const queryParams: unknown[] = []; diff --git a/index.html b/index.html index 2b85627..796c0ea 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - my-dashboard + WorkAllocation
diff --git a/src/pages/RatesPage.tsx b/src/pages/RatesPage.tsx index 275ebd8..415a544 100644 --- a/src/pages/RatesPage.tsx +++ b/src/pages/RatesPage.tsx @@ -485,7 +485,7 @@ export const RatesPage: React.FC = () => {
- -
- + // Selection card component + const SelectionCard = ({ + item, + isSelected, + onClick, + icon, + }: { + item: SelectionCard; + isSelected: boolean; + onClick: () => void; + icon: React.ReactNode; + }) => ( + + ); - - {/* Filters */} -
-
- -

Filters

+ // Step indicator + const StepIndicator = () => ( +
+ {[1, 2, 3, 4].map((step) => ( + +
+ {step < wizardStep ? : step} +
+ {step < 4 && ( +
+ )} + + ))} +
+ ); + + // Render wizard steps + const renderWizardContent = () => { + switch (wizardStep) { + case 1: + return ( +
+
+ +

Select Date Range

+

Choose the period for your report

-
+
- - Start Date + setDateRange((prev) => ({ ...prev, startDate: e.target.value }))} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
- - End Date + setDateRange((prev) => ({ ...prev, endDate: e.target.value }))} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
- ({ - value: String(c.id), - label: c.name, - })), - ]} - /> +
-
- ({ - value: String(sd.id), - label: sd.name, - })), - ]} - /> - setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent" + />
+
- )} - {/* Search and Refresh */} -
-
- - setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
- -
+ {error && ( +
Error: {error}
+ )} - {/* Error */} - {error && ( -
- Error: {error} -
- )} - - {/* Table */} - {loading - ?
Loading report data...
- : filteredAllocations.length > 0 - ? ( + {/* Table */} + {loading ? ( +
Loading report data...
+ ) : filteredAllocations.length > 0 ? (
@@ -412,71 +474,111 @@ export const ReportingPage: React.FC = () => { Activity Assigned Completed - Rate (₹) + Actual Rate (₹) + Standard Rate (₹) + Difference (₹) Units Total (₹) {filteredAllocations.map((allocation) => { const rate = parseFloat(allocation.rate) || 0; + const standardRate = parseFloat(allocation.standard_rate) || 0; + const difference = rate - standardRate; const units = parseFloat(allocation.units) || 0; const total = parseFloat(allocation.total_amount) || rate; return ( {allocation.id} - - {allocation.employee_name || "-"} - + {allocation.employee_name || "-"} + {allocation.contractor_name || "-"} + {allocation.department_name || "-"} + {allocation.sub_department_name || "-"} - {allocation.contractor_name || "-"} - - - {allocation.department_name || "-"} - - - {allocation.sub_department_name || "-"} - - - + {allocation.activity || "Standard"} + {new Date(allocation.assigned_date).toLocaleDateString()} - {new Date(allocation.assigned_date) - .toLocaleDateString()} - - - {allocation.completion_date - ? new Date(allocation.completion_date) - .toLocaleDateString() - : "-"} + {allocation.completion_date ? new Date(allocation.completion_date).toLocaleDateString() : "-"} ₹{rate.toFixed(2)} - {units > 0 ? units : "-"} - - ₹{total.toFixed(2)} + {standardRate > 0 ? `₹${standardRate.toFixed(2)}` : "-"} + + {standardRate > 0 ? ( + 0 ? "text-red-600" : difference < 0 ? "text-green-600" : "text-gray-600" + }`}> + {difference > 0 ? "+" : ""}₹{difference.toFixed(2)} + + ) : "-"} + {units > 0 ? units : "-"} + ₹{total.toFixed(2)} ); })}
- ) - : ( + ) : (
- No completed work allocations found. Adjust your filters or - check back later. + No completed work allocations found for the selected criteria.
)} + + +
+ ); + } + + // Wizard view + return ( +
+ +
+
+ +

Generate Work Allocation Report

+
+
+ + +
+ + + {renderWizardContent()} + + {/* Navigation */} +
+ + + {wizardStep < 4 ? ( + + ) : ( + + )} +
+
diff --git a/src/utils/excelExport.ts b/src/utils/excelExport.ts index 6548291..a3440c3 100644 --- a/src/utils/excelExport.ts +++ b/src/utils/excelExport.ts @@ -324,3 +324,150 @@ export const exportAllocationsToXLSX = ( // Write and download XLSX.writeFile(wb, outputFilename); }; + +/** + * Combined export function with improved formatting + * Includes standard rate and difference columns + */ +export const exportReportToXLSX = ( + allocations: AllocationData[], + departmentName: string, + dateRange: { startDate: string; endDate: string }, +) => { + if (allocations.length === 0) { + alert("No data to export"); + return; + } + + // Create workbook + const wb = XLSX.utils.book_new(); + + // ===== Sheet 1: Detailed Report ===== + const detailedData = allocations.map((a, index) => { + const rate = parseFloat(String(a.rate)) || 0; + const standardRate = parseFloat(String((a as any).standard_rate)) || 0; + const difference = standardRate > 0 ? rate - standardRate : 0; + const units = parseFloat(String(a.units)) || 0; + const total = parseFloat(String(a.total_amount)) || rate; + + return { + "S.No": index + 1, + "Employee": a.employee_name || "", + "Contractor": a.contractor_name || "", + "Department": a.department_name || "", + "Sub-Department": a.sub_department_name || "", + "Activity": a.activity || "Standard", + "Assigned Date": a.assigned_date + ? new Date(a.assigned_date).toLocaleDateString() + : "", + "Completed Date": a.completion_date + ? new Date(a.completion_date).toLocaleDateString() + : "", + "Actual Rate (₹)": rate, + "Standard Rate (₹)": standardRate > 0 ? standardRate : "-", + "Difference (₹)": standardRate > 0 ? difference : "-", + "Units": units > 0 ? units : "-", + "Total Amount (₹)": total, + }; + }); + + const ws1 = XLSX.utils.json_to_sheet(detailedData); + + // Set column widths for detailed sheet + ws1["!cols"] = [ + { wch: 6 }, // S.No + { wch: 22 }, // Employee + { wch: 20 }, // Contractor + { wch: 15 }, // Department + { wch: 18 }, // Sub-Department + { wch: 15 }, // Activity + { wch: 12 }, // Assigned Date + { wch: 14 }, // Completed Date + { wch: 14 }, // Actual Rate + { wch: 16 }, // Standard Rate + { wch: 14 }, // Difference + { wch: 8 }, // Units + { wch: 16 }, // Total Amount + ]; + + XLSX.utils.book_append_sheet(wb, ws1, "Detailed Report"); + + // ===== Sheet 2: Summary by Activity ===== + const activitySummary = new Map(); + + allocations.forEach((a) => { + const activity = a.activity || "Standard"; + const existing = activitySummary.get(activity) || { count: 0, totalAmount: 0, totalUnits: 0 }; + existing.count += 1; + existing.totalAmount += parseFloat(String(a.total_amount)) || parseFloat(String(a.rate)) || 0; + existing.totalUnits += parseFloat(String(a.units)) || 0; + activitySummary.set(activity, existing); + }); + + const summaryData = Array.from(activitySummary.entries()).map(([activity, data]) => ({ + "Activity": activity, + "Total Completed": data.count, + "Total Units": data.totalUnits, + "Total Amount (₹)": data.totalAmount.toFixed(2), + })); + + // Add grand total row + const grandTotal = { + "Activity": "GRAND TOTAL", + "Total Completed": allocations.length, + "Total Units": allocations.reduce((sum, a) => sum + (parseFloat(String(a.units)) || 0), 0), + "Total Amount (₹)": allocations.reduce((sum, a) => + sum + (parseFloat(String(a.total_amount)) || parseFloat(String(a.rate)) || 0), 0 + ).toFixed(2), + }; + summaryData.push(grandTotal); + + const ws2 = XLSX.utils.json_to_sheet(summaryData); + ws2["!cols"] = [ + { wch: 25 }, // Activity + { wch: 16 }, // Total Completed + { wch: 12 }, // Total Units + { wch: 18 }, // Total Amount + ]; + + XLSX.utils.book_append_sheet(wb, ws2, "Summary by Activity"); + + // ===== Sheet 3: Summary by Contractor ===== + const contractorSummary = new Map }>(); + + allocations.forEach((a) => { + const contractor = a.contractor_name || "Unknown"; + const existing = contractorSummary.get(contractor) || { count: 0, totalAmount: 0, employees: new Set() }; + existing.count += 1; + existing.totalAmount += parseFloat(String(a.total_amount)) || parseFloat(String(a.rate)) || 0; + if (a.employee_name) existing.employees.add(a.employee_name); + contractorSummary.set(contractor, existing); + }); + + const contractorData = Array.from(contractorSummary.entries()).map(([contractor, data]) => ({ + "Contractor": contractor, + "Employees": data.employees.size, + "Total Completed": data.count, + "Total Amount (₹)": data.totalAmount.toFixed(2), + })); + + const ws3 = XLSX.utils.json_to_sheet(contractorData); + ws3["!cols"] = [ + { wch: 25 }, // Contractor + { wch: 12 }, // Employees + { wch: 16 }, // Total Completed + { wch: 18 }, // Total Amount + ]; + + XLSX.utils.book_append_sheet(wb, ws3, "Summary by Contractor"); + + // Generate filename + const dateStr = dateRange.startDate && dateRange.endDate + ? `${dateRange.startDate}_to_${dateRange.endDate}` + : new Date().toISOString().split("T")[0]; + const deptStr = departmentName.toLowerCase().replace(/\s+/g, "_"); + const filename = `work_report_${deptStr}_${dateStr}.xlsx`; + + // Write and download + XLSX.writeFile(wb, filename); +};