fix(pitch-deck): Financials Overview tab fully from fp_* data
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 29s
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 29s
Replaced all useFinancialModel-based charts with fp_*-based: - Revenue vs Costs: annual bars from fpKPIs (was FinancialChart/monthly) - EBIT & Liquidität: dual bar chart (was WaterfallChart) - Unit Economics 2030: ACV, Gross Margin, NRR, EBIT Margin (was RunwayGauge + UnitEconomicsCards) All 3 tabs (Overview, GuV, Cashflow) now read from fp_* tables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -144,60 +144,87 @@ export default function FinancialsSlide({ lang, investorId, preferredScenarioId,
|
||||
{/* Main content: full width */}
|
||||
<div className="space-y-3">
|
||||
|
||||
{/* TAB: Overview — monatlicher Chart + Waterfall + Unit Economics */}
|
||||
{/* TAB: Overview — annual charts from fp_* */}
|
||||
{activeTab === 'overview' && (
|
||||
<>
|
||||
{/* Revenue vs Costs */}
|
||||
<FadeInView delay={0.1}>
|
||||
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<p className="text-xs text-white/40">
|
||||
{de ? 'Umsatz vs. Kosten (60 Monate)' : 'Revenue vs. Costs (60 months)'}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-[9px]">
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-indigo-500 inline-block" /> {de ? 'Umsatz' : 'Revenue'}</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-red-400 inline-block" style={{ borderBottom: '1px dashed' }} /> {de ? 'Kosten' : 'Costs'}</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-emerald-500 inline-block" /> {de ? 'Kunden' : 'Customers'}</span>
|
||||
</div>
|
||||
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
|
||||
<p className="text-xs text-white/40 mb-3">{de ? 'Umsatz vs. Kosten (pro Jahr) · Quelle: Finanzplan' : 'Revenue vs. Costs (per year) · Source: Financial Plan'}</p>
|
||||
<div className="grid grid-cols-5 gap-3 items-end h-40">
|
||||
{[2026,2027,2028,2029,2030].map((y, idx) => {
|
||||
const rev = fpKPIs[`y${y}`]?.revenue || 0
|
||||
const costs = rev - (fpKPIs[`y${y}`]?.ebit || 0)
|
||||
const cust = fpKPIs[`y${y}`]?.customers || 0
|
||||
const maxVal = Math.max(...[2026,2027,2028,2029,2030].map(yr => Math.max(fpKPIs[`y${yr}`]?.revenue || 0, (fpKPIs[`y${yr}`]?.revenue || 0) - (fpKPIs[`y${yr}`]?.ebit || 0))), 1)
|
||||
return (
|
||||
<div key={idx} className="flex flex-col items-center gap-1">
|
||||
<div className="flex items-end gap-1 w-full justify-center" style={{ height: '110px' }}>
|
||||
<div className="w-7 bg-indigo-500/60 rounded-t" style={{ height: `${(rev / maxVal) * 100}px` }}>
|
||||
<div className="text-[10px] text-indigo-300 text-center -mt-4 whitespace-nowrap font-semibold">{rev >= 1000000 ? `${(rev/1000000).toFixed(1)}M` : `${Math.round(rev/1000)}k`}</div>
|
||||
</div>
|
||||
<div className="w-7 bg-red-500/40 rounded-t" style={{ height: `${(costs / maxVal) * 100}px` }}>
|
||||
<div className="text-[10px] text-red-300 text-center -mt-4 whitespace-nowrap font-semibold">{costs >= 1000000 ? `${(costs/1000000).toFixed(1)}M` : `${Math.round(costs/1000)}k`}</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-white/50 font-medium">{y}</span>
|
||||
<span className="text-[9px] text-emerald-400/60">{cust} {de ? 'Kd.' : 'cust.'}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="flex justify-center gap-6 mt-2 text-[10px]">
|
||||
<span className="flex items-center gap-1"><span className="w-3 h-2 bg-indigo-500/60 rounded inline-block" /> {de ? 'Umsatz' : 'Revenue'}</span>
|
||||
<span className="flex items-center gap-1"><span className="w-3 h-2 bg-red-500/40 rounded inline-block" /> {de ? 'Kosten' : 'Costs'}</span>
|
||||
</div>
|
||||
<FinancialChart
|
||||
activeResults={activeResults}
|
||||
compareResults={compareResults}
|
||||
compareMode={fm.compareMode}
|
||||
scenarioColors={scenarioColors}
|
||||
lang={lang}
|
||||
/>
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{/* EBIT + Liquidität */}
|
||||
<FadeInView delay={0.2}>
|
||||
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
|
||||
<p className="text-xs text-white/40 mb-2">
|
||||
{de ? 'Cash-Flow (Quartal)' : 'Cash Flow (Quarterly)'}
|
||||
</p>
|
||||
{activeResults && <WaterfallChart results={activeResults.results} lang={lang} />}
|
||||
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
|
||||
<p className="text-xs text-white/40 mb-3">EBIT & {de ? 'Liquidität' : 'Cash'}</p>
|
||||
<div className="grid grid-cols-5 gap-1 items-end h-28">
|
||||
{[2026,2027,2028,2029,2030].map((y, idx) => {
|
||||
const ebit = fpKPIs[`y${y}`]?.ebit || 0
|
||||
const liq = fpKPIs[`y${y}`]?.liquiditaet || 0
|
||||
const maxAbs = Math.max(...[2026,2027,2028,2029,2030].map(yr => Math.max(Math.abs(fpKPIs[`y${yr}`]?.ebit || 0), Math.abs(fpKPIs[`y${yr}`]?.liquiditaet || 0))), 1)
|
||||
return (
|
||||
<div key={idx} className="flex flex-col items-center gap-0.5">
|
||||
<div className="flex items-end gap-0.5 justify-center" style={{ height: '80px' }}>
|
||||
<div className={`w-5 ${ebit >= 0 ? 'bg-emerald-500/60 rounded-t' : 'bg-red-500/60 rounded-b'}`} style={{ height: `${Math.max(Math.abs(ebit)/maxAbs * 70, 2)}px` }} />
|
||||
<div className={`w-5 ${liq >= 0 ? 'bg-cyan-500/60 rounded-t' : 'bg-red-500/40 rounded-b'}`} style={{ height: `${Math.max(Math.abs(liq)/maxAbs * 70, 2)}px` }} />
|
||||
</div>
|
||||
<span className="text-[9px] text-white/40">{y}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="flex justify-center gap-4 mt-2 text-[9px]">
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 bg-emerald-500/60 rounded inline-block" /> EBIT</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 bg-cyan-500/60 rounded inline-block" /> {de ? 'Liquidität' : 'Cash'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Key Metrics */}
|
||||
<FadeInView delay={0.25}>
|
||||
<div className="space-y-3">
|
||||
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3 flex justify-center">
|
||||
<RunwayGauge
|
||||
months={lastResult?.runway_months || 0}
|
||||
size={120}
|
||||
label={de ? 'Runway (Monate)' : 'Runway (months)'}
|
||||
/>
|
||||
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
|
||||
<p className="text-xs text-white/40 mb-3">Unit Economics (2030)</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{[
|
||||
{ label: 'ACV', value: fpLast?.arpu ? `${fpLast.arpu.toLocaleString('de-DE')} EUR` : '—', color: 'text-indigo-300' },
|
||||
{ label: 'Gross Margin', value: fpLast?.grossMargin ? `${fpLast.grossMargin}%` : '—', color: 'text-emerald-300' },
|
||||
{ label: 'NRR', value: fpLast?.nrr ? `${fpLast.nrr}%` : '—', color: 'text-purple-300' },
|
||||
{ label: 'EBIT Margin', value: fpLast?.ebitMargin ? `${fpLast.ebitMargin}%` : '—', color: 'text-amber-300' },
|
||||
].map((m, idx) => (
|
||||
<div key={idx} className="text-center bg-white/[0.03] rounded-lg p-2">
|
||||
<p className="text-[10px] text-white/40 uppercase tracking-wider">{m.label}</p>
|
||||
<p className={`text-lg font-bold ${m.color}`}>{m.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{lastResult && (
|
||||
<UnitEconomicsCards
|
||||
cac={lastResult.cac_eur}
|
||||
ltv={lastResult.ltv_eur}
|
||||
ltvCacRatio={lastResult.ltv_cac_ratio}
|
||||
grossMargin={lastResult.gross_margin_pct}
|
||||
churnRate={fm.activeScenario?.assumptions.find(a => a.key === 'churn_rate_monthly')?.value as number || 3}
|
||||
lang={lang}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</FadeInView>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user