// Reporting & Analytics Logic let charts = {}; let currentPeriod = '24h'; document.addEventListener('DOMContentLoaded', () => { initializeCharts(); startRealTimeUpdates(); }); function initializeCharts() { // Check if Chart.js is available if (typeof Chart === 'undefined') { console.error('Chart.js not loaded'); return; } // Detection Timeline Chart const detectionCanvas = document.getElementById('detectionChart'); if (!detectionCanvas) { console.error('Detection chart canvas not found'); return; } const detectionCtx = detectionCanvas.getContext('2d'); if (!detectionCtx) { console.error('Could not get 2d context'); return; } charts.detection = new Chart(detectionCtx, { type: 'line', data: { labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '23:59'], datasets: [{ label: 'Détections', data: [12, 8, 45, 89, 67, 123, 45], borderColor: '#06b6d4', backgroundColor: 'rgba(6, 182, 212, 0.1)', tension: 0.4, fill: true }, { label: 'Alertes Confirmées', data: [2, 1, 8, 15, 12, 28, 9], borderColor: '#ef4444', backgroundColor: 'rgba(239, 68, 68, 0.1)', tension: 0.4, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#94a3b8' } } }, scales: { y: { grid: { color: '#334155' }, ticks: { color: '#64748b' } }, x: { grid: { color: '#334155' }, ticks: { color: '#64748b' } } } } }); // Zone Activity Chart const zoneCanvas = document.getElementById('zoneChart'); if (!zoneCanvas) return; const zoneCtx = zoneCanvas.getContext('2d'); if (!zoneCtx) return; charts.zone = new Chart(zoneCtx, { type: 'bar', data: { labels: ['Entrée', 'Rayon A', 'Rayon B', 'Caisse', 'Réserve', 'Sortie'], datasets: [{ label: 'Activité', data: [145, 289, 198, 456, 67, 134], backgroundColor: [ 'rgba(6, 182, 212, 0.8)', 'rgba(34, 211, 238, 0.8)', 'rgba(34, 211, 238, 0.6)', 'rgba(16, 185, 129, 0.8)', 'rgba(245, 158, 11, 0.8)', 'rgba(239, 68, 68, 0.8)' ], borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { grid: { color: '#334155' }, ticks: { color: '#64748b' } }, x: { grid: { display: false }, ticks: { color: '#64748b' } } } } }); // Alert Types Doughnut const alertCanvas = document.getElementById('alertTypeChart'); if (!alertCanvas) return; const alertCtx = alertCanvas.getContext('2d'); if (!alertCtx) return; charts.alertType = new Chart(alertCtx, { type: 'doughnut', data: { labels: ['Démarque', 'Déplacement', 'Non-déclaré', 'Technique'], datasets: [{ data: [45, 25, 20, 10], backgroundColor: [ '#ef4444', '#f59e0b', '#22d3ee', '#64748b' ], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8', padding: 10, font: { size: 11 } } } } } }); } function setPeriod(period, btnElement) { // Validation pour éviter undefinednull dans l'UI if (!period || typeof period !== 'string') { console.error('[Reporting] Période invalide:', period); return; } currentPeriod = period; // Update UI - reset all buttons avec vérification null-safety document.querySelectorAll('.period-btn').forEach(btn => { if (btn && btn.classList) { btn.classList.remove('bg-cyan-600', 'text-white'); btn.classList.add('text-slate-400'); } }); // Style active button if (btnElement && btnElement.classList) { btnElement.classList.remove('text-slate-400'); btnElement.classList.add('bg-cyan-600', 'text-white'); } // Update label const labels = { '24h': 'Dernières 24 heures', '7d': '7 derniers jours', '30d': '30 derniers jours', 'custom': 'Période personnalisée' }; const currentPeriodEl = document.getElementById('currentPeriod'); const safePeriod = String(period || 'période inconnue'); const periodLabel = labels[safePeriod] || safePeriod; if (currentPeriodEl) currentPeriodEl.textContent = String(periodLabel); // Simulate data update updateChartsData(); } function updateChartsData() { // Safety check - ensure charts exist if (!charts || !charts.detection || !charts.zone) return; // Simulate different data based on period const multiplier = currentPeriod === '24h' ? 1 : currentPeriod === '7d' ? 7 : 30; if (charts.detection.data && charts.detection.data.datasets[0]) { charts.detection.data.datasets[0].data = charts.detection.data.datasets[0].data.map(v => Math.floor((v || 0) * multiplier * (0.8 + Math.random() * 0.4)) ); charts.detection.update(); } if (charts.zone.data && charts.zone.data.datasets[0]) { charts.zone.data.datasets[0].data = charts.zone.data.datasets[0].data.map(v => Math.floor((v || 0) * multiplier * (0.8 + Math.random() * 0.4)) ); charts.zone.update(); } } function refreshData(btnElement) { // Validation défensive if (!btnElement || !btnElement.innerHTML) { console.warn('[Reporting] Bouton invalide pour refresh'); return; } const btn = btnElement; const originalContent = btn.innerHTML || ' Actualiser'; btn.innerHTML = ' Actualisation...'; btn.disabled = true; setTimeout(() => { try { updateChartsData(); updateKPIs(); btn.innerHTML = originalContent; btn.disabled = false; if (typeof feather !== 'undefined' && typeof feather.replace === 'function') { feather.replace(); } } catch (err) { console.error('[Reporting] Refresh error:', err); btn.innerHTML = originalContent; btn.disabled = false; } }, 1000); } function updateKPIs() { // Randomize KPIs slightly with null checks const kpiAlerts = document.getElementById('kpi-alerts'); const kpiAccuracy = document.getElementById('kpi-accuracy'); const kpiTracked = document.getElementById('kpi-tracked'); const kpiResponse = document.getElementById('kpi-response'); if (kpiAlerts) kpiAlerts.textContent = Math.floor(40 + Math.random() * 20); if (kpiAccuracy) kpiAccuracy.textContent = (97 + Math.random() * 2).toFixed(1) + '%'; if (kpiTracked) kpiTracked.textContent = (12000 + Math.floor(Math.random() * 1000)).toLocaleString(); if (kpiResponse) kpiResponse.textContent = (2 + Math.random()).toFixed(1) + 's'; } function exportReport(format) { const timestamp = new Date().toISOString().split('T')[0]; if (format === 'pdf') { alert(`Génération du rapport PDF Sentinel_Report_${timestamp}.pdf...\n( Simulation d'export )`); } else { // Simulate CSV download const csv = 'Date,Article ID,Zone,Type,Confiance\n' + '2024-01-15,ART-001,RAYON,Doute latent,0.89\n' + '2024-01-15,ART-002,SORTIE,Alerte confirmée,0.95\n'; const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `sentinel_data_${timestamp}.csv`; a.click(); } } function startRealTimeUpdates() { // Update KPIs every 30 seconds setInterval(() => { updateKPIs(); }, 30000); // Simulate live chart updates setInterval(() => { if (charts && charts.detection && charts.detection.data && charts.detection.data.datasets[0] && Math.random() > 0.7) { const dataArray = charts.detection.data.datasets[0].data; const lastIndex = dataArray.length - 1; if (lastIndex >= 0) { dataArray[lastIndex] = (dataArray[lastIndex] || 0) + Math.floor(Math.random() * 3); try { charts.detection.update('none'); // Update without animation } catch (e) { console.warn('Chart update failed:', e); } } } }, 5000); } // Expose functions with proper parameter passing window.setPeriod = function(period, btnElement) { setPeriod(period, btnElement); }; window.refreshData = function(btnElement) { refreshData(btnElement); }; window.exportReport = exportReport;