// 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;