document.addEventListener('DOMContentLoaded', function () { const dataEl = document.getElementById('sodino-dashboard-data'); if (!dataEl) { return; } let dashboardData; try { dashboardData = JSON.parse(dataEl.textContent || '{}'); } catch (e) { return; } function toNumbers(values) { return (values || []).map(function (value) { const number = Number(value); return Number.isFinite(number) ? number : 0; }); } function fitCanvas(canvas) { const ratio = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = Math.max(1, Math.floor(rect.width * ratio)); canvas.height = Math.max(1, Math.floor(rect.height * ratio)); const ctx = canvas.getContext('2d'); ctx.setTransform(ratio, 0, 0, ratio, 0, 0); return { ctx, width: rect.width, height: rect.height }; } function drawGrid(ctx, area, lines) { ctx.strokeStyle = 'rgba(148, 163, 184, 0.22)'; ctx.lineWidth = 1; for (let i = 0; i <= lines; i++) { const y = area.top + (area.height / lines) * i; ctx.beginPath(); ctx.moveTo(area.left, y); ctx.lineTo(area.left + area.width, y); ctx.stroke(); } } function drawLineChart(canvas, series) { if (!canvas) { return; } const fitted = fitCanvas(canvas); const ctx = fitted.ctx; const width = fitted.width; const height = fitted.height; const area = { left: 18, top: 18, width: width - 36, height: height - 42 }; const allValues = series.reduce(function (values, item) { return values.concat(item.values); }, []); const max = Math.max(1, ...allValues); ctx.clearRect(0, 0, width, height); drawGrid(ctx, area, 4); series.forEach(function (item) { const values = item.values; if (!values.length) { return; } ctx.strokeStyle = item.color; ctx.fillStyle = item.fill; ctx.lineWidth = 2.5; ctx.beginPath(); values.forEach(function (value, index) { const x = area.left + (values.length === 1 ? area.width / 2 : (area.width / (values.length - 1)) * index); const y = area.top + area.height - ((value / max) * area.height); if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); const lastX = area.left + area.width; ctx.lineTo(lastX, area.top + area.height); ctx.lineTo(area.left, area.top + area.height); ctx.closePath(); ctx.fill(); }); } function drawVerticalBars(canvas, labels, values, colors) { if (!canvas) { return; } const fitted = fitCanvas(canvas); const ctx = fitted.ctx; const width = fitted.width; const height = fitted.height; const area = { left: 24, top: 18, width: width - 48, height: height - 52 }; const max = Math.max(1, ...values); const gap = 18; const barWidth = Math.max(18, (area.width - gap * (values.length - 1)) / Math.max(1, values.length)); ctx.clearRect(0, 0, width, height); drawGrid(ctx, area, 4); ctx.font = '12px system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'; ctx.textAlign = 'center'; ctx.fillStyle = '#475569'; values.forEach(function (value, index) { const x = area.left + index * (barWidth + gap); const barHeight = (value / max) * area.height; const y = area.top + area.height - barHeight; ctx.fillStyle = colors[index % colors.length]; roundRect(ctx, x, y, barWidth, barHeight, 8); ctx.fill(); ctx.fillStyle = '#475569'; ctx.fillText(labels[index] || '', x + barWidth / 2, height - 16); }); } function drawHorizontalBars(canvas, labels, revenue, discount) { if (!canvas) { return; } const fitted = fitCanvas(canvas); const ctx = fitted.ctx; const width = fitted.width; const height = fitted.height; const area = { left: 18, top: 18, width: width - 36, height: height - 36 }; const rows = Math.max(1, labels.length); const max = Math.max(1, ...revenue, ...discount); const rowHeight = area.height / rows; ctx.clearRect(0, 0, width, height); ctx.font = '12px system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'; ctx.textAlign = 'right'; labels.forEach(function (label, index) { const y = area.top + rowHeight * index + 10; const labelWidth = Math.min(110, area.width * 0.35); const chartLeft = area.left; const chartWidth = area.width - labelWidth - 12; const revenueWidth = (Number(revenue[index] || 0) / max) * chartWidth; const discountWidth = (Number(discount[index] || 0) / max) * chartWidth; ctx.fillStyle = '#475569'; ctx.fillText(String(label || '').slice(0, 18), width - 18, y + 14); ctx.fillStyle = '#0ea5e9'; roundRect(ctx, chartLeft, y, revenueWidth, 8, 6); ctx.fill(); ctx.fillStyle = '#f59e0b'; roundRect(ctx, chartLeft, y + 13, discountWidth, 8, 6); ctx.fill(); }); } function roundRect(ctx, x, y, width, height, radius) { const r = Math.min(radius, Math.abs(width) / 2, Math.abs(height) / 2); ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + width - r, y); ctx.quadraticCurveTo(x + width, y, x + width, y + r); ctx.lineTo(x + width, y + height - r); ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height); ctx.lineTo(x + r, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); } function render() { const sales = dashboardData.salesChart || {}; drawLineChart(document.getElementById('sodinoSalesChart'), [ { values: toNumbers(sales.after), color: '#0ea5e9', fill: 'rgba(14, 165, 233, 0.12)' }, { values: toNumbers(sales.before), color: '#ef4444', fill: 'rgba(239, 68, 68, 0.10)' }, ]); const summary = dashboardData.summary || {}; drawVerticalBars( document.getElementById('sodinoDiscountChart'), [dashboardData.translations.totalDiscount, dashboardData.translations.totalRevenue], toNumbers([summary.total_discount, summary.total_revenue]), ['#f59e0b', '#0ea5e9'] ); const rules = dashboardData.rulePerformance || {}; drawHorizontalBars( document.getElementById('sodinoRuleChart'), rules.names || [], toNumbers(rules.revenue), toNumbers(rules.discount) ); } render(); window.addEventListener('resize', render); });