'use client' import { useCallback, useEffect, useMemo, useState } from 'react' import { Language } from '@/lib/types' import { t } from '@/lib/i18n' import { useFinancialModel } from '@/lib/hooks/useFinancialModel' import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis' import ProjectionFooter from '../ui/ProjectionFooter' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' import GlassCard from '../ui/GlassCard' import { RefreshCw, Download, ChevronLeft, ChevronRight, BarChart3, Target } from 'lucide-react' interface FinanzplanSlideProps { lang: Language investorId?: string | null } interface SheetMeta { name: string label_de: string label_en: string rows: number } interface SheetRow { id: number row_label?: string person_name?: string item_name?: string category?: string section?: string is_editable?: boolean is_sum_row?: boolean values?: Record values_total?: Record values_brutto?: Record brutto_monthly?: number position?: string start_date?: string purchase_amount?: number } const MONTH_LABELS = [ 'Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez', ] function getLabel(row: SheetRow): string { return row.row_label || row.person_name || row.item_name || '—' } function getValues(row: SheetRow): Record { return row.values || row.values_total || row.values_brutto || {} } function formatCell(v: number | undefined): string { if (v === undefined || v === null) return '' if (v === 0) return '—' return Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 }) } export default function FinanzplanSlide({ lang, investorId }: FinanzplanSlideProps) { const [sheets, setSheets] = useState([]) const [activeSheet, setActiveSheet] = useState('personalkosten') const [rows, setRows] = useState([]) const [loading, setLoading] = useState(false) const [computing, setComputing] = useState(false) const [yearOffset, setYearOffset] = useState(0) // 0=2026, 1=2027, ... const de = lang === 'de' // Financial model — same source as FinancialsSlide (Slide 15) const fm = useFinancialModel(investorId || null) const annualKPIs = useMemo( () => computeAnnualKPIs(fm.activeResults?.results || []), [fm.activeResults], ) // Load sheet list useEffect(() => { fetch('/api/finanzplan') .then(r => r.json()) .then(data => setSheets(data.sheets || [])) .catch(() => {}) }, []) // Load sheet data const loadSheet = useCallback(async (name: string) => { if (name === 'kpis' || name === 'charts') { setRows([]) setLoading(false) return } setLoading(true) try { const r = await fetch(`/api/finanzplan/${name}`) const data = await r.json() setRows(data.rows || []) } catch { /* ignore */ } setLoading(false) }, []) useEffect(() => { loadSheet(activeSheet) }, [activeSheet, loadSheet]) // Compute const handleCompute = async () => { setComputing(true) try { await fetch('/api/finanzplan/compute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) await loadSheet(activeSheet) } catch { /* ignore */ } setComputing(false) } // Cell edit const handleCellEdit = async (rowId: number, monthKey: string, newValue: string) => { const numVal = parseFloat(newValue.replace(/[^\d.-]/g, '')) if (isNaN(numVal)) return try { await fetch(`/api/finanzplan/${activeSheet}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rowId, updates: { [monthKey]: numVal } }), }) await loadSheet(activeSheet) } catch { /* ignore */ } } const currentYear = 2026 + yearOffset const monthStart = yearOffset * 12 + 1 const monthEnd = monthStart + 11 return (

{de ? 'Finanzplan' : 'Financial Plan'}

{de ? '2026–2030 · Monatliche Granularitaet · Editierbar' : '2026–2030 · Monthly Granularity · Editable'}

{/* Tab Bar */}
{sheets.map(s => ( ))} {/* KPIs + Grafiken Tabs */} | {[ { id: 'kpis', label: 'KPIs', icon: Target }, { id: 'charts', label: de ? 'Grafiken' : 'Charts', icon: BarChart3 }, ].map(tab => ( ))}
{/* === KPIs Tab === */} {activeSheet === 'kpis' && (

{de ? 'Wichtige Kennzahlen (pro Jahr)' : 'Key Metrics (per year)'}

{[2026, 2027, 2028, 2029, 2030].map(y => ( ))} {(() => { if (annualKPIs.length === 0) return ( ) const kpiRows = [ { label: 'MRR (Dez)', values: annualKPIs.map(k => k.mrr), unit: '€', bold: true }, { label: 'ARR', values: annualKPIs.map(k => k.arr), unit: '€', bold: true }, { label: de ? 'Kunden (Dez)' : 'Customers (Dec)', values: annualKPIs.map(k => k.customers), unit: '', bold: false }, { label: 'ARPU (MRR/Kunden)', values: annualKPIs.map(k => k.arpu), unit: '€', bold: false }, { label: de ? 'Mitarbeiter' : 'Employees', values: annualKPIs.map(k => k.employees), unit: '', bold: false }, { label: de ? 'Umsatz/Mitarbeiter' : 'Revenue/Employee', values: annualKPIs.map(k => k.revenuePerEmployee), unit: '€', bold: false }, { label: de ? 'Personalkosten' : 'Personnel Costs', values: annualKPIs.map(k => k.personnelCosts), unit: '€', bold: false }, { label: 'EBIT', values: annualKPIs.map(k => k.ebit), unit: '€', bold: true }, { label: de ? 'EBIT-Marge' : 'EBIT Margin', values: annualKPIs.map(k => k.ebitMargin), unit: '%', bold: false }, { label: de ? 'Steuern' : 'Taxes', values: annualKPIs.map(k => k.taxes), unit: '€', bold: false }, { label: de ? 'Jahresueberschuss' : 'Net Income', values: annualKPIs.map(k => k.netIncome), unit: '€', bold: true }, { label: de ? 'Serverkosten/Kunde' : 'Server Cost/Customer', values: annualKPIs.map(k => k.serverCostPerCustomer), unit: '€', bold: false }, { label: de ? 'Bruttomarge' : 'Gross Margin', values: annualKPIs.map(k => k.grossMargin), unit: '%', bold: false }, { label: 'Burn Rate (Dez)', values: annualKPIs.map(k => k.burnRate), unit: '€/Mo', bold: false }, { label: de ? 'Runway (Monate)' : 'Runway (months)', values: annualKPIs.map(k => k.runway === null ? '∞' : k.runway), unit: '', bold: false }, ] return kpiRows.map((row, idx) => ( {row.values.map((v, i) => { const num = typeof v === 'number' ? v : 0 const display = typeof v === 'string' ? v : ( row.unit === '%' ? `${v}%` : row.unit === '€/Mo' ? formatCell(num) + '/Mo' : formatCell(num) ) return ( ) })} )) })()}
KPI{y}
{de ? 'Finanzmodell wird geladen...' : 'Loading financial model...'}
{row.label} {display}
)} {/* === Charts Tab === */} {activeSheet === 'charts' && (
{/* MRR + Kunden Chart */}

{de ? 'MRR & Kundenentwicklung' : 'MRR & Customer Growth'}

{(() => { const maxMrr = Math.max(...annualKPIs.map(k => k.mrr), 1) const maxCust = Math.max(...annualKPIs.map(k => k.customers), 1) return annualKPIs.map(k => ({ year: String(k.year), mrr: k.mrr, cust: k.customers, max_mrr: maxMrr, max_cust: maxCust })) })().map((d, idx) => (
{/* MRR bar */}
{d.mrr >= 100000 ? `${Math.round(d.mrr/1000)}k` : d.mrr.toLocaleString('de-DE')}
{/* Kunden bar */}
{d.cust}
{d.year}
))}
MRR (€) {de ? 'Kunden' : 'Customers'}
{/* EBIT + Cash Chart */}

EBIT

{(() => { const maxAbs = Math.max(...annualKPIs.map(k => Math.abs(k.ebit)), 1) return annualKPIs.map((k, idx) => { const h = Math.abs(k.ebit) / maxAbs * 100 return (
{k.ebit >= 0 ? (
{Math.round(k.ebit/1000)}k
) : (
{Math.round(k.ebit/1000)}k
)}
{k.year}
) }) })()}

{de ? 'Personalaufbau' : 'Headcount'}

{(() => { const maxEmp = Math.max(...annualKPIs.map(k => k.employees), 1) return annualKPIs.map(k => ({ year: String(k.year), val: k.employees })) .map((d, idx) => (
{d.val}
{d.year}
)) })()}
)} {/* Year Navigation — not for GuV, KPIs, Charts (annual views) */} {!['guv', 'kpis', 'charts'].includes(activeSheet) && (
{currentYear}
)} {/* Data Grid — not shown for KPIs and Charts */} {!['kpis', 'charts'].includes(activeSheet) && ( {loading ? (
{de ? 'Lade...' : 'Loading...'}
) : activeSheet === 'guv' ? ( /* === GuV: Annual table (y2026-y2030) === */ {[2026, 2027, 2028, 2029, 2030].map(y => ( ))} {rows.map(row => { const values = getValues(row) const label = getLabel(row) const isSumRow = row.is_sum_row || label.includes('EBIT') || label.includes('Summe') || label.includes('Rohergebnis') || label.includes('Gesamtleistung') || label.includes('Jahresüberschuss') || label.includes('Ergebnis') return ( {[2026, 2027, 2028, 2029, 2030].map(y => { const v = values[`y${y}`] || 0 return ( ) })} ) })}
{de ? 'GuV-Position' : 'P&L Item'} {y}
{label} 0 ? (isSumRow ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isSumRow ? 'font-bold' : ''}`}> {v === 0 ? '—' : Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 })}
) : ( /* === Monthly Grid (all other sheets) === */ {MONTH_LABELS.map((label, idx) => ( ))} {rows.map(row => { const values = getValues(row) const label = getLabel(row) const isSumRow = row.is_sum_row || label.includes('GESAMT') || label.includes('Summe') || label.includes('ÜBERSCHUSS') || label.includes('LIQUIDITÄT') || label.includes('UEBERSCHUSS') || label.includes('LIQUIDITAET') const isEditable = row.is_editable // Balance rows show Dec value, flow rows show annual sum const isBalanceRow = label.includes('Kontostand') || label === 'LIQUIDITÄT' || label === 'LIQUIDITAET' let annual = 0 if (isBalanceRow) { // Point-in-time: show last month (December) value annual = values[`m${monthEnd}`] || 0 } else { // Flow: sum all 12 months for (let m = monthStart; m <= monthEnd; m++) annual += values[`m${m}`] || 0 } return ( {Array.from({ length: 12 }, (_, idx) => { const mKey = `m${monthStart + idx}` const v = values[mKey] || 0 return ( ) })} ) })} {/* Summenzeile für relevante Sheets */} {['personalkosten', 'materialaufwand', 'betriebliche', 'investitionen', 'sonst_ertraege', 'umsatzerloese', 'kunden', 'kunden_summary'].includes(activeSheet) && rows.length > 0 && (() => { // Berechne Summe über alle Zeilen die keine Summenzeilen sind const sumValues: Record = {} let sumAnnual = 0 const nonSumRows = rows.filter(r => { const l = getLabel(r) return !(r.is_sum_row || l.includes('GESAMT') || l.includes('Summe') || l.includes('Gesamtkosten') || l === 'SUMME') }) for (let idx = 0; idx < 12; idx++) { const mKey = `m${monthStart + idx}` let colSum = 0 for (const row of nonSumRows) { const v = getValues(row) colSum += v[mKey] || 0 } sumValues[mKey] = colSum sumAnnual += colSum } return ( {Array.from({ length: 12 }, (_, idx) => { const mKey = `m${monthStart + idx}` const v = sumValues[mKey] || 0 return ( ) })} ) })()}
{de ? 'Position' : 'Item'} {currentYear} {label}
{isEditable && } {label} {row.position && ({row.position})} {row.section && [{row.section}]}
{formatCell(annual)} 0 ? (isSumRow ? 'text-white/70' : 'text-white/50') : 'text-white/15' } ${isEditable ? 'cursor-pointer hover:bg-indigo-500/10' : ''}`} onDoubleClick={() => { if (!isEditable) return const input = prompt(`${label} — ${MONTH_LABELS[idx]} ${currentYear}`, String(v)) if (input !== null) handleCellEdit(row.id, mKey, input) }} > {formatCell(v)}
{de ? 'SUMME' : 'TOTAL'} {['kunden', 'kunden_summary'].includes(activeSheet) ? formatCell(sumValues[`m${monthEnd}`] || 0) : formatCell(sumAnnual) } 0 ? 'text-white/70' : 'text-white/15'}`}> {formatCell(v)}
)}
)}
) }