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.");
}
});