const { useState, useEffect, useRef, useMemo, useCallback } = React; // ─── Color system ──────────────────────────────────────────────────────────── const C = { primary: '#185FA5', success: '#1D9E75', warning: '#EF9F27', danger: '#E24B4A', bg: '#F0F2F5', sidebar: '#0D1F35', sideText: '#A8BDD4', card: '#FFFFFF', text: '#1A2332', textSub: '#64748B', border: '#E2E8F0', borderSub: '#F1F5F9', }; // ─── Utilities ─────────────────────────────────────────────────────────────── function fmtBRL(val, compact = true) { if (val === null || val === undefined) return '—'; const abs = Math.abs(val); const sign = val < 0 ? '-' : ''; if (compact) { if (abs >= 1e9) return `${sign}R$ ${(abs/1e9).toFixed(1).replace('.',',')}B`; if (abs >= 1e6) return `${sign}R$ ${(abs/1e6).toFixed(1).replace('.',',')}M`; if (abs >= 1e3) return `${sign}R$ ${(abs/1e3).toFixed(0)}k`; return `${sign}R$ ${abs.toFixed(0)}`; } return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(val); } function fmtPct(val, decimals = 1) { if (val === null || val === undefined) return '—'; const s = val >= 0 ? '+' : ''; return `${s}${val.toFixed(decimals)}%`; } function statusColor(status) { return ({ complete: C.success, on_track: C.success, ok: C.success, at_risk: C.warning, warning: C.warning, delayed: C.danger, critical: C.danger, upcoming: C.primary, pending: C.textSub, over: C.danger, under: C.success, })[status] || C.textSub; } function statusLabel(status) { return ({ complete: '✓ Concluído', on_track: '✓ Alinhado', ok: '✓ OK', at_risk: '⚠ Em risco', warning: '⚠ Atenção', delayed: '⏴ Atrasado', critical: '⚠ Crítico', upcoming: '○ Previsto', pending: '○ Pendente', over: '▲ Acima', under: '▼ Abaixo', })[status] || status; } // ─── Sidebar ───────────────────────────────────────────────────────────────── function Sidebar({ active, onNav }) { const items = [ { id: 1, code: 'SPG', icon: '▦', label: 'Dashboard Executivo' }, { id: 2, code: 'IAP', icon: '◈', label: 'Dashboard IA Preditivo' }, { id: 3, code: 'AUT', icon: '◉', label: 'Autorizações de Pedidos'}, { id: 4, code: 'EVM', icon: '◑', label: 'Desvios de Custos' }, { id: 5, code: 'AMB', icon: '◎', label: 'Painel Ambiental LI' }, { id: 6, code: 'SUP', icon: '◐', label: 'Suprimentos / Lead Time'}, { id: 7, code: 'RIS', icon: '◌', label: 'Gestão de Riscos' }, { id: 8, code: 'SED', icon: '◷', label: 'SED — Campo (Mobile)' }, ]; return (
{/* Header */}
DP
SIGP
Destilaria Pioneira
⚠ CONDICIONANTE CRÍTICA
PGR → IMASUL amanhã 24/05
{/* Nav */} {/* Footer */}
v1.0.0 · Semana 8 · 23/05/2026
LI Nº 000977/2022 · IMASUL/MS
); } // ─── TopBar ────────────────────────────────────────────────────────────────── function TopBar({ evm, project }) { const spiColor = evm.SPI >= 1 ? C.success : evm.SPI >= 0.9 ? C.warning : C.danger; const cpiColor = evm.CPI >= 1 ? C.success : evm.CPI >= 0.9 ? C.warning : C.danger; const devDays = 45; // dias de desvio no forecast const metric = (label, value, color, sub) => (
{label} {value} {sub && {sub}}
); return (
DESTILARIA PIONEIRA
SIGP · Implantação
{metric('SPI', evm.SPI.toFixed(2), spiColor)} {metric('CPI', evm.CPI.toFixed(2), cpiColor)} {metric('Avanço Físico', `${evm.physicalReal}%`, C.warning, `Plano: ${evm.physicalPlanned}%`)} {metric('Forecast Conclusão', project.forecastEnd, C.warning, `Baseline: ${project.baselineEnd}`)} {metric('Saldo Contingência', fmtBRL(project.contingencyRemaining), '#fff', `de ${fmtBRL(project.contingencyOriginal)}`)}
Saúde: EM RISCO
23/05/2026 · S08
); } // ─── Card ──────────────────────────────────────────────────────────────────── function Card({ children, title, subtitle, headerRight, style, noPad }) { return (
{title && (
{title}
{subtitle &&
{subtitle}
}
{headerRight}
)}
{children}
); } // ─── Badge ─────────────────────────────────────────────────────────────────── function Badge({ label, variant = 'default', small }) { const map = { danger: { bg:'#FEE2E2', color:C.danger, border:'#FECACA' }, success: { bg:'#D1FAE5', color:'#065F46', border:'#A7F3D0' }, warning: { bg:'#FEF3C7', color:'#92400E', border:'#FDE68A' }, primary: { bg:'#DBEAFE', color:'#1E40AF', border:'#BFDBFE' }, default: { bg:C.borderSub, color:C.textSub, border:C.border }, }; const s = map[variant] || map.default; return ( {label} ); } // ─── KPI Card ──────────────────────────────────────────────────────────────── function KPICard({ label, value, sub, status, delta, style }) { const col = status ? statusColor(status) : C.text; return (
{label}
{value}
{sub &&
{sub}
} {delta &&
{delta}
}
); } // ─── Dual Progress Bar ─────────────────────────────────────────────────────── function DualBar({ planned, real, status, label, showValues = true }) { const col = statusColor(status); return (
{label && (
{label} {showValues && ( {real}% / {planned}% plan )}
)}
{/* Planned */}
{/* Real */}
); } // ─── Gauge (Semicircle) ────────────────────────────────────────────────────── function Gauge({ value, max = 1.5, warnAt = 1.0, dangerAt = 0.9, label, size = 150 }) { const W = size, H = size * 0.6; const cx = W / 2, cy = H; const r = H * 0.92; const trackW = size * 0.09; const pctOf = v => Math.min(0.999, Math.max(0.001, v / max)); function ptOnArc(pct) { const angle = Math.PI - pct * Math.PI; return [cx + r * Math.cos(angle), cy - r * Math.sin(angle)]; } function arcD(p1, p2) { const [x1, y1] = ptOnArc(Math.max(0.001, p1)); const [x2, y2] = ptOnArc(Math.min(0.999, p2)); const la = (p2 - p1) > 0.5 ? 1 : 0; return `M ${x1.toFixed(2)} ${y1.toFixed(2)} A ${r} ${r} 0 ${la} 0 ${x2.toFixed(2)} ${y2.toFixed(2)}`; } const pct = pctOf(value); const wPct = pctOf(dangerAt); const yPct = pctOf(warnAt); const color = value >= warnAt ? C.success : value >= dangerAt ? C.warning : C.danger; const [nx, ny] = ptOnArc(pct); return ( {/* Zone track */} {/* Value arc */} {/* Needle */} {/* Ticks: 0.9, 1.0 */} {[dangerAt, warnAt].map(v => { const [tx, ty] = ptOnArc(pctOf(v)); const [tx2, ty2] = ptOnArc(pctOf(v)); return ; })} {/* Value */} {value.toFixed(2)} {label && {label}} ); } // ─── Sparkline ─────────────────────────────────────────────────────────────── function Sparkline({ data, color = C.primary, w = 80, h = 28 }) { if (!data || data.length < 2) return ; const mn = Math.min(...data), mx = Math.max(...data); const range = mx - mn || 1; const xs = data.map((_, i) => (i / (data.length - 1)) * w); const ys = data.map(v => h - ((v - mn) / range) * (h - 4) - 2); const d = xs.map((x, i) => `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${ys[i].toFixed(1)}`).join(' '); const trend = data[data.length-1] >= data[0]; const col = color === 'auto' ? (trend ? C.success : C.danger) : color; return ( ); } // ─── Curva S Chart ─────────────────────────────────────────────────────────── function CurvaS({ data, height = 250 }) { const W = 580, H = height; const pad = { t:18, r:14, b:32, l:44 }; const cW = W - pad.l - pad.r, cH = H - pad.t - pad.b; const MAX_WEEK = 43; const xf = w => (w / MAX_WEEK) * cW; const yf = p => cH - (p / 100) * cH; function makePath(pts, key) { const valid = pts.filter(d => d[key] != null); if (!valid.length) return ''; return valid.map((d, i) => `${i===0?'M':'L'}${xf(d.week).toFixed(1)},${yf(d[key]).toFixed(1)}`).join(' '); } const plannedD = makePath(data.filter(d => d.planned != null), 'planned'); const actualD = makePath(data.filter(d => d.actual != null), 'actual'); const forecastD = makePath(data.filter(d => d.forecast != null), 'forecast'); const todayX = xf(8); // Fill under actual const actualFill = `${actualD} L${xf(8).toFixed(1)},${cH} L${xf(0)},${cH} Z`; const yTicks = [0, 25, 50, 75, 100]; const xTicks = [0, 8, 16, 24, 32, 40]; return ( {/* Grid */} {yTicks.map(v => ( {v}% ))} {xTicks.map(w => ( S{String(w).padStart(2,'0')} ))} {/* Fill */} {/* TODAY */} HOJE {/* Lines */} {/* Actual dots */} {data.filter(d => d.actual != null).map(d => ( ))} {/* TODAY intersection */} {/* Labels beside today */} 37,4% 41,2% {/* Legend */} Planejado (baseline) Realizado Forecast IA ); } // ─── Screen Layout Shell ───────────────────────────────────────────────────── function ScreenShell({ title, subtitle, children, actions }) { return (

{title}

{subtitle &&

{subtitle}

}
{actions &&
{actions}
}
{children}
); } // ─── Table helpers ─────────────────────────────────────────────────────────── function Th({ children, style }) { return {children}; } function Td({ children, style }) { return {children}; } function Table({ children, style }) { return
{children}
; } // ─── SLA Countdown Badge ──────────────────────────────────────────────────── function SLABadge({ slaHours, slaRemaining }) { const ratio = slaRemaining / slaHours; const expired = slaRemaining <= 0; const color = expired ? C.danger : ratio > 0.5 ? C.success : ratio > 0.25 ? C.warning : C.danger; const bg = expired ? '#FEE2E2' : ratio > 0.5 ? '#D1FAE5' : ratio > 0.25 ? '#FEF3C7' : '#FEE2E2'; const label = expired ? 'VENCIDO' : slaRemaining < 1 ? `${Math.round(slaRemaining*60)}min` : `${slaRemaining.toFixed(0)}h`; return ( {expired ? '⚠ ' : ''}{label} restante ); } // ─── Btn ───────────────────────────────────────────────────────────────────── function Btn({ children, variant = 'default', onClick, small, style }) { const map = { primary: { bg:C.primary, color:'#fff', border:C.primary }, success: { bg:C.success, color:'#fff', border:C.success }, danger: { bg:C.danger, color:'#fff', border:C.danger }, warning: { bg:C.warning, color:'#fff', border:C.warning }, default: { bg:'#fff', color:C.text, border:C.border }, ghost: { bg:'transparent', color:C.textSub, border:C.border }, }; const s = map[variant] || map.default; return ( ); } // Export everything Object.assign(window, { C, fmtBRL, fmtPct, statusColor, statusLabel, Sidebar, TopBar, Card, Badge, KPICard, DualBar, Gauge, Sparkline, CurvaS, ScreenShell, Th, Td, Table, SLABadge, Btn, });