Files
breakpilot-core/pitch-deck/components/slides/FinancialsSlide.tsx
Benjamin Admin c967d80aed feat: Folie 14 Finanzen — direkt aus Finanzplan DB
- Mock-Daten und Szenario-Toggle entfernt
- Lädt automatisch Finanzplan-Daten beim Öffnen
- KPIs: ARR 2030, Mitarbeiter 2030, Break-Even Jahr, Cash Ende
- Übersicht: Revenue vs. Costs Chart + Waterfall + Cashflow
- GuV: Direkt aus fp_guv DB-Tabelle (keine Mocks)
- Cashflow: AnnualCashflowChart mit 1M InitialFunding
- Keine Slider/Szenario-Sidebar mehr (nicht relevant)
- Umlaute korrigiert (Übersicht statt Uebersicht)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:00:15 +01:00

259 lines
10 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { Language, FMComputeResponse } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import FinancialChart from '../ui/FinancialChart'
import KPICard from '../ui/KPICard'
import WaterfallChart from '../ui/WaterfallChart'
import AnnualCashflowChart from '../ui/AnnualCashflowChart'
type FinTab = 'overview' | 'guv' | 'cashflow'
interface FinancialsSlideProps {
lang: Language
}
export default function FinancialsSlide({ lang }: FinancialsSlideProps) {
const i = t(lang)
const [activeTab, setActiveTab] = useState<FinTab>('overview')
const [data, setData] = useState<FMComputeResponse | null>(null)
const [loading, setLoading] = useState(true)
const [guv, setGuv] = useState<any[]>([])
const de = lang === 'de'
// Auto-load Finanzplan data
useEffect(() => {
async function load() {
try {
// Compute Finanzplan and get FMResult format
const res = await fetch('/api/financial-model/compute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'finanzplan' }),
})
if (res.ok) {
const d = await res.json()
setData(d)
}
// Load GuV separately
const guvRes = await fetch('/api/finanzplan/guv')
if (guvRes.ok) {
const guvData = await guvRes.json()
setGuv(guvData.rows || [])
}
} catch (e) {
console.error('Failed to load Finanzplan:', e)
} finally {
setLoading(false)
}
}
load()
}, [])
const summary = data?.summary
const results = data?.results || []
const lastResult = results.length > 0 ? results[results.length - 1] : null
// Find break-even month (first month where revenue > costs)
const breakEvenMonth = results.findIndex(r => r.revenue_eur > r.total_costs_eur && r.revenue_eur > 0)
const breakEvenYear = breakEvenMonth >= 0 ? results[breakEvenMonth]?.year : null
const tabs: { id: FinTab; label: string }[] = [
{ id: 'overview', label: de ? 'Übersicht' : 'Overview' },
{ id: 'guv', label: de ? 'GuV (Jahres)' : 'P&L (Annual)' },
{ id: 'cashflow', label: de ? 'Cashflow' : 'Cash Flow' },
]
if (loading) {
return (
<div className="flex items-center justify-center h-full">
<div className="w-8 h-8 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
</div>
)
}
return (
<div className="max-w-6xl mx-auto px-4">
<FadeInView className="text-center mb-3">
<h2 className="text-3xl md:text-4xl font-bold mb-1">
<GradientText>{i.financials.title}</GradientText>
</h2>
<p className="text-sm text-white/50 max-w-2xl mx-auto">{i.financials.subtitle}</p>
</FadeInView>
{/* Hero KPI Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-3">
<KPICard
label="ARR 2030"
value={lastResult ? Math.round(lastResult.arr_eur / 1_000_000 * 10) / 10 : 0}
suffix=" Mio."
decimals={1}
trend="up"
color="#6366f1"
delay={0.1}
subLabel="EUR"
/>
<KPICard
label={de ? 'Mitarbeiter 2030' : 'Employees 2030'}
value={lastResult?.employees_count || 0}
trend="up"
color="#22c55e"
delay={0.15}
/>
<KPICard
label="Break-Even"
value={breakEvenYear || 0}
trend={breakEvenMonth >= 0 ? 'up' : 'neutral'}
color="#eab308"
delay={0.2}
subLabel={breakEvenMonth >= 0 ? `${de ? 'Monat' : 'Month'} ${breakEvenMonth + 1}` : ''}
/>
<KPICard
label={de ? 'Cash Ende 2030' : 'Cash End 2030'}
value={lastResult ? Math.round(lastResult.cash_balance_eur / 1_000_000 * 10) / 10 : 0}
suffix=" Mio."
decimals={1}
trend={(lastResult?.cash_balance_eur || 0) > 0 ? 'up' : 'down'}
color="#a855f7"
delay={0.25}
subLabel="EUR"
/>
</div>
{/* Tab Navigation */}
<div className="flex items-center gap-1 mb-3">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-3 py-1.5 rounded-lg text-xs transition-all
${activeTab === tab.id
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
}`}
>
{tab.label}
</button>
))}
</div>
{/* TAB: Overview — monatlicher Chart */}
{activeTab === 'overview' && (
<div className="space-y-3">
<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" /> {de ? 'Kosten' : 'Costs'}</span>
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-emerald-500 inline-block" /> Cash</span>
</div>
</div>
<FinancialChart
activeResults={data}
compareResults={new Map()}
compareMode={false}
scenarioColors={{}}
lang={lang}
/>
</div>
</FadeInView>
<div className="grid md:grid-cols-2 gap-3">
<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>
{data && <WaterfallChart results={data.results} lang={lang} />}
</div>
</FadeInView>
<FadeInView delay={0.25}>
<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 ? 'Jährlicher Cashflow' : 'Annual Cash Flow'}
</p>
{data && (
<AnnualCashflowChart
results={data.results}
initialFunding={1000000}
lang={lang}
/>
)}
</div>
</FadeInView>
</div>
</div>
)}
{/* TAB: GuV — aus Finanzplan DB */}
{activeTab === 'guv' && (
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<div className="flex items-center justify-between mb-3">
<p className="text-xs text-white/40">
{de ? 'Gewinn- und Verlustrechnung (5 Jahre)' : 'Profit & Loss Statement (5 Years)'}
</p>
<p className="text-[9px] text-white/20">{de ? 'Alle Werte in EUR' : 'All values in EUR'}</p>
</div>
<table className="w-full text-[11px]">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-2 px-2 text-white/40 font-medium min-w-[200px]">{de ? 'Position' : 'Item'}</th>
{[2026, 2027, 2028, 2029, 2030].map(y => (
<th key={y} className="text-right py-2 px-3 text-white/40 font-medium">{y}</th>
))}
</tr>
</thead>
<tbody>
{guv.map((row: any) => {
const label = row.row_label || ''
const values = row.values || {}
const isBold = row.is_sum_row || label.includes('EBIT') || label.includes('Summe') || label.includes('Rohergebnis') || label.includes('Gesamtleistung') || label.includes('Jahresueberschuss') || label.includes('Ergebnis')
return (
<tr key={row.id} className={`border-b border-white/[0.03] ${isBold ? 'bg-white/[0.03]' : ''}`}>
<td className={`py-1.5 px-2 ${isBold ? 'font-bold text-white/80' : 'text-white/60'}`}>{label}</td>
{[2026, 2027, 2028, 2029, 2030].map(y => {
const v = Math.round(values[`y${y}`] || 0)
return (
<td key={y} className={`text-right py-1.5 px-3 ${v < 0 ? 'text-red-400' : v > 0 ? (isBold ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isBold ? 'font-bold' : ''}`}>
{v === 0 ? '—' : v.toLocaleString('de-DE')}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
</FadeInView>
)}
{/* TAB: Cashflow */}
{activeTab === 'cashflow' && data && (
<FadeInView delay={0.1}>
<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 ? 'Jährlicher Cashflow & Liquiditätsentwicklung' : 'Annual Cash Flow & Liquidity Development'}
</p>
<AnnualCashflowChart
results={data.results}
initialFunding={1000000}
lang={lang}
/>
</div>
</FadeInView>
)}
</div>
)
}