// Primitives.jsx — KpiCard, LineChart, BarChart, DonutChart, HeatmapChart, Alert
function KpiCard({ label, value, sub, delta, deltaDir, icon, variant, accentColor }) {
const isDanger = variant === 'danger';
const isBrand = variant === 'brand';
const isWarning = variant === 'warning';
const isSuccess = variant === 'success';
const topBorder = isDanger ? '3px solid var(--danger-500)'
: isWarning ? '3px solid var(--warning-500)'
: isSuccess ? '3px solid var(--success-500)'
: isBrand ? 'none' : '1px solid var(--border-subtle)';
const bg = isBrand ? 'var(--brand-gradient)' : '#fff';
const fg = isBrand ? '#fff' : 'var(--fg-1)';
const fgMuted = isBrand ? 'rgba(255,255,255,0.8)' : 'var(--fg-3)';
const deltaUp = deltaDir === 'up';
const deltaDn = deltaDir === 'down';
return (
{label}
{icon && }
{value}
{sub &&
{sub}
}
{delta && (
{deltaUp ? '↑ ' : deltaDn ? '↓ ' : ''}{delta}
)}
);
}
function LineChart({ series, labels, height = 200 }) {
const W = 700, H = height;
const pad = { l: 40, r: 16, t: 12, b: 28 };
const all = series.flatMap(s => s.data);
const maxV = Math.max(...all) * 1.12;
const minV = 0;
const n = series[0].data.length;
const xStep = (W - pad.l - pad.r) / Math.max(n - 1, 1);
const yFn = v => H - pad.b - ((v - minV) / (maxV - minV || 1)) * (H - pad.t - pad.b);
const xFn = i => pad.l + i * xStep;
const gridVals = [0, 0.25, 0.5, 0.75, 1].map(t => Math.round(minV + (maxV - minV) * t));
return (
);
}
function BarChart({ data, height = 200, horizontal = false }) {
if (horizontal) {
const W = 360, H = Math.max(height, data.length * 36 + 20);
const pad = { l: 140, r: 40, t: 8, b: 8 };
const maxV = Math.max(...data.map(d => d.value)) * 1.1;
const barH = 18;
const step = (H - pad.t - pad.b) / data.length;
return (
);
}
const W = 400, H = height;
const pad = { l: 36, r: 12, t: 12, b: 32 };
const maxV = Math.max(...data.map(d => d.value)) * 1.15;
const bw = (W - pad.l - pad.r) / data.length * 0.62;
const sw = (W - pad.l - pad.r) / data.length;
const yFn = v => H - pad.b - (v / (maxV || 1)) * (H - pad.t - pad.b);
return (
);
}
function DonutChart({ segments, size = 160, label, sub }) {
const total = segments.reduce((a, s) => a + s.value, 0);
let angle = -Math.PI / 2;
const cx = size / 2, cy = size / 2;
const R = size * 0.38, r = size * 0.24;
const paths = segments.map(seg => {
const sweep = (seg.value / total) * Math.PI * 2;
const x1 = cx + R * Math.cos(angle);
const y1 = cy + R * Math.sin(angle);
const x2 = cx + R * Math.cos(angle + sweep);
const y2 = cy + R * Math.sin(angle + sweep);
const xi1 = cx + r * Math.cos(angle);
const yi1 = cy + r * Math.sin(angle);
const xi2 = cx + r * Math.cos(angle + sweep);
const yi2 = cy + r * Math.sin(angle + sweep);
const large = sweep > Math.PI ? 1 : 0;
const d = `M ${xi1} ${yi1} L ${x1} ${y1} A ${R} ${R} 0 ${large} 1 ${x2} ${y2} L ${xi2} ${yi2} A ${r} ${r} 0 ${large} 0 ${xi1} ${yi1} Z`;
angle += sweep;
return { d, color: seg.color };
});
return (
);
}
function HeatmapChart({ data, days, hours }) {
// data: array of {day, hour, value}
const cellW = 28, cellH = 22, padL = 52, padT = 28;
const W = padL + hours.length * cellW + 10;
const H = padT + days.length * cellH + 10;
const maxV = Math.max(...data.map(d => d.value));
function cellColor(v) {
if (v === 0) return 'var(--gray-100)';
const t = v / maxV;
if (t < 0.2) return '#e9d5f5';
if (t < 0.4) return '#c084d4';
if (t < 0.6) return '#9a3f90';
if (t < 0.8) return '#7d2574';
return '#4a1545';
}
return (
);
}
function Alert({ level, title, body, action }) {
const cfg = {
danger: { bg: 'var(--danger-50)', border: 'var(--danger-200)', icon: 'alert-triangle', color: 'var(--danger-700)', iconBg: 'var(--danger-100)' },
warning: { bg: 'var(--warning-50)', border: 'var(--warning-200)', icon: 'alert-circle', color: 'var(--warning-700)', iconBg: 'var(--warning-100)' },
info: { bg: 'var(--info-50)', border: 'var(--info-100)', icon: 'info', color: 'var(--info-700)', iconBg: 'var(--info-100)' },
success: { bg: 'var(--success-50)', border: 'var(--success-100)', icon: 'check-circle-2', color: 'var(--success-700)', iconBg: 'var(--success-100)' },
reco: { bg: 'var(--primary-50)', border: 'var(--primary-200)', icon: 'sparkles', color: 'var(--primary-700)', iconBg: 'var(--primary-100)' },
}[level] || {};
return (
{title}
{body &&
{body}
}
{action && (
)}
);
}
function SectionCard({ title, subtitle, children, style, action }) {
return (
{title}
{subtitle &&
{subtitle}
}
{action}
{children}
);
}
function Badge({ label, color = 'var(--primary-600)', bg = 'var(--primary-50)' }) {
return (
{label}
);
}
function StatRow({ label, value, sub, bar, barColor = 'var(--primary-500)', total }) {
const pct = total ? (value / total) * 100 : bar;
return (
{label}
{typeof value === 'number' ? value.toLocaleString('es-AR') : value}
{sub &&
{sub}
}
{pct !== undefined && (
)}
);
}
Object.assign(window, { KpiCard, LineChart, BarChart, DonutChart, HeatmapChart, Alert, SectionCard, Badge, StatRow });