Operational Performance Dashboard

Operational Performance Dashboard

Monitor production uptime, output, quality, and costs.

Overall Performance (Month-to-Date)

Production Output vs. Target

Downtime by Reason (Hours)

Daily Operations Log

Date Uptime Output (Units) Quality Score Maint. Cost

Enter Daily Operational Data

Enter Downtime Incidents

No operational data available.

`; return; } const avgUptime = state.dailyData.reduce((sum, d) => sum + d.uptime, 0) / totalDays; const totalOutput = state.dailyData.reduce((sum, d) => sum + d.output, 0); const avgQuality = state.dailyData.reduce((sum, d) => sum + d.quality, 0) / totalDays; const totalMaintCost = state.dailyData.reduce((sum, d) => sum + d.maintCost, 0); kpiContainer.innerHTML = `

Avg. Uptime

${formatPercent(avgUptime)}

Total Output

${totalOutput.toLocaleString()}

Avg. Quality

${formatPercent(avgQuality)}

Maint. Costs

${formatCurrency(totalMaintCost)}

`; }; const renderDataTable = () => { dataTableBody.innerHTML = ''; const sortedData = [...state.dailyData].sort((a, b) => new Date(b.date) - new Date(a.date)); sortedData.forEach(d => { const row = ` ${d.date} ${formatPercent(d.uptime)} ${d.output.toLocaleString()} ${formatPercent(d.quality)} ${formatCurrency(d.maintCost)} `; dataTableBody.innerHTML += row; }); }; const renderCharts = () => { // Production Chart const sortedDailyData = [...state.dailyData].sort((a, b) => new Date(a.date) - new Date(b.date)); const labels = sortedDailyData.map(d => d.date); if (productionChart) productionChart.destroy(); productionChart = new Chart(productionCanvas.getContext('2d'), { type: 'line', data: { labels: labels, datasets: [ { label: 'Actual Output', data: sortedDailyData.map(d => d.output), borderColor: '#4f46e5', tension: 0.1 }, { label: 'Target Output', data: Array(labels.length).fill(state.productionTarget), borderColor: '#9ca3af', borderDash: [5, 5] } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { type: 'time', time: { unit: 'day' } }, y: { beginAtZero: false } } } }); // Downtime Chart const downtimeByReason = state.downtime.reduce((acc, d) => { acc[d.reason] = (acc[d.reason] || 0) + d.duration; return acc; }, {}); const downtimeLabels = Object.keys(downtimeByReason); const downtimeData = downtimeLabels.map(label => downtimeByReason[label]); if (downtimeChart) downtimeChart.destroy(); downtimeChart = new Chart(downtimeCanvas.getContext('2d'), { type: 'doughnut', data: { labels: downtimeLabels, datasets: [{ data: downtimeData, backgroundColor: ['#f59e0b', '#ef4444', '#84cc16'] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } } } }); }; const renderConfigRows = () => { // Daily Data Config dailyConfigContainer.innerHTML = ''; [...state.dailyData].sort((a,b) => new Date(b.date) - new Date(a.date)).forEach(d => { dailyConfigContainer.innerHTML += `
`.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"'); }); // Downtime Data Config downtimeConfigContainer.innerHTML = ''; [...state.downtime].sort((a,b) => new Date(b.date) - new Date(a.date)).forEach(d => { downtimeConfigContainer.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 type = e.target.dataset.type; const field = e.target.dataset.field; const value = (e.target.type === 'number') ? parseFloat(e.target.value) || 0 : e.target.value; const entry = state[type === 'daily' ? 'dailyData' : 'downtime'].find(item => item.id === id); if (entry) entry[field] = value; renderAll(); }; const handleAddLog = (type) => { const today = new Date().toISOString().split('T')[0]; if (type === 'daily') { const newId = state.dailyData.length > 0 ? Math.max(...state.dailyData.map(d => d.id)) + 1 : 1; state.dailyData.push({ id: newId, date: today, uptime: 0, output: 0, quality: 0, maintCost: 0 }); } else { const newId = state.downtime.length > 0 ? Math.max(...state.downtime.map(d => d.id)) + 1 : 101; state.downtime.push({ id: newId, date: today, reason: "New Incident", duration: 0 }); } renderAll(); }; const handleRemoveLog = (e) => { const id = parseInt(e.target.dataset.id); const type = e.target.dataset.type; state[type === 'daily' ? 'dailyData' : 'downtime'] = state[type === 'daily' ? 'dailyData' : 'downtime'].filter(item => item.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('Operational-Performance-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 && addDailyBtn && addDowntimeBtn && downloadPdfBtn) { addDailyBtn.addEventListener('click', () => handleAddLog('daily')); addDowntimeBtn.addEventListener('click', () => handleAddLog('downtime')); downloadPdfBtn.addEventListener('click', handleDownloadPdf); renderAll(); updateTabButtons(); } else { console.error("Essential dashboard elements could not be found."); } });
Scroll to Top