// Company detail view // Forecast chart with confidence band const ForecastChart = ({ history, forecast, w = 680, h = 220 }) => { const all = [...history.map(h => ({ ...h, type: 'actual' })), ...forecast.map(f => ({ ...f, type: 'forecast' }))]; const padL = 32, padR = 16, padT = 12, padB = 24; const cw = w - padL - padR, ch = h - padT - padB; const vals = [...history.map(h => h.actual), ...forecast.map(f => f.upper)]; const min = 0, max = Math.max(...vals) * 1.1; const step = cw / (all.length - 1); const y = v => padT + ch - ((v - min) / (max - min)) * ch; const x = i => padL + i * step; // Confidence band const splitIdx = history.length; const bandUpper = forecast.map((f, i) => [x(splitIdx + i), y(f.upper)]); const bandLower = forecast.map((f, i) => [x(splitIdx + i), y(f.lower)]); // Connect to last actual point const lastActual = history[history.length - 1].actual; const connectX = x(splitIdx - 1), connectY = y(lastActual); const bandD = `M${connectX},${connectY} ` + bandUpper.map(p => `L${p[0]},${p[1]}`).join(' ') + ' ' + bandLower.slice().reverse().map(p => `L${p[0]},${p[1]}`).join(' ') + ` L${connectX},${connectY} Z`; const actualLine = 'M' + history.map((d, i) => `${x(i)},${y(d.actual)}`).join(' L'); const forecastLine = `M${connectX},${connectY} ` + forecast.map((d, i) => `L${x(splitIdx + i)},${y(d.forecast)}`).join(' '); const yTicks = [0, max * 0.25, max * 0.5, max * 0.75, max].map(v => Math.round(v * 10) / 10); return ( {yTicks.map((t, i) => ( {t} ))} {history.map((d, i) => ( ))} previsione {all.map((d, i) => i % 2 === 0 && ( {d.month} ))} ); }; // Revenue bar chart const RevenueBars = ({ data, w = 340, h = 180 }) => { const padL = 28, padR = 8, padT = 8, padB = 22; const cw = w - padL - padR, ch = h - padT - padB; const max = Math.max(...data.map(d => Math.max(d.actual, d.budget))) * 1.1; const bw = cw / data.length * 0.6; const step = cw / data.length; const y = v => padT + ch - (v / max) * ch; return ( {[0, max/2, max].map((t, i) => ( {t.toFixed(0)} ))} {data.map((d, i) => { const cx = padL + i * step + step/2; return ( {i % 2 === 0 && {d.month}} ); })} ); }; const CompanyDetail = ({ company, onBack }) => { const [tab, setTab] = React.useState('panoramica'); const D = window.REGULA_DATA; // Use MetalTech detail data as the canonical detail view (demo) const det = D.metaltech; // Context-aware KPIs const contextKPIs = { metaltech: [ { label: "Ricavo medio mensile", value: "4.02 M€", delta: "+6.3%" }, { label: "Margine EBITDA", value: "14.8%", delta: "+0.9 p.p." }, { label: "DSO", value: "62 gg", delta: "−4 gg" }, { label: "PFN / EBITDA", value: "1.82×", delta: "−0.14×" } ], aquaworld: [ { label: "Ricavo per visitatore", value: "28.40 €", delta: "+4.2%" }, { label: "Visitatori YTD", value: "654.900", delta: "+6.8%" }, { label: "Occupazione media", value: "68%", delta: "+3 p.p." }, { label: "DSCR", value: "1.24×", delta: "−0.08×", bad: true } ], lumiere: [ { label: "Ricavo per serata", value: "12.8 k€", delta: "−8.1%", bad: true }, { label: "Presenze medie", value: "412", delta: "−11%", bad: true }, { label: "Scontrino medio", value: "31.10 €", delta: "+2.4%" }, { label: "Serate aperte", value: "138", delta: "+4" } ], fondazione: [ { label: "Copertura da donazioni", value: "108%", delta: "+6 p.p." }, { label: "Donatori attivi", value: "1.284", delta: "+14%" }, { label: "Visitatori mostre", value: "42.100", delta: "+9%" }, { label: "Rapporto costi / entrate", value: "0.94", delta: "−0.04" } ] }; const kpis = contextKPIs[company.id] || contextKPIs.metaltech; return (
← Dashboard gruppo
{company.short}

{company.name}

{company.sectorTag} · P.IVA {company.pivat} · {company.headquarter} · {company.employees} dipendenti
● {company.statusNote} Controllata 100% Consolidamento integrale
{['Panoramica', 'Economico', 'Finanziario', 'KPI', 'Documenti', 'Normative'].map(t => { const k = t.toLowerCase(); return (
setTab(k)}> {t} {t === 'Documenti' && 18} {t === 'Normative' && 7}
); })}
{tab === 'panoramica' && ( <>
Conto economico riclassificato
Apr 2026 YTD · M€
{det.conto.map((row, i) => { const delta = ((row.actual - row.budget) / Math.abs(row.budget)) * 100; return ( ); })}
Voce Actual Budget Prev. Δ vs Bdg
{row.label} {row.actual > 0 ? '' : '−'}{Math.abs(row.actual).toFixed(1)} {row.budget > 0 ? '' : '−'}{Math.abs(row.budget).toFixed(1)} {row.prev > 0 ? '' : '−'}{Math.abs(row.prev).toFixed(1)} 0 ? 'var(--green)' : delta < 0 ? 'var(--red)' : 'var(--muted)' }}> {delta > 0 ? '+' : ''}{delta.toFixed(1)}%
Ricavi mensili · actual vs budget
Budget Actual
Stato patrimoniale sintetico
Attivo
{[ ['Immobilizzazioni', 58.4], ['Rimanenze', 12.1], ['Crediti', 14.8], ['Liquidità', 12.4] ].map(([l, v]) => (
{l} {v.toFixed(1)}
))}
Totale97.7
Passivo
{[ ['Patrimonio netto', 42.1], ['Debito M/L termine', 32.0], ['Debito B/T', 6.2], ['Altri debiti', 17.4] ].map(([l, v]) => (
{l} {v.toFixed(1)}
))}
Totale97.7
Forecast ricavi · 12 mesi
Actual Forecast Banda 80%
KPI contestuali — {company.sector}
Drag & drop per riordinare · Aggiungi KPI con formula custom
{kpis.map((kpi, i) => (
{kpi.label}
{kpi.value}
{kpi.delta}
))}
Aggiungi KPI
)} {tab !== 'panoramica' && (
Tab "{tab}" — demo
In questo prototipo la Panoramica è completamente funzionale.
)}
); }; Object.assign(window, { CompanyDetail });