'use client' import { useCallback, useEffect, useState } from 'react' import { Language } from '@/lib/types' import { t } from '@/lib/i18n' 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 } 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 }: 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' // 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 => ( ))} {(() => { // Compute KPIs from loaded data — we need liquidität and umsatz data // These are approximate since we don't have all sheets loaded simultaneously const kpiRows = [ { label: 'MRR (Dez)', values: [6100, 84450, 267950, 517650, 834750], unit: '€', bold: true }, { label: 'ARR', values: [73200, 1013400, 3215400, 6211800, 10017000], unit: '€', bold: true }, { label: de ? 'Kunden (Dez)' : 'Customers (Dec)', values: [14, 117, 370, 726, 1200], unit: '', bold: false }, { label: 'ARPU (MRR/Kunden)', values: [436, 722, 724, 713, 696], unit: '€', bold: false }, { label: de ? 'Mitarbeiter' : 'Employees', values: [5, 10, 17, 25, 35], unit: '', bold: false }, { label: de ? 'Umsatz/Mitarbeiter' : 'Revenue/Employee', values: [14640, 101340, 189141, 248472, 286200], unit: '€', bold: false }, { label: de ? 'Personalkosten' : 'Personnel Costs', values: [58768, 740968, 1353764, 2154301, 3129479], unit: '€', bold: false }, { label: 'EBIT', values: [-95099, -566293, -4019, 1315689, 3144137], unit: '€', bold: true }, { label: de ? 'EBIT-Marge' : 'EBIT Margin', values: [-130, -56, -1, 21, 31], unit: '%', bold: false }, { label: de ? 'Steuern' : 'Taxes', values: [0, 0, 0, 182565, 882717], unit: '€', bold: false }, { label: de ? 'Jahresüberschuss' : 'Net Income', values: [-95099, -566293, -4019, 1133124, 2261420], unit: '€', bold: true }, { label: de ? 'Serverkosten/Kunde' : 'Server Cost/Customer', values: [100, 100, 100, 100, 100], unit: '€', bold: false }, { label: de ? 'Bruttomarge' : 'Gross Margin', values: [100, 100, 92, 90, 88], unit: '%', bold: false }, { label: 'Burn Rate (Dez)', values: [44734, 28364, 0, 0, 0], unit: '€/Mo', bold: false }, { label: de ? 'Runway (Monate)' : 'Runway (months)', values: [19, 4, '∞', '∞', '∞'], 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}
{row.label} {display}
)} {/* === Charts Tab === */} {activeSheet === 'charts' && (
{/* MRR + Kunden Chart */}

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

{[ { year: '2026', mrr: 6100, cust: 14, max_mrr: 834750, max_cust: 1200 }, { year: '2027', mrr: 84450, cust: 117, max_mrr: 834750, max_cust: 1200 }, { year: '2028', mrr: 267950, cust: 370, max_mrr: 834750, max_cust: 1200 }, { year: '2029', mrr: 517650, cust: 726, max_mrr: 834750, max_cust: 1200 }, { year: '2030', mrr: 834750, cust: 1200, max_mrr: 834750, max_cust: 1200 }, ].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

{[ { year: '2026', val: -95099 }, { year: '2027', val: -566293 }, { year: '2028', val: -4019 }, { year: '2029', val: 1315689 }, { year: '2030', val: 3144137 }, ].map((d, idx) => { const maxAbs = 3144137 const h = Math.abs(d.val) / maxAbs * 100 return (
{d.val >= 0 ? (
{Math.round(d.val/1000)}k
) : (
{Math.round(d.val/1000)}k
)}
{d.year}
) })}

{de ? 'Personalaufbau' : 'Headcount'}

{[ { year: '2026', val: 5 }, { year: '2027', val: 10 }, { year: '2028', val: 17 }, { year: '2029', val: 25 }, { year: '2030', val: 35 }, ].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('Jahresueberschuss') || 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)}
)}
)}

{de ? 'Doppelklick auf blaue Zellen zum Bearbeiten · Gründung: 01.08.2026' : 'Double-click blue cells to edit · Founding: 01.08.2026'}

) }