/* =========================================================== Shared UI primitives, icons, charts, helpers =========================================================== */ const { useState, useEffect, useRef, useMemo, useCallback, Fragment } = React; /* ---------- Icons (inline SVG, currentColor) ---------- */ const I = { layout: (p) => , funnel: (p) => , list: (p) => , terminal: (p) => , trophy: (p) => , swords: (p) => , retry: (p) => , newspaper: (p) => , settings: (p) => , bell: (p) => , moon: (p) => , sun: (p) => , check: (p) => , x: (p) => , alert: (p) => , arrowUp: (p) => , arrowDown: (p) => , ext: (p) => , chevron: (p) => , search: (p) => , back: (p) => , menu: (p) => , spark: (p) => , link: (p) => , }; /* ---------- Chip / Badge ---------- */ function StatusChip({ status }) { const map = { success: { cls: 'ok', label: 'success' }, partial: { cls: 'warn', label: 'partial' }, error: { cls: 'err', label: 'error' }, }; const s = map[status] || { cls: '', label: status }; return {s.label}; } function Chip({ children, kind, style }) { return {children}; } function StatusDot({ status }) { return ; } /* ---------- Sparkline (mini line) ---------- */ function Sparkline({ data, color, height=28 }) { if (!data || data.length === 0) return null; const w = 100, h = height; const min = Math.min(...data), max = Math.max(...data); const range = max - min || 1; const pts = data.map((v, i) => { const x = (i / (data.length - 1)) * w; const y = h - ((v - min) / range) * (h - 4) - 2; return `${x.toFixed(1)},${y.toFixed(1)}`; }).join(' '); return ( ); } /* ---------- Donut (svg) ---------- */ function Donut({ data, size=170, thickness=24 }) { // data: [{label, value, color}] const total = data.reduce((a, d) => a + d.value, 0); const r = (size - thickness) / 2; const cx = size / 2, cy = size / 2; const circ = 2 * Math.PI * r; let offset = 0; return (
{data.map((d, i) => { const len = (d.value / total) * circ; const el = ( ); offset += len; return el; })} {total.toFixed(3)} EDITORIAL SCORE
{data.map((d, i) => (
{d.label} {d.value.toFixed(3)}
))}
); } /* ---------- Funnel ---------- */ function Funnel({ stages }) { const max = Math.max(...stages.map(s => s.value)); return (
{stages.map((s, i) => { const w = (s.value / max) * 100; const prev = i === 0 ? s.value : stages[i-1].value; const ret = i === 0 ? 100 : (s.value / prev * 100); const drop = ret < 50; return (
{s.name}
{s.value.toLocaleString()}
{s.value}
{i === 0 ? '—' : `${ret.toFixed(1)}%`}
); })}
); } /* ---------- Gantt ---------- */ function Gantt({ lanes, totalDuration }) { return (
{lanes.map((lane, i) => (
{lane.name}
{lane.bars.map((b, j) => { const left = (b.start / totalDuration) * 100; const width = (b.duration / totalDuration) * 100; return (
{b.label}
); })}
))}
0s {(totalDuration/4).toFixed(0)}s {(totalDuration/2).toFixed(0)}s {(totalDuration*3/4).toFixed(0)}s {totalDuration.toFixed(0)}s
); } /* ---------- Score breakdown stacked bar ---------- */ function ScoreBar({ breakdown, total }) { const entries = [ { key: 'source', label: 'Source', color: '#5b8def' }, { key: 'freshness', label: 'Freshness', color: 'var(--gold)' }, { key: 'relevance', label: 'Relevance', color: '#4ec9b0' }, { key: 'trends', label: 'Trends', color: '#d685c3' }, { key: 'gemini', label: 'Gemini', color: '#c98c4e' }, ]; return (
`${e.label}: ${(breakdown[e.key]||0).toFixed(3)}`).join(' · ')}> {entries.map(e => { const v = breakdown[e.key] || 0; const w = (v / total) * 100; return
; })}
); } /* ---------- helpers ---------- */ function fmtDuration(secs) { if (secs == null) return '—'; const m = Math.floor(secs / 60); const s = Math.floor(secs % 60); return `${m}:${String(s).padStart(2, '0')}`; } function fmtAgo(iso) { const now = Date.now(); const t = new Date(iso).getTime(); const diff = Math.floor((now - t) / 1000); if (diff < 60) return `${diff}s atrás`; if (diff < 3600) return `${Math.floor(diff/60)}m atrás`; if (diff < 86400) return `${Math.floor(diff/3600)}h ${Math.floor((diff%3600)/60)}m atrás`; return `${Math.floor(diff/86400)}d atrás`; } function fmtClock(iso) { const d = new Date(iso); return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`; } Object.assign(window, { I, StatusChip, StatusDot, Chip, Sparkline, Donut, Funnel, Gantt, ScoreBar, fmtDuration, fmtAgo, fmtClock, });