Smart Payroll & Salary Slip Generator

Smart Payroll & Salary Slip Generator

Payroll Overview

Total Monthly Cost

$0.00

Total Employees

0

Average Salary

$0.00

Recent Payroll Runs

No payroll has been processed yet.

${emp.name}

${emp.position}

`).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(); });
Scroll to Top