From 01400ad4e1b4e512f5a99527f6409fa30f15e9ff Mon Sep 17 00:00:00 2001 From: bereck-work Date: Thu, 18 Dec 2025 10:11:22 +0000 Subject: [PATCH] (Fix): Fixed the excel reporting system, fixed alot of hardcoded dropdown values in activities, auto selection of departments is also implemented --- backend-deno/routes/contractor-rates.ts | 16 +- backend-deno/routes/standard-rates.ts | 20 +- package-lock.json | 106 +++++++- package.json | 3 +- src/pages/ActivitiesPage.tsx | 22 +- src/pages/RatesPage.tsx | 50 ++-- src/pages/ReportingPage.tsx | 98 +++----- src/pages/StandardRatesPage.tsx | 32 ++- src/pages/WorkAllocationPage.tsx | 32 ++- src/utils/excelExport.ts | 306 ++++++++++++++++++++++++ 10 files changed, 568 insertions(+), 117 deletions(-) create mode 100644 src/utils/excelExport.ts diff --git a/backend-deno/routes/contractor-rates.ts b/backend-deno/routes/contractor-rates.ts index c88b5b4..6eedd96 100644 --- a/backend-deno/routes/contractor-rates.ts +++ b/backend-deno/routes/contractor-rates.ts @@ -17,11 +17,13 @@ router.get("/", authenticateToken, async (ctx) => { SELECT cr.*, u.name as contractor_name, u.username as contractor_username, sd.name as sub_department_name, - d.name as department_name + d.name as department_name, + a.unit_of_measurement FROM contractor_rates cr JOIN users u ON cr.contractor_id = u.id LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id LEFT JOIN departments d ON sd.department_id = d.id + LEFT JOIN activities a ON a.sub_department_id = cr.sub_department_id AND a.name = cr.activity WHERE 1=1 `; const queryParams: unknown[] = []; @@ -57,10 +59,12 @@ router.get("/contractor/:contractorId/current", authenticateToken, async (ctx) = let query = ` SELECT cr.*, u.name as contractor_name, u.username as contractor_username, - sd.name as sub_department_name + sd.name as sub_department_name, + a.unit_of_measurement FROM contractor_rates cr JOIN users u ON cr.contractor_id = u.id LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id + LEFT JOIN activities a ON a.sub_department_id = cr.sub_department_id AND a.name = cr.activity WHERE cr.contractor_id = ? `; const queryParams: unknown[] = [contractorId]; @@ -130,10 +134,12 @@ router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async const newRate = await db.query( `SELECT cr.*, u.name as contractor_name, u.username as contractor_username, - sd.name as sub_department_name + sd.name as sub_department_name, + a.unit_of_measurement FROM contractor_rates cr JOIN users u ON cr.contractor_id = u.id LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id + LEFT JOIN activities a ON a.sub_department_id = cr.sub_department_id AND a.name = cr.activity WHERE cr.id = ?`, [result.insertId] ); @@ -197,10 +203,12 @@ router.put("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), asy const updatedRate = await db.query( `SELECT cr.*, u.name as contractor_name, u.username as contractor_username, - sd.name as sub_department_name + sd.name as sub_department_name, + a.unit_of_measurement FROM contractor_rates cr JOIN users u ON cr.contractor_id = u.id LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id + LEFT JOIN activities a ON a.sub_department_id = cr.sub_department_id AND a.name = cr.activity WHERE cr.id = ?`, [rateId] ); diff --git a/backend-deno/routes/standard-rates.ts b/backend-deno/routes/standard-rates.ts index 90d0d41..1570743 100644 --- a/backend-deno/routes/standard-rates.ts +++ b/backend-deno/routes/standard-rates.ts @@ -34,11 +34,13 @@ router.get("/", authenticateToken, async (ctx) => { sd.name as sub_department_name, d.name as department_name, d.id as department_id, - u.name as created_by_name + u.name as created_by_name, + a.unit_of_measurement FROM standard_rates sr LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id LEFT JOIN departments d ON sd.department_id = d.id LEFT JOIN users u ON sr.created_by = u.id + LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity WHERE 1=1 `; const queryParams: unknown[] = []; @@ -209,10 +211,12 @@ router.get("/compare", authenticateToken, authorize("Supervisor", "SuperAdmin"), SELECT sr.*, sd.name as sub_department_name, d.name as department_name, - d.id as department_id + d.id as department_id, + a.unit_of_measurement FROM standard_rates sr LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id LEFT JOIN departments d ON sd.department_id = d.id + LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity WHERE 1=1 ${departmentFilter} `; @@ -231,11 +235,13 @@ router.get("/compare", authenticateToken, authorize("Supervisor", "SuperAdmin"), u.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, + a.unit_of_measurement FROM contractor_rates cr JOIN users u ON cr.contractor_id = u.id LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id LEFT JOIN departments d ON sd.department_id = d.id + LEFT JOIN activities a ON a.sub_department_id = cr.sub_department_id AND a.name = cr.activity WHERE 1=1 `; const contractorParams: unknown[] = []; @@ -337,11 +343,13 @@ router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async `SELECT sr.*, sd.name as sub_department_name, d.name as department_name, - u.name as created_by_name + u.name as created_by_name, + a.unit_of_measurement FROM standard_rates sr LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id LEFT JOIN departments d ON sd.department_id = d.id LEFT JOIN users u ON sr.created_by = u.id + LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity WHERE sr.id = ?`, [result.insertId] ); @@ -421,11 +429,13 @@ router.put("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), asy `SELECT sr.*, sd.name as sub_department_name, d.name as department_name, - u.name as created_by_name + u.name as created_by_name, + a.unit_of_measurement FROM standard_rates sr LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id LEFT JOIN departments d ON sd.department_id = d.id LEFT JOIN users u ON sr.created_by = u.id + LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity WHERE sr.id = ?`, [rateId] ); diff --git a/package-lock.json b/package-lock.json index 65b37d7..a0fa34e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "lucide-react": "^0.555.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "recharts": "^3.5.0" + "recharts": "^3.5.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -1769,6 +1770,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1995,6 +2005,19 @@ } ] }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2054,6 +2077,15 @@ "node": ">=6" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2090,6 +2122,18 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2648,6 +2692,15 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -3721,6 +3774,18 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4106,6 +4171,24 @@ "node": ">= 8" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4114,6 +4197,27 @@ "node": ">=0.10.0" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 2db841a..31af9bc 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "lucide-react": "^0.555.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "recharts": "^3.5.0" + "recharts": "^3.5.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/src/pages/ActivitiesPage.tsx b/src/pages/ActivitiesPage.tsx index 68b0c8b..9568675 100644 --- a/src/pages/ActivitiesPage.tsx +++ b/src/pages/ActivitiesPage.tsx @@ -273,12 +273,10 @@ export const ActivitiesPage: React.FC = () => { {/* Sub-Departments List */} - - Sub-Department Name - Activities Count - Created At - Actions - + Sub-Department Name + Activities Count + Created At + Actions {subDepartments.length === 0 ? ( @@ -358,13 +356,11 @@ export const ActivitiesPage: React.FC = () => { {/* Activities List */}
- - Activity Name - Sub-Department - Unit of Measurement - Created At - Actions - + Activity Name + Sub-Department + Unit of Measurement + Created At + Actions {activities.length === 0 ? ( diff --git a/src/pages/RatesPage.tsx b/src/pages/RatesPage.tsx index cd09c4f..7ce0f7e 100644 --- a/src/pages/RatesPage.tsx +++ b/src/pages/RatesPage.tsx @@ -6,6 +6,7 @@ import { Button } from '../components/ui/Button'; import { Input, Select } from '../components/ui/Input'; import { api } from '../services/api'; import { useDepartments, useSubDepartments } from '../hooks/useDepartments'; +import { useActivities } from '../hooks/useActivities'; import { useAuth } from '../contexts/AuthContext'; export const RatesPage: React.FC = () => { @@ -16,7 +17,6 @@ export const RatesPage: React.FC = () => { const [contractors, setContractors] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); - // Form state const [formData, setFormData] = useState({ contractorId: '', @@ -27,6 +27,7 @@ export const RatesPage: React.FC = () => { }); const [selectedDept, setSelectedDept] = useState(''); const { subDepartments } = useSubDepartments(selectedDept); + const { activities } = useActivities(formData.subDepartmentId); const [formError, setFormError] = useState(''); const [formLoading, setFormLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); @@ -72,7 +73,24 @@ export const RatesPage: React.FC = () => { const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + + // Auto-select department when contractor is selected + if (name === 'contractorId' && value) { + const selectedContractor = contractors.find(c => String(c.id) === value); + if (selectedContractor?.department_id) { + setSelectedDept(String(selectedContractor.department_id)); + // Clear sub-department and activity when contractor changes + setFormData(prev => ({ ...prev, [name]: value, subDepartmentId: '', activity: '' })); + } else { + setFormData(prev => ({ ...prev, [name]: value })); + } + } + // Clear activity when sub-department changes + else if (name === 'subDepartmentId') { + setFormData(prev => ({ ...prev, [name]: value, activity: '' })); + } else { + setFormData(prev => ({ ...prev, [name]: value })); + } setFormError(''); }; @@ -239,7 +257,7 @@ export const RatesPage: React.FC = () => { {rate.sub_department_name || '-'} @@ -248,7 +266,7 @@ export const RatesPage: React.FC = () => { - {rate.activity === 'Loading' || rate.activity === 'Unloading' + {rate.unit_of_measurement === 'Per Bag' ? 'Per Unit' : 'Flat Rate'} @@ -302,8 +320,8 @@ export const RatesPage: React.FC = () => {

Rate Calculation Info

    -
  • Loading/Unloading: Total = Units × Rate per Unit
  • -
  • Standard/Other: Total = Flat Rate (no unit calculation)
  • +
  • Per Bag Activities: Total = Units × Rate per Unit
  • +
  • Fixed Rate Activities: Total = Flat Rate (no unit calculation)
@@ -359,18 +377,22 @@ export const RatesPage: React.FC = () => { name="activity" value={formData.activity} onChange={handleInputChange} + disabled={!formData.subDepartmentId} options={[ - { value: '', label: 'Select Activity (Optional)' }, - { value: 'Loading', label: 'Loading (per unit × rate)' }, - { value: 'Unloading', label: 'Unloading (per unit × rate)' }, - { value: 'Standard', label: 'Standard Work (flat rate)' }, - { value: 'Other', label: 'Other (flat rate)' }, + { value: '', label: formData.subDepartmentId ? 'Select Activity (Optional)' : 'Select Sub-Department First' }, + ...activities.map(a => ({ + value: a.name, + label: `${a.name} (${a.unit_of_measurement === 'Per Bag' ? 'per unit × rate' : 'flat rate'})` + })) ]} /> { + const selectedActivity = activities.find(a => a.name === formData.activity); + return selectedActivity?.unit_of_measurement === 'Per Bag' + ? "Rate per Unit (₹)" + : "Rate Amount (₹)"; + })()} name="rate" type="number" value={formData.rate} diff --git a/src/pages/ReportingPage.tsx b/src/pages/ReportingPage.tsx index 0812b11..5209959 100644 --- a/src/pages/ReportingPage.tsx +++ b/src/pages/ReportingPage.tsx @@ -7,6 +7,7 @@ import { Input, Select } from '../components/ui/Input'; import { api } from '../services/api'; import { useDepartments } from '../hooks/useDepartments'; import { useAuth } from '../contexts/AuthContext'; +import { exportWorkReportToXLSX, exportAllocationsToXLSX } from '../utils/excelExport'; export const ReportingPage: React.FC = () => { const { user } = useAuth(); @@ -71,74 +72,35 @@ export const ReportingPage: React.FC = () => { ); }, [allocations, searchQuery]); - // Export to Excel (CSV format) - const exportToExcel = () => { + // Get selected department name + const selectedDeptName = filters.departmentId + ? departments.find(d => d.id === parseInt(filters.departmentId))?.name || 'All Departments' + : user?.role === 'Supervisor' + ? departments.find(d => d.id === user?.department_id)?.name || 'Department' + : 'All Departments'; + + // Export to Excel (XLSX format) - Formatted Report + const exportFormattedReport = () => { if (filteredAllocations.length === 0) { alert('No data to export'); return; } - // Define headers - const headers = [ - 'ID', - 'Employee Name', - 'Employee Phone', - 'Contractor', - 'Department', - 'Sub-Department', - 'Activity', - 'Assigned Date', - 'Completion Date', - 'Rate (₹)', - 'Units', - 'Total Amount (₹)', - 'Status', - ]; + exportWorkReportToXLSX( + filteredAllocations, + selectedDeptName, + { startDate: filters.startDate, endDate: filters.endDate } + ); + }; - // Map data to rows - const rows = filteredAllocations.map(a => [ - a.id, - a.employee_name || '', - a.employee_phone || '', - a.contractor_name || '', - a.department_name || '', - a.sub_department_name || '', - a.activity || 'Standard', - a.assigned_date ? new Date(a.assigned_date).toLocaleDateString() : '', - a.completion_date ? new Date(a.completion_date).toLocaleDateString() : '', - a.rate || 0, - a.units || '', - a.total_amount || a.rate || 0, - a.status, - ]); + // Export to Excel (XLSX format) - Simple List + const exportSimpleList = () => { + if (filteredAllocations.length === 0) { + alert('No data to export'); + return; + } - // Create CSV content - const csvContent = [ - headers.join(','), - ...rows.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')) - ].join('\n'); - - // Add summary at the end - const summaryRows = [ - '', - 'SUMMARY', - `Total Allocations,${summary?.totalAllocations || 0}`, - `Total Amount (₹),${summary?.totalAmount || 0}`, - `Total Units,${summary?.totalUnits || 0}`, - ]; - - const fullContent = csvContent + '\n' + summaryRows.join('\n'); - - // Create and download file - const blob = new Blob([fullContent], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - const url = URL.createObjectURL(blob); - link.setAttribute('href', url); - link.setAttribute('download', `completed_work_allocations_${new Date().toISOString().split('T')[0]}.csv`); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + exportAllocationsToXLSX(filteredAllocations); }; const handleFilterChange = (e: React.ChangeEvent) => { @@ -169,10 +131,16 @@ export const ReportingPage: React.FC = () => {

Work Allocation Reports

- +
+ + +
diff --git a/src/pages/StandardRatesPage.tsx b/src/pages/StandardRatesPage.tsx index 4a68b8e..ba9891c 100644 --- a/src/pages/StandardRatesPage.tsx +++ b/src/pages/StandardRatesPage.tsx @@ -6,6 +6,7 @@ import { Button } from '../components/ui/Button'; import { Input, Select } from '../components/ui/Input'; import { api } from '../services/api'; import { useDepartments, useSubDepartments } from '../hooks/useDepartments'; +import { useActivities } from '../hooks/useActivities'; import { useAuth } from '../contexts/AuthContext'; export const StandardRatesPage: React.FC = () => { @@ -28,6 +29,7 @@ export const StandardRatesPage: React.FC = () => { }); const [selectedDept, setSelectedDept] = useState(''); const { subDepartments } = useSubDepartments(selectedDept); + const { activities } = useActivities(formData.subDepartmentId); const [formError, setFormError] = useState(''); const [formLoading, setFormLoading] = useState(false); const [editingId, setEditingId] = useState(null); @@ -98,7 +100,12 @@ export const StandardRatesPage: React.FC = () => { const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + // Clear activity when sub-department changes + if (name === 'subDepartmentId') { + setFormData(prev => ({ ...prev, [name]: value, activity: '' })); + } else { + setFormData(prev => ({ ...prev, [name]: value })); + } setFormError(''); }; @@ -278,7 +285,7 @@ export const StandardRatesPage: React.FC = () => { {rate.sub_department_name || 'All'} @@ -380,17 +387,22 @@ export const StandardRatesPage: React.FC = () => { name="activity" value={formData.activity} onChange={handleInputChange} + disabled={!formData.subDepartmentId} options={[ - { value: '', label: 'Standard (Default)' }, - { value: 'Loading', label: 'Loading (per unit)' }, - { value: 'Unloading', label: 'Unloading (per unit)' }, - { value: 'Other', label: 'Other' }, + { value: '', label: formData.subDepartmentId ? 'Standard (Default)' : 'Select Sub-Department First' }, + ...activities.map(a => ({ + value: a.name, + label: `${a.name} (${a.unit_of_measurement === 'Per Bag' ? 'per unit' : 'flat rate'})` + })) ]} /> { + const selectedActivity = activities.find(a => a.name === formData.activity); + return selectedActivity?.unit_of_measurement === 'Per Bag' + ? "Rate per Unit (₹)" + : "Standard Rate (₹)"; + })()} name="rate" type="number" value={formData.rate} @@ -468,7 +480,7 @@ export const StandardRatesPage: React.FC = () => { {comp.sub_department_name || 'All'} diff --git a/src/pages/WorkAllocationPage.tsx b/src/pages/WorkAllocationPage.tsx index a86dfce..0f69121 100644 --- a/src/pages/WorkAllocationPage.tsx +++ b/src/pages/WorkAllocationPage.tsx @@ -60,8 +60,12 @@ export const WorkAllocationPage: React.FC = () => { // Get selected rate details const selectedRate = contractorRates.find(r => r.id === parseInt(formData.rateId)); - // Check if rate is per unit (Loading/Unloading) - const isPerUnitRate = selectedRate?.activity === 'Loading' || selectedRate?.activity === 'Unloading'; + // Get selected activity details + const selectedActivity = activities.find(a => a.name === formData.activity); + + // Check if rate is per unit based on activity's unit_of_measurement + const isPerUnitRate = selectedActivity?.unit_of_measurement === 'Per Bag' || + selectedRate?.unit_of_measurement === 'Per Bag'; // Calculate total amount const unitCount = parseFloat(formData.units) || 0; @@ -89,7 +93,24 @@ export const WorkAllocationPage: React.FC = () => { const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + + // Auto-select department when contractor is selected + if (name === 'contractorId' && value) { + const selectedContractor = contractors.find(c => String(c.id) === value); + if (selectedContractor?.department_id) { + setSelectedDept(String(selectedContractor.department_id)); + // Clear sub-department and activity when contractor changes + setFormData(prev => ({ ...prev, [name]: value, subDepartmentId: '', activity: '', rateId: '' })); + } else { + setFormData(prev => ({ ...prev, [name]: value })); + } + } + // Clear activity when sub-department changes + else if (name === 'subDepartmentId') { + setFormData(prev => ({ ...prev, [name]: value, activity: '' })); + } else { + setFormData(prev => ({ ...prev, [name]: value })); + } setFormError(''); }; @@ -252,7 +273,10 @@ export const WorkAllocationPage: React.FC = () => { disabled={!formData.subDepartmentId} options={[ { value: '', label: formData.subDepartmentId ? 'Select Activity' : 'Select Sub-Department First' }, - ...activities.map(a => ({ value: a.name, label: a.name })) + ...activities.map(a => ({ + value: a.name, + label: `${a.name} (${a.unit_of_measurement === 'Per Bag' ? 'per unit' : 'flat rate'})` + })) ]} /> { + // Group allocations by work (activity + sub_department) and date + const workDataMap = new Map(); + const allDates = new Set(); + + allocations.forEach(allocation => { + const workKey = `${allocation.sub_department_name || ''} ${allocation.activity || 'Standard'}`.trim(); + const date = allocation.assigned_date ? new Date(allocation.assigned_date).getDate().toString() : ''; + + if (date) { + allDates.add(date); + } + + if (!workDataMap.has(workKey)) { + workDataMap.set(workKey, { + work: workKey, + dates: {}, + totalBag: 0, + totalAmount: 0, + }); + } + + const workData = workDataMap.get(workKey)!; + + if (!workData.dates[date]) { + workData.dates[date] = { bag: 0, rate: 0, total: 0 }; + } + + const bag = parseFloat(String(allocation.units)) || 0; + const rate = parseFloat(String(allocation.rate)) || 0; + const total = parseFloat(String(allocation.total_amount)) || (bag * rate) || rate; + + workData.dates[date].bag += bag; + workData.dates[date].rate = rate; // Use latest rate + workData.dates[date].total += total; + workData.totalBag += bag; + workData.totalAmount += total; + }); + + // Sort dates numerically + const sortedDates = Array.from(allDates).sort((a, b) => parseInt(a) - parseInt(b)); + + // Create workbook and worksheet + const wb = XLSX.utils.book_new(); + const wsData: (string | number | null)[][] = []; + + // Row 1: DATE header with merged cells for each date + const dateHeaderRow: (string | number | null)[] = ['', 'DATE']; + sortedDates.forEach(date => { + dateHeaderRow.push(date, '', ''); // Each date spans 3 columns (Bag, Rate, Total) + }); + dateHeaderRow.push('', 'Total', '', '', 'Total-As per Standered', '', ''); + wsData.push(dateHeaderRow); + + // Row 2: WORK and Bag/Rate/Total sub-headers + const subHeaderRow: (string | number | null)[] = ['', 'WORK']; + sortedDates.forEach(() => { + subHeaderRow.push('Bag', 'Rate', 'Total'); + }); + subHeaderRow.push('', 'Bag', 'Rate', 'Total', 'Bag', 'Rate', 'Total'); + wsData.push(subHeaderRow); + + // Row 3: Department header (yellow background) + const deptHeaderRow: (string | number | null)[] = ['', `${departmentName.toUpperCase()} Department`]; + const deptHeaderCols = sortedDates.length * 3 + 7; + for (let col = 0; col < deptHeaderCols; col++) { + deptHeaderRow.push(''); + } + wsData.push(deptHeaderRow); + + // Data rows for each work item + const workDataArray = Array.from(workDataMap.values()); + + workDataArray.forEach((workData, index) => { + const dataRow: (string | number | null)[] = [index + 1, workData.work]; + + sortedDates.forEach(date => { + const dateData = workData.dates[date] || { bag: 0, rate: 0, total: 0 }; + dataRow.push( + dateData.bag || '', + dateData.rate || '', + dateData.total || '' + ); + }); + + // Total columns + dataRow.push(''); // Empty column + dataRow.push(workData.totalBag || ''); + dataRow.push(''); // Rate for total (could be average) + dataRow.push(workData.totalAmount || ''); + + // Standard columns (placeholder - would need standard rates data) + dataRow.push(''); + dataRow.push(''); + dataRow.push(''); + + wsData.push(dataRow); + }); + + // Empty row before Sub Total + wsData.push([]); + + // Sub Total row + const subTotalRow: (string | number | null)[] = ['', 'Sub Total']; + + // Calculate totals for each date + sortedDates.forEach(date => { + let dateBagTotal = 0; + let dateTotalAmount = 0; + workDataArray.forEach(workData => { + const dateData = workData.dates[date]; + if (dateData) { + dateBagTotal += dateData.bag; + dateTotalAmount += dateData.total; + } + }); + subTotalRow.push(dateBagTotal || '', '', dateTotalAmount || ''); + }); + + // Grand totals + const grandTotalBag = workDataArray.reduce((sum, w) => sum + w.totalBag, 0); + const grandTotalAmount = workDataArray.reduce((sum, w) => sum + w.totalAmount, 0); + + subTotalRow.push(''); + subTotalRow.push(grandTotalBag || ''); + subTotalRow.push(''); + subTotalRow.push(grandTotalAmount || ''); + subTotalRow.push(''); + subTotalRow.push(''); + subTotalRow.push(grandTotalAmount || ''); // Standard total same as actual for now + + wsData.push(subTotalRow); + + // Create worksheet + const ws = XLSX.utils.aoa_to_sheet(wsData); + + // Set column widths + const colWidths: { wch: number }[] = [ + { wch: 4 }, // A - Row number + { wch: 35 }, // B - Work name + ]; + + // Add widths for date columns + sortedDates.forEach(() => { + colWidths.push({ wch: 8 }); // Bag + colWidths.push({ wch: 6 }); // Rate + colWidths.push({ wch: 10 }); // Total + }); + + // Total columns + colWidths.push({ wch: 3 }); // Empty + colWidths.push({ wch: 10 }); // Total Bag + colWidths.push({ wch: 6 }); // Total Rate + colWidths.push({ wch: 12 }); // Total Amount + colWidths.push({ wch: 10 }); // Standard Bag + colWidths.push({ wch: 6 }); // Standard Rate + colWidths.push({ wch: 12 }); // Standard Total + + ws['!cols'] = colWidths; + + // Merge cells for DATE headers + const merges: XLSX.Range[] = []; + + // Merge DATE header cells for each date (row 1) + let colIndex = 2; // Start after row number and WORK columns + sortedDates.forEach(() => { + merges.push({ + s: { r: 0, c: colIndex }, + e: { r: 0, c: colIndex + 2 } + }); + colIndex += 3; + }); + + // Merge Total header + merges.push({ + s: { r: 0, c: colIndex + 1 }, + e: { r: 0, c: colIndex + 3 } + }); + + // Merge "Total-As per Standered" header + merges.push({ + s: { r: 0, c: colIndex + 4 }, + e: { r: 0, c: colIndex + 6 } + }); + + // Merge department header row + merges.push({ + s: { r: 2, c: 1 }, + e: { r: 2, c: colIndex + 6 } + }); + + ws['!merges'] = merges; + + // Add worksheet to workbook + XLSX.utils.book_append_sheet(wb, ws, 'Work Report'); + + // Generate filename + const filename = `work_report_${departmentName.toLowerCase().replace(/\s+/g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`; + + // Write and download + XLSX.writeFile(wb, filename); +}; + +/** + * Simple export for basic allocation data + */ +export const exportAllocationsToXLSX = ( + allocations: AllocationData[], + filename?: string +) => { + if (allocations.length === 0) { + alert('No data to export'); + return; + } + + // Transform data for export + const exportData = allocations.map((a, index) => ({ + 'S.No': index + 1, + 'Employee Name': 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() : '', + 'Completion Date': a.completion_date ? new Date(a.completion_date).toLocaleDateString() : '', + 'Rate': a.rate || 0, + 'Units': a.units || '', + 'Total Amount': a.total_amount || a.rate || 0, + 'Status': a.status || '', + })); + + // Create workbook + const wb = XLSX.utils.book_new(); + const ws = XLSX.utils.json_to_sheet(exportData); + + // Set column widths + ws['!cols'] = [ + { wch: 6 }, // S.No + { wch: 25 }, // Employee Name + { wch: 20 }, // Contractor + { wch: 15 }, // Department + { wch: 20 }, // Sub-Department + { wch: 15 }, // Activity + { wch: 12 }, // Assigned Date + { wch: 14 }, // Completion Date + { wch: 10 }, // Rate + { wch: 8 }, // Units + { wch: 12 }, // Total Amount + { wch: 10 }, // Status + ]; + + XLSX.utils.book_append_sheet(wb, ws, 'Allocations'); + + // Generate filename + const outputFilename = filename || `allocations_${new Date().toISOString().split('T')[0]}.xlsx`; + + // Write and download + XLSX.writeFile(wb, outputFilename); +};