Average Level
${avgLevel.toFixed(1)} dB
Peak Level
${peakLevel} dB
Most Common Source
${mostCommonSource}
`;
};
const renderDataTable = () => {
dataTableBody.innerHTML = '';
const sortedReadings = [...state.readings].sort((a, b) => new Date(b.datetime) - new Date(a.datetime));
sortedReadings.forEach(r => {
const dt = new Date(r.datetime);
const formattedDt = `${dt.toLocaleDateString()} ${dt.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}`;
const row = `
| ${formattedDt} |
${r.location} |
${r.source} |
${r.level} dB |
`;
dataTableBody.innerHTML += row;
});
};
const renderCharts = () => {
// Noise Trend Chart (Line)
const sortedByTime = [...state.readings].sort((a, b) => new Date(a.datetime) - new Date(b.datetime));
if (noiseTrendChart) noiseTrendChart.destroy();
noiseTrendChart = new Chart(noiseTrendCanvas.getContext('2d'), {
type: 'line',
data: {
datasets: [{
label: 'Noise Level (dB)',
data: sortedByTime.map(r => ({ x: r.datetime, y: r.level })),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.1
}]
},
options: {
responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } },
scales: { x: { type: 'time', time: { tooltipFormat: 'PPpp' } }, y: { beginAtZero: false, title: { display: true, text: 'Noise Level (dB)' } } }
}
});
// Source Chart (Bar)
const avgLevelBySource = state.readings.reduce((acc, r) => {
if (!acc[r.source]) acc[r.source] = { total: 0, count: 0 };
acc[r.source].total += r.level;
acc[r.source].count++;
return acc;
}, {});
const sourceLabels = Object.keys(avgLevelBySource);
const sourceData = sourceLabels.map(label => avgLevelBySource[label].total / avgLevelBySource[label].count);
if (sourceChart) sourceChart.destroy();
sourceChart = new Chart(sourceCanvas.getContext('2d'), {
type: 'bar',
data: {
labels: sourceLabels,
datasets: [{
label: 'Average dB',
data: sourceData,
backgroundColor: ['#ef4444', '#f97316', '#eab308', '#84cc16', '#22c55e', '#10b981', '#06b6d4'],
}]
},
options: {
indexAxis: 'y',
responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } },
scales: { x: { title: { display: true, text: 'Average Noise Level (dB)' } } }
}
});
};
const renderConfigRows = () => {
configRowsContainer.innerHTML = '';
state.readings.forEach(r => {
const row = `
`;
configRowsContainer.innerHTML += row;
});
addConfigEventListeners();
};
// --- EVENT HANDLERS ---
const handleConfigChange = (e) => {
const id = parseInt(e.target.dataset.id);
const field = e.target.dataset.field;
const value = (field === 'level') ? parseInt(e.target.value, 10) : e.target.value;
const reading = state.readings.find(r => r.id === id);
if (reading) reading[field] = value;
renderAll();
};
const handleAddReading = () => {
const newId = state.readings.length > 0 ? Math.max(...state.readings.map(r => r.id)) + 1 : 1;
const now = new Date();
now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); // Adjust for local time input
const nowISO = now.toISOString().slice(0, 16);
state.readings.push({ id: newId, datetime: nowISO, location: "New Location", source: "Unknown", level: 50 });
renderAll();
};
const handleRemoveReading = (e) => {
const id = parseInt(e.target.dataset.id);
state.readings = state.readings.filter(r => r.id !== id);
renderAll();
};
const addConfigEventListeners = () => {
document.querySelectorAll('.config-input').forEach(input => input.addEventListener('change', handleConfigChange));
document.querySelectorAll('.remove-reading-btn').forEach(button => button.addEventListener('click', handleRemoveReading));
};
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('Noise-Pollution-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 (kpiCardsContainer && addReadingBtn && downloadPdfBtn) {
addReadingBtn.addEventListener('click', handleAddReading);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
renderAll();
updateTabButtons();
} else {
console.error("Essential dashboard elements could not be found.");
}
});