const { useState } = React; // ─── EVM metric card ───────────────────────────────────────────────────────── function EVMCard({ code, label, value, fmt, status, tooltip }) { const col = statusColor(status); return (
{code} {status && }
{fmt ? fmt(value) : value}
{label}
); } // ─── Trend mini chart ───────────────────────────────────────────────────────── function TrendChart({ data, h = 120 }) { const W = 480; const pad = { t:10, r:10, b:28, l:40 }; const cW = W - pad.l - pad.r, cH = h - pad.t - pad.b; const allVals = data.flatMap(d => [d.cpi, d.spi]); const minV = Math.min(...allVals) - 0.02, maxV = Math.max(...allVals) + 0.02; const xf = i => (i / (data.length - 1)) * cW; const yf = v => cH - ((v - minV) / (maxV - minV)) * cH; const makeLine = key => data.map((d,i) => `${i===0?'M':'L'}${xf(i).toFixed(1)},${yf(d[key]).toFixed(1)}`).join(' '); // 1.0 reference line const y1 = yf(1.0); return ( {/* Reference line at 1.0 */} 1,00 {/* 0.9 reference */} 0,90 {/* Grid */} {data.map((d,i) => ( {d.label} ))} {/* Lines */} {data.map((d,i) => ( ))} {/* Legend */} CPI SPI ); } // ─── Budget commitment bars ────────────────────────────────────────────────── function CommitmentBar({ label, budgeted, contracted, realized, total }) { const pBudg = (budgeted / total) * 100; const pCont = (contracted / total) * 100; const pReal = (realized / total) * 100; return (
{label} {fmtBRL(realized)} / {fmtBRL(budgeted)}
□ Orçado: {fmtBRL(budgeted)} □ Contratado: {fmtBRL(contracted)} ■ Realizado: {fmtBRL(realized)}
); } // ─── Screen 4: Dashboard de Custos (EVM) ──────────────────────────────────── function Screen4Custos() { const { evm, wbsDeviations, cpiSpiTrend, currencyExposure } = window.SIGP_DATA; const [showAlert, setShowAlert] = useState(true); const evmCards = [ { code:'PV', label:'Valor Planejado', value: evm.PV, fmt: v => fmtBRL(v), status: null }, { code:'EV', label:'Valor Agregado', value: evm.EV, fmt: v => fmtBRL(v), status: null }, { code:'AC', label:'Custo Real', value: evm.AC, fmt: v => fmtBRL(v), status: null }, { code:'SV', label:'Variação de Prazo', value: evm.SV, fmt: v => fmtBRL(v), status: evm.SV < 0 ? 'delayed' : 'ok' }, { code:'CV', label:'Variação de Custo', value: evm.CV, fmt: v => fmtBRL(v), status: evm.CV < 0 ? 'at_risk' : 'ok' }, { code:'SPI', label:'Índice Desempenho Prazo', value: evm.SPI, fmt: v => v.toFixed(3), status: evm.SPI < 0.9 ? 'delayed' : evm.SPI < 1 ? 'at_risk' : 'ok' }, { code:'CPI', label:'Índice Desempenho Custo', value: evm.CPI, fmt: v => v.toFixed(3), status: evm.CPI < 0.9 ? 'delayed' : evm.CPI < 1 ? 'at_risk' : 'ok' }, { code:'EAC', label:'Estimativa no Término', value: evm.EAC, fmt: v => fmtBRL(v), status: evm.EAC > evm.BAC ? 'at_risk' : 'ok' }, { code:'ETC', label:'Estimativa para Concluir', value: evm.ETC, fmt: v => fmtBRL(v), status: null }, { code:'TCPI', label:'Índice Desempenho Necessário', value:evm.TCPI, fmt: v => v.toFixed(3), status: evm.TCPI > 1.1 ? 'delayed' : evm.TCPI > 1 ? 'at_risk' : 'ok' }, ]; return ( {/* ── Alert FN-06 ── */} {showAlert && (
📋
Ação Requerida — Formulário FN-06 3 pacotes de trabalho com desvio >5% ou >R$100k aguardando Análise de Causa Raiz: WBS 1.1 (terraplenagem), WBS 1.2 (fundações), WBS 2.1 (equipamentos)
setShowAlert(false)}>Preencher FN-06
)} {/* ── EVM Cards ── */}
{evmCards.map(c => )}
{/* ── Gauges + Trend chart ── */}
{/* ── WBS Deviations ── */} {wbsDeviations.map((row, i) => { const needsAction = Math.abs(row.pct) >= 5 && row.deviation !== 0; const isTotal = row.wbs === 'TOTAL'; return ( ); })}
WBS Pacote de Trabalho Orçado Realizado Desvio R$ Desvio % Status Análise de Causa
{row.wbs} {row.name} {fmtBRL(row.budgeted)} {row.actual > 0 ? fmtBRL(row.actual) : '—'} {row.deviation !== 0 && ( {row.deviation > 0 ? '+' : ''}{fmtBRL(Math.abs(row.deviation))} )} {row.pct !== 0 && ( {row.pct > 0 ? '+' : ''}{row.pct.toFixed(1)}% )} {row.status !== 'pending' && } {row.status === 'pending' && Não iniciado} {row.cause ? <>⚠ FN-06 pendente · {row.cause} : row.status === 'pending' ? '—' : 'Dentro do esperado'}
{/* ── Row: Commitment + Currency ── */}
Total contratado: {fmtBRL(109080000)} · Realizado: {fmtBRL(evm.AC)} · Saldo BAC: {fmtBRL(evm.BAC - evm.AC)}
{currencyExposure.map((cx, i) => ( ))}
Moeda Valor Contratado Taxa Base Taxa Atual Variação Hedge
{cx.currency} {fmtBRL(cx.contracted)} {cx.baseRate.toFixed(2)} {cx.currentRate.toFixed(2)} +{cx.variation.toFixed(1)}%
⚠ Exposição líquida sem cobertura: R$ 2,8M (EUR) — Risco de impacto de R$+420k no EAC caso EUR/BRL atinja 6,60.
⚠ Hedge parcial USD (60%): R$+1,2M de exposição adicional — encaminhar para aprovação CFO.
); } Object.assign(window, { Screen4Custos });