(Fix): Fixed the excel reporting system, fixed alot of hardcoded dropdown values in activities, auto selection of departments is also implemented

This commit is contained in:
2025-12-18 10:11:22 +00:00
parent 2a38cf372a
commit 01400ad4e1
10 changed files with 568 additions and 117 deletions

View File

@@ -273,12 +273,10 @@ export const ActivitiesPage: React.FC = () => {
{/* Sub-Departments List */}
<Table>
<TableHeader>
<TableRow>
<TableHead>Sub-Department Name</TableHead>
<TableHead>Activities Count</TableHead>
<TableHead>Created At</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
<TableHead>Sub-Department Name</TableHead>
<TableHead>Activities Count</TableHead>
<TableHead>Created At</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableHeader>
<TableBody>
{subDepartments.length === 0 ? (
@@ -358,13 +356,11 @@ export const ActivitiesPage: React.FC = () => {
{/* Activities List */}
<Table>
<TableHeader>
<TableRow>
<TableHead>Activity Name</TableHead>
<TableHead>Sub-Department</TableHead>
<TableHead>Unit of Measurement</TableHead>
<TableHead>Created At</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
<TableHead>Activity Name</TableHead>
<TableHead>Sub-Department</TableHead>
<TableHead>Unit of Measurement</TableHead>
<TableHead>Created At</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableHeader>
<TableBody>
{activities.length === 0 ? (

View File

@@ -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<any[]>([]);
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<HTMLInputElement | HTMLSelectElement>) => {
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 = () => {
<TableCell>{rate.sub_department_name || '-'}</TableCell>
<TableCell>
<span className={`px-2 py-1 rounded text-xs font-medium ${
rate.activity === 'Loading' || rate.activity === 'Unloading'
rate.unit_of_measurement === 'Per Bag'
? 'bg-blue-100 text-blue-700'
: 'bg-gray-100 text-gray-700'
}`}>
@@ -248,7 +266,7 @@ export const RatesPage: React.FC = () => {
</TableCell>
<TableCell>
<span className="text-xs text-gray-500">
{rate.activity === 'Loading' || rate.activity === 'Unloading'
{rate.unit_of_measurement === 'Per Bag'
? 'Per Unit'
: 'Flat Rate'}
</span>
@@ -302,8 +320,8 @@ export const RatesPage: React.FC = () => {
<div className="p-4 bg-blue-50 border border-blue-200 rounded-md">
<h4 className="font-medium text-blue-800 mb-2">Rate Calculation Info</h4>
<ul className="text-sm text-blue-700 space-y-1">
<li><strong>Loading/Unloading:</strong> Total = Units × Rate per Unit</li>
<li><strong>Standard/Other:</strong> Total = Flat Rate (no unit calculation)</li>
<li><strong>Per Bag Activities:</strong> Total = Units × Rate per Unit</li>
<li><strong>Fixed Rate Activities:</strong> Total = Flat Rate (no unit calculation)</li>
</ul>
</div>
@@ -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'})`
}))
]}
/>
<Input
label={formData.activity === 'Loading' || formData.activity === 'Unloading'
? "Rate per Unit (₹)"
: "Standard Rate (₹)"}
label={(() => {
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}

View File

@@ -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<HTMLInputElement | HTMLSelectElement>) => {
@@ -169,10 +131,16 @@ export const ReportingPage: React.FC = () => {
<FileSpreadsheet className="text-green-600" size={24} />
<h2 className="text-xl font-semibold text-gray-800">Work Allocation Reports</h2>
</div>
<Button onClick={exportToExcel} disabled={filteredAllocations.length === 0}>
<Download size={16} className="mr-2" />
Export to Excel
</Button>
<div className="flex gap-2">
<Button onClick={exportFormattedReport} disabled={filteredAllocations.length === 0}>
<Download size={16} className="mr-2" />
Export Report (XLSX)
</Button>
<Button variant="outline" onClick={exportSimpleList} disabled={filteredAllocations.length === 0}>
<Download size={16} className="mr-2" />
Export List
</Button>
</div>
</div>
</div>

View File

@@ -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<number | null>(null);
@@ -98,7 +100,12 @@ export const StandardRatesPage: React.FC = () => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
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 = () => {
<TableCell className="font-medium">{rate.sub_department_name || 'All'}</TableCell>
<TableCell>
<span className={`px-2 py-1 rounded text-xs font-medium ${
rate.activity === 'Loading' || rate.activity === 'Unloading'
rate.unit_of_measurement === 'Per Bag'
? 'bg-blue-100 text-blue-700'
: 'bg-gray-100 text-gray-700'
}`}>
@@ -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'})`
}))
]}
/>
<Input
label={formData.activity === 'Loading' || formData.activity === 'Unloading'
? "Rate per Unit (₹)"
: "Standard Rate (₹)"}
label={(() => {
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 = () => {
<TableCell className="font-medium">{comp.sub_department_name || 'All'}</TableCell>
<TableCell>
<span className={`px-2 py-1 rounded text-xs font-medium ${
comp.activity === 'Loading' || comp.activity === 'Unloading'
comp.unit_of_measurement === 'Per Bag'
? 'bg-blue-100 text-blue-700'
: 'bg-gray-100 text-gray-700'
}`}>

View File

@@ -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<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
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'})`
}))
]}
/>
<Input

306
src/utils/excelExport.ts Normal file
View File

@@ -0,0 +1,306 @@
import * as XLSX from 'xlsx';
interface AllocationData {
id: number;
employee_name: string;
contractor_name: string;
department_name: string;
sub_department_name: string;
activity: string;
assigned_date: string;
completion_date: string;
rate: number;
units: number;
total_amount: number;
status: string;
}
interface WorkReportData {
work: string;
dates: {
[date: string]: {
bag: number;
rate: number;
total: number;
};
};
totalBag: number;
totalAmount: number;
standardBag?: number;
standardRate?: number;
standardTotal?: number;
}
/**
* Export work allocation report to XLSX with proper formatting
* Format matches the provided image with:
* - DATE header row spanning columns for each date
* - Bag, Rate, Total sub-headers for each date
* - Department section headers with yellow background
* - Work activities listed with data
* - Sub Total row at bottom
* - Total-As per Standard columns
*/
export const exportWorkReportToXLSX = (
allocations: AllocationData[],
departmentName: string,
_dateRange: { startDate: string; endDate: string }
) => {
// Group allocations by work (activity + sub_department) and date
const workDataMap = new Map<string, WorkReportData>();
const allDates = new Set<string>();
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);
};