Files
cypher-hr/pdf_reports.js
2026-04-25 15:15:49 +05:00

135 lines
4.5 KiB
JavaScript

/* ═══ CYPHER-HR PDF Reports Module ═══ */
const PdfReports = {
export() {
if (!Reports.lastData || Reports.lastData.length === 0) {
App.toast('Generate a report first to export', 'error');
return;
}
try {
const type = document.getElementById('reportType').value;
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// Header
doc.setFillColor(21, 112, 239); // Primary blue
doc.rect(0, 0, 210, 20, 'F');
doc.setTextColor(255, 255, 255);
doc.setFontSize(16);
doc.text('CYPHER-HR', 14, 13);
doc.setTextColor(50, 50, 50);
doc.setFontSize(14);
doc.text(type === 'detailed' ? 'Detailed Leave Report' : 'Summary Leave Report', 14, 30);
doc.setFontSize(10);
doc.setTextColor(100, 100, 100);
doc.text(`Generated on: ${App.formatDate(new Date())}`, 14, 36);
if (type === 'detailed') {
this.generateDetailed(doc, Reports.lastData);
} else {
this.generateSummary(doc, Reports.lastData);
}
} catch (err) {
console.error(err);
App.toast('Failed to generate PDF. Make sure libraries are loaded.', 'error');
}
},
generateDetailed(doc, data) {
const columns = [
{ header: 'Employee', dataKey: 'employee' },
{ header: 'Type', dataKey: 'type' },
{ header: 'Dates', dataKey: 'dates' },
{ header: 'Status', dataKey: 'status' },
{ header: 'Department', dataKey: 'department' }
];
const rows = data.map(r => ({
employee: `${r.first_name} ${r.last_name}`,
type: r.leave_type,
dates: `${App.formatDate(r.start_date)} to ${App.formatDate(r.end_date)}`,
status: r.status.charAt(0).toUpperCase() + r.status.slice(1),
department: r.department || '—'
}));
doc.autoTable({
columns: columns,
body: rows,
startY: 42,
styles: { fontSize: 9, cellPadding: 4 },
headStyles: { fillColor: [21, 112, 239], textColor: 255 },
alternateRowStyles: { fillColor: [245, 247, 250] },
didDrawPage: function (data) {
doc.setFontSize(8);
doc.text(`Page ${doc.internal.getNumberOfPages()}`, data.settings.margin.left, doc.internal.pageSize.height - 10);
}
});
doc.save('detailed_report.pdf');
App.toast('PDF generated successfully');
},
generateSummary(doc, data) {
const months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
// Group data for summary exactly like the UI
const grouped = {};
data.forEach(r => {
const name = `${r.first_name} ${r.last_name}`;
if (!grouped[name]) grouped[name] = {};
const m = months[parseInt(r.month) - 1];
if (!grouped[name][m]) grouped[name][m] = { approved: 0, rejected: 0, pending: 0 };
grouped[name][m][r.status] = (grouped[name][m][r.status] || 0) + parseInt(r.count);
});
let currentY = 42;
Object.entries(grouped).forEach(([name, monthData], index) => {
// Add employee name header
doc.setFontSize(11);
doc.setTextColor(30, 30, 30);
doc.text(name, 14, currentY);
const rows = Object.entries(monthData).map(([m, statuses]) => ({
month: m,
approved: statuses.approved || 0,
rejected: statuses.rejected || 0,
pending: statuses.pending || 0,
total: (statuses.approved || 0) + (statuses.rejected || 0) + (statuses.pending || 0)
}));
doc.autoTable({
columns: [
{ header: 'Month', dataKey: 'month' },
{ header: 'Approved', dataKey: 'approved' },
{ header: 'Rejected', dataKey: 'rejected' },
{ header: 'Pending', dataKey: 'pending' },
{ header: 'Total', dataKey: 'total' }
],
body: rows,
startY: currentY + 4,
margin: { bottom: 20 },
styles: { fontSize: 9, cellPadding: 3 },
headStyles: { fillColor: [240, 242, 245], textColor: [50, 50, 50] },
didDrawPage: function (data) {
doc.setFontSize(8);
doc.text(`Page ${doc.internal.getNumberOfPages()}`, data.settings.margin.left, doc.internal.pageSize.height - 10);
}
});
currentY = doc.lastAutoTable.finalY + 12;
// If close to bottom, add new page
if (currentY > 260 && index < Object.entries(grouped).length - 1) {
doc.addPage();
currentY = 20;
}
});
doc.save('summary_report.pdf');
App.toast('PDF generated successfully');
}
};