// public/dashboard/js/charts.js // Chart.js helpers: create & update charts without recreating canvases. let dailyLineChart = null; let categoryChart = null; // Export chart instances untuk akses dari dashboard.js export function getDailyChart() { return dailyLineChart; } export function getCategoryChart() { return categoryChart; } export function initDailyChart(ctx) { if (dailyLineChart) return dailyLineChart; dailyLineChart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [ { label: 'Jumlah', data: [], borderColor: '#111827', backgroundColor: 'rgba(17, 24, 39, 0.06)', borderWidth: 2, tension: 0.35, fill: true, pointRadius: 2.5, pointBackgroundColor: '#111827' }, { label: 'Pendapatan', data: [], borderColor: '#6b7280', backgroundColor: 'rgba(156, 163, 175, 0.08)', borderWidth: 2, tension: 0.35, fill: true, pointRadius: 2.5, pointBackgroundColor: '#6b7280', yAxisID: 'y1' } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: { y: { beginAtZero: true, grid: { color: '#e5e7eb' }, ticks: { font: { size: 11 } } }, y1: { beginAtZero: true, position: 'right', grid: { drawOnChartArea: false }, ticks: { font: { size: 11 }, callback: value => 'Rp ' + new Intl.NumberFormat('id-ID').format(value) } }, x: { grid: { display: false }, ticks: { font: { size: 11 } } } }, plugins: { legend: { position: 'bottom', labels: { boxWidth: 14, boxHeight: 4 } } } } }); return dailyLineChart; } export function updateDailyChart({ labels, counts, amounts }) { if (!dailyLineChart) { console.warn('[Charts] Daily chart belum di-init, skip update'); // Try to init if canvas exists const canvas = document.getElementById('daily-chart'); if (canvas) { console.log('[Charts] Attempting to init daily chart from update function...'); initDailyChart(canvas.getContext('2d')); } else { console.error('[Charts] Daily chart canvas not found!'); return; } } if (!dailyLineChart) { console.error('[Charts] Failed to init daily chart'); return; } dailyLineChart.data.labels = labels || []; dailyLineChart.data.datasets[0].data = counts || []; dailyLineChart.data.datasets[1].data = amounts || []; dailyLineChart.update(); console.log('[Charts] Daily chart updated:', { labelsCount: labels?.length, countsCount: counts?.length, amountsCount: amounts?.length }); } export function initCategoryChart(ctx) { if (categoryChart) return categoryChart; categoryChart = new Chart(ctx, { type: 'doughnut', data: { labels: [], datasets: [ { label: 'Per Kategori', data: [], backgroundColor: ['#111827', '#4b5563', '#9ca3af'], borderWidth: 0 } ] }, options: { responsive: true, maintainAspectRatio: false, cutout: '60%', plugins: { legend: { position: 'bottom', labels: { boxWidth: 14, boxHeight: 6 } } } } }); return categoryChart; } export function updateCategoryChart({ labels, values }) { if (!categoryChart) { console.warn('[Charts] Category chart belum di-init, skip update'); // Try to init if canvas exists const canvas = document.getElementById('category-chart'); if (canvas) { console.log('[Charts] Attempting to init category chart from update function...'); initCategoryChart(canvas.getContext('2d')); } else { console.error('[Charts] Category chart canvas not found!'); return; } } if (!categoryChart) { console.error('[Charts] Failed to init category chart'); return; } // Pastikan labels dan values tidak kosong const finalLabels = labels && labels.length > 0 ? labels : ['Orang', 'Motor', 'Mobil']; const finalValues = values && values.length > 0 ? values : [0, 0, 0]; // Pastikan length sama const minLength = Math.min(finalLabels.length, finalValues.length); const safeLabels = finalLabels.slice(0, minLength); const safeValues = finalValues.slice(0, minLength); categoryChart.data.labels = safeLabels; categoryChart.data.datasets[0].data = safeValues; // Update chart dengan mode 'none' untuk animasi halus categoryChart.update('none'); console.log('[Charts] Category chart updated:', { labels: safeLabels, values: safeValues, total: safeValues.reduce((a, b) => a + b, 0) }); }