Overtime Dashboard

Overtime Dashboard

Analyze employee overtime hours, costs, and trends.

This Month's Overview

Overtime Hours by Day

Overtime Hours by Department

Recent Overtime Logs

Date Employee Department Hours Cost

Enter Overtime Logs

${avgHoursPerEmployee.toFixed(1)}

Top Department

${topDepartment}

`; }; const renderDataTable = () => { dataTableBody.innerHTML = ''; const sortedLogs = [...state.logs].sort((a, b) => new Date(b.date) - new Date(a.date)); sortedLogs.forEach(log => { const cost = log.hours * log.rate * 1.5; const row = ` ${log.date} ${log.employee} ${log.department} ${log.hours.toFixed(1)} ${formatCurrency(cost)} `; dataTableBody.innerHTML += row; }); }; const renderCharts = () => { // Overtime Trend Chart const hoursByDate = state.logs.reduce((acc, log) => { acc[log.date] = (acc[log.date] || 0) + log.hours; return acc; }, {}); const sortedDates = Object.keys(hoursByDate).sort((a, b) => new Date(a) - new Date(b)); if (trendChart) trendChart.destroy(); trendChart = new Chart(trendCanvas.getContext('2d'), { type: 'bar', data: { labels: sortedDates, datasets: [{ label: 'Overtime Hours', data: sortedDates.map(date => hoursByDate[date]), backgroundColor: 'rgba(217, 70, 239, 0.7)', }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { type: 'time', time: { unit: 'day' } }, y: { beginAtZero: true, title: { display: true, text: 'Hours' } } } } }); // Department Chart const hoursByDept = state.logs.reduce((acc, log) => { acc[log.department] = (acc[log.department] || 0) + log.hours; return acc; }, {}); const deptLabels = Object.keys(hoursByDept); const deptData = deptLabels.map(label => hoursByDept[label]); if (departmentChart) departmentChart.destroy(); departmentChart = new Chart(departmentCanvas.getContext('2d'), { type: 'doughnut', data: { labels: deptLabels, datasets: [{ data: deptData, backgroundColor: ['#a855f7', '#ec4899', '#f472b6', '#c084fc'] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } } } }); }; const renderConfigRows = () => { configRowsContainer.innerHTML = ''; const sortedLogs = [...state.logs].sort((a, b) => new Date(b.date) - new Date(a.date)); sortedLogs.forEach(log => { configRowsContainer.innerHTML += `
`.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"'); }); addConfigEventListeners(); }; // --- EVENT HANDLERS --- const handleConfigChange = (e) => { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; const value = (e.target.type === 'number') ? parseFloat(e.target.value) || 0 : e.target.value; const log = state.logs.find(l => l.id === id); if (log) log[field] = value; renderAll(); }; const handleAddLog = () => { const newId = state.logs.length > 0 ? Math.max(...state.logs.map(l => l.id)) + 1 : 1; const today = new Date().toISOString().split('T')[0]; state.logs.push({ id: newId, date: today, employee: "New Employee", department: "Unassigned", hours: 0, rate: 20 }); renderAll(); }; const handleRemoveLog = (e) => { const id = parseInt(e.target.dataset.id); state.logs = state.logs.filter(l => l.id !== id); renderAll(); }; const addConfigEventListeners = () => { document.querySelectorAll('.config-input').forEach(input => input.addEventListener('change', handleConfigChange)); document.querySelectorAll('.remove-btn').forEach(button => button.addEventListener('click', handleRemoveLog)); }; const handleDownloadPdf = () => { const { jsPDF } = window.jspdf; const pdfContent = document.getElementById('pdf-content'); document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'hidden'); Chart.defaults.animation = false; html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => { document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'visible'); Chart.defaults.animation = true; const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const imgWidth = pdfWidth - 20; const imgHeight = canvas.height * imgWidth / canvas.width; pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight); pdf.save('Overtime-Dashboard.pdf'); }); }; // --- TABBING LOGIC --- let currentTabIndex = 0; const updateTabButtons = () => { prevTabBtn.disabled = currentTabIndex === 0; nextTabBtn.disabled = currentTabIndex === tabContents.length - 1; }; const switchTab = (index) => { tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); tabButtons[index].classList.add('active'); tabContents[index].classList.add('active'); currentTabIndex = index; updateTabButtons(); }; tabButtons.forEach((button, index) => button.addEventListener('click', () => switchTab(index))); prevTabBtn.addEventListener('click', () => { if (currentTabIndex > 0) switchTab(currentTabIndex - 1); }); nextTabBtn.addEventListener('click', () => { if (currentTabIndex < tabContents.length - 1) switchTab(currentTabIndex + 1); }); // --- INITIALIZATION --- if (kpiContainer && addLogBtn && downloadPdfBtn) { addLogBtn.addEventListener('click', handleAddLog); downloadPdfBtn.addEventListener('click', handleDownloadPdf); renderAll(); updateTabButtons(); } else { console.error("Essential dashboard elements could not be found."); } });
Scroll to Top