`).join('');
}
function renderPayrollHistory() {
if (payrollRuns.length === 0) {
payrollHistoryContainer.innerHTML = '
No payroll has been processed yet.
';
} else {
payrollHistoryContainer.innerHTML = payrollRuns.slice().reverse().map(run => `
Payroll for ${run.period}
Total Cost: $${run.totalCost.toFixed(2)} for ${run.employeeCount} employees
`).join('');
}
}
// --- PAYROLL LOGIC ---
function processPayroll() {
const month = payrollMonthSelect.options[payrollMonthSelect.selectedIndex].text;
const year = payrollYearInput.value;
const period = `${month} ${year}`;
if (payrollRuns.some(run => run.period === period)) {
showModal('Duplicate Payroll', `Payroll for ${period} has already been processed.`);
return;
}
const results = employees.map(emp => {
const grossSalary = emp.basicSalary + emp.allowances;
const netSalary = grossSalary - emp.deductions;
return { ...emp, grossSalary, netSalary };
});
payrollResultsTable.innerHTML = results.map(res => `
| ${res.name} |
$${res.grossSalary.toFixed(2)} |
$${res.deductions.toFixed(2)} |
$${res.netSalary.toFixed(2)} |
|
`).join('');
const totalCost = results.reduce((sum, res) => sum + res.grossSalary, 0);
const newRun = { id: Date.now().toString(), period, totalCost, employeeCount: employees.length, results };
payrollRuns.push(newRun);
updateAll();
showModal('Success', `Payroll for ${period} processed successfully.`);
}
// --- PDF GENERATION ---
function generateSlipPDF(employeeId, period) {
const employee = employees.find(e => e.id === employeeId);
if (!employee) return;
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const grossSalary = employee.basicSalary + employee.allowances;
const netSalary = grossSalary - employee.deductions;
doc.setFontSize(18);
doc.text('Salary Slip', 105, 20, { align: 'center' });
doc.setFontSize(12);
doc.text(`For the period of ${period}`, 105, 28, { align: 'center' });
doc.autoTable({
startY: 40,
head: [['Employee Details', '']],
body: [
['Employee Name', employee.name],
['Position', employee.position],
],
theme: 'plain',
styles: { fontStyle: 'bold' },
});
doc.autoTable({
startY: doc.lastAutoTable.finalY + 10,
head: [['Earnings', 'Amount ($)']],
body: [
['Basic Salary', employee.basicSalary.toFixed(2)],
['Allowances', employee.allowances.toFixed(2)],
],
foot: [['Gross Salary', grossSalary.toFixed(2)]],
theme: 'striped',
headStyles: { fillColor: [22, 163, 74] },
footStyles: { fontStyle: 'bold' }
});
doc.autoTable({
startY: doc.lastAutoTable.finalY + 10,
head: [['Deductions', 'Amount ($)']],
body: [['Total Deductions', employee.deductions.toFixed(2)]],
foot: [['Total Deductions', employee.deductions.toFixed(2)]],
theme: 'striped',
headStyles: { fillColor: [220, 38, 38] },
footStyles: { fontStyle: 'bold' }
});
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text(`Net Salary: $${netSalary.toFixed(2)}`, 14, doc.lastAutoTable.finalY + 20);
doc.save(`Salary_Slip_${employee.name.replace(' ','_')}_${period}.pdf`);
}
function downloadSummaryPDF(runId) {
const run = payrollRuns.find(r => r.id === runId);
if (!run) return;
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(18);
doc.text(`Payroll Summary: ${run.period}`, 14, 22);
doc.setFontSize(11);
doc.text(`Total Cost: $${run.totalCost.toFixed(2)} | Employees: ${run.employeeCount}`, 14, 30);
const head = [['Employee Name', 'Position', 'Gross Salary', 'Deductions', 'Net Salary']];
const body = run.results.map(r => [r.name, r.position, `$${r.grossSalary.toFixed(2)}`, `$${r.deductions.toFixed(2)}`, `$${r.netSalary.toFixed(2)}`]);
doc.autoTable({ head, body, startY: 40 });
doc.save(`Payroll_Summary_${run.period.replace(' ','_')}.pdf`);
}
// --- EVENT HANDLERS & HELPERS ---
employeeForm.addEventListener('submit', (e) => {
e.preventDefault();
const id = parseInt(editEmployeeIdInput.value);
const empData = {
name: document.getElementById('employee-name').value,
position: document.getElementById('employee-position').value,
basicSalary: parseFloat(document.getElementById('basic-salary').value),
allowances: parseFloat(document.getElementById('allowances').value),
deductions: parseFloat(document.getElementById('deductions').value),
};
if (id) {
const index = employees.findIndex(emp => emp.id === id);
if (index !== -1) employees[index] = { ...employees[index], ...empData };
} else {
empData.id = Date.now();
employees.push(empData);
}
resetEmployeeForm();
updateAll();
showModal('Success', `Employee data has been ${id ? 'updated' : 'saved'}.`);
});
function resetEmployeeForm() {
employeeForm.reset();
editEmployeeIdInput.value = '';
employeeFormTitle.textContent = 'Add New Employee';
employeeSubmitBtn.textContent = 'Add Employee';
employeeCancelEditBtn.classList.add('hidden');
}
function showModal(title, message) {
modalTitle.textContent = title;
modalMessage.textContent = message;
modal.classList.add('visible');
}
modalCloseBtn.addEventListener('click', () => modal.classList.remove('visible'));
// --- GLOBAL API ---
window.app = {
editEmployee: (id) => {
const emp = employees.find(e => e.id === id);
if (emp) {
setActiveTab('employees');
editEmployeeIdInput.value = emp.id;
document.getElementById('employee-name').value = emp.name;
document.getElementById('employee-position').value = emp.position;
document.getElementById('basic-salary').value = emp.basicSalary;
document.getElementById('allowances').value = emp.allowances;
document.getElementById('deductions').value = emp.deductions;
employeeFormTitle.textContent = 'Edit Employee';
employeeSubmitBtn.textContent = 'Update Employee';
employeeCancelEditBtn.classList.remove('hidden');
}
},
deleteEmployee: (id) => {
employees = employees.filter(emp => emp.id !== id);
updateAll();
},
generateSlipPDF,
downloadSummaryPDF
};
// --- TAB NAVIGATION ---
function setActiveTab(tabId) {
currentTab = tabId;
tabButtons.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabId));
tabPanes.forEach(pane => pane.style.display = pane.id === tabId ? 'block' : 'none');
const currentIndex = tabs.indexOf(currentTab);
prevTabBtn.disabled = currentIndex === 0;
nextTabBtn.disabled = currentIndex === tabs.length - 1;
}
tabButtons.forEach(button => button.addEventListener('click', () => setActiveTab(button.dataset.tab)));
prevTabBtn.addEventListener('click', () => {
const newIndex = tabs.indexOf(currentTab) - 1;
if (newIndex >= 0) setActiveTab(tabs[newIndex]);
});
nextTabBtn.addEventListener('click', () => {
const newIndex = tabs.indexOf(currentTab) + 1;
if (newIndex < tabs.length) setActiveTab(tabs[newIndex]);
});
employeeCancelEditBtn.addEventListener('click', resetEmployeeForm);
processPayrollBtn.addEventListener('click', processPayroll);
// --- START ---
initialize();
});