(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:
@@ -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 ? (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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'
|
||||
}`}>
|
||||
|
||||
@@ -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
306
src/utils/excelExport.ts
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user