'use client' import { motion } from 'framer-motion' import { FMResult } from '@/lib/types' export type AccountingStandard = 'hgb' | 'usgaap' interface AnnualPLTableProps { results: FMResult[] lang: 'de' | 'en' standard: AccountingStandard } interface AnnualRow { year: number revenue: number cogs: number grossProfit: number grossMarginPct: number personnel: number marketing: number infra: number totalOpex: number ebitda: number ebitdaMarginPct: number customers: number employees: number // Detail costs adminCosts: number officeCosts: number travelCosts: number softwareLicenses: number depreciation: number interestExpense: number taxes: number netIncome: number ebit: number ihk: number foundingCosts: number // US GAAP aggregates rAndD: number sga: number } function fmt(v: number): string { if (Math.abs(v) >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M` if (Math.abs(v) >= 1_000) return `${(v / 1_000).toFixed(0)}k` return Math.round(v).toLocaleString('de-DE') } export default function AnnualPLTable({ results, lang, standard }: AnnualPLTableProps) { const de = lang === 'de' // Aggregate monthly results into annual const annualMap = new Map() for (const r of results) { if (!annualMap.has(r.year)) annualMap.set(r.year, []) annualMap.get(r.year)!.push(r) } const rows: AnnualRow[] = Array.from(annualMap.entries()).map(([year, months]) => { const revenue = months.reduce((s, m) => s + m.revenue_eur, 0) const cogs = months.reduce((s, m) => s + m.cogs_eur, 0) const grossProfit = revenue - cogs const personnel = months.reduce((s, m) => s + m.personnel_eur, 0) const marketing = months.reduce((s, m) => s + m.marketing_eur, 0) const infra = months.reduce((s, m) => s + m.infra_eur, 0) const adminCosts = months.reduce((s, m) => s + (m.admin_costs_eur || 0), 0) const officeCosts = months.reduce((s, m) => s + (m.office_costs_eur || 0), 0) const travelCosts = months.reduce((s, m) => s + (m.travel_costs_eur || 0), 0) const softwareLicenses = months.reduce((s, m) => s + (m.software_licenses_eur || 0), 0) const depreciation = months.reduce((s, m) => s + (m.depreciation_eur || 0), 0) const interestExpense = months.reduce((s, m) => s + (m.interest_expense_eur || 0), 0) const taxes = months.reduce((s, m) => s + (m.taxes_eur || 0), 0) const netIncome = months.reduce((s, m) => s + (m.net_income_eur || 0), 0) const ebit = months.reduce((s, m) => s + (m.ebit_eur || 0), 0) const ihk = months.reduce((s, m) => s + (m.ihk_eur || 0), 0) const foundingCosts = months.reduce((s, m) => s + (m.founding_costs_eur || 0), 0) const totalOpex = personnel + marketing + infra + adminCosts + officeCosts + travelCosts + softwareLicenses + ihk + foundingCosts const ebitda = grossProfit - totalOpex const lastMonth = months[months.length - 1] // US GAAP aggregates const rAndD = infra + softwareLicenses const sga = personnel + adminCosts + officeCosts + travelCosts + ihk + foundingCosts return { year, revenue, cogs, grossProfit, grossMarginPct: revenue > 0 ? (grossProfit / revenue) * 100 : 0, personnel, marketing, infra, totalOpex, ebitda, ebitdaMarginPct: revenue > 0 ? (ebitda / revenue) * 100 : 0, customers: lastMonth.total_customers, employees: lastMonth.employees_count, adminCosts, officeCosts, travelCosts, softwareLicenses, depreciation, interestExpense, taxes, netIncome, ebit, ihk, foundingCosts, rAndD, sga, } }) type LineItem = { label: string getValue: (r: AnnualRow) => number isBold?: boolean isPercent?: boolean isSeparator?: boolean isNegative?: boolean isSubRow?: boolean } const hgbLineItems: LineItem[] = [ { label: 'Umsatzerloese', getValue: r => r.revenue, isBold: true }, { label: '- Materialaufwand', getValue: r => r.cogs, isNegative: true }, { label: '- Personalaufwand', getValue: r => r.personnel, isNegative: true }, { label: '- Abschreibungen', getValue: r => r.depreciation, isNegative: true }, { label: '- Sonstige betr. Aufwendungen', getValue: r => r.marketing + r.adminCosts + r.officeCosts + r.travelCosts + r.softwareLicenses + r.ihk + r.foundingCosts + r.infra, isNegative: true, isSeparator: true }, { label: ' davon Marketing', getValue: r => r.marketing, isNegative: true, isSubRow: true }, { label: ' davon Steuerberater/Recht', getValue: r => r.adminCosts, isNegative: true, isSubRow: true }, { label: ' davon Buero/Telefon', getValue: r => r.officeCosts, isNegative: true, isSubRow: true }, { label: ' davon Software', getValue: r => r.softwareLicenses, isNegative: true, isSubRow: true }, { label: ' davon Reisekosten', getValue: r => r.travelCosts, isNegative: true, isSubRow: true }, { label: ' davon Infrastruktur', getValue: r => r.infra, isNegative: true, isSubRow: true }, { label: '= Betriebsergebnis (EBIT)', getValue: r => r.ebit, isBold: true, isSeparator: true }, { label: '- Zinsaufwand', getValue: r => r.interestExpense, isNegative: true }, { label: '= Ergebnis vor Steuern (EBT)', getValue: r => r.ebit - r.interestExpense, isBold: true, isSeparator: true }, { label: '- Steuern', getValue: r => r.taxes, isNegative: true }, { label: '= Jahresueberschuss', getValue: r => r.netIncome, isBold: true, isSeparator: true }, { label: 'Kunden (Jahresende)', getValue: r => r.customers }, { label: 'Mitarbeiter', getValue: r => r.employees }, ] const usgaapLineItems: LineItem[] = [ { label: 'Revenue', getValue: r => r.revenue, isBold: true }, { label: '- Cost of Revenue (COGS)', getValue: r => r.cogs, isNegative: true }, { label: '= Gross Profit', getValue: r => r.grossProfit, isBold: true, isSeparator: true }, { label: ' Gross Margin', getValue: r => r.grossMarginPct, isPercent: true }, { label: '- Sales & Marketing', getValue: r => r.marketing, isNegative: true }, { label: '- Research & Development', getValue: r => r.rAndD, isNegative: true }, { label: '- General & Administrative', getValue: r => r.sga, isNegative: true }, { label: '- Depreciation & Amortization', getValue: r => r.depreciation, isNegative: true }, { label: '= Operating Income (EBIT)', getValue: r => r.ebit, isBold: true, isSeparator: true }, { label: ' Operating Margin', getValue: r => r.revenue > 0 ? (r.ebit / r.revenue) * 100 : 0, isPercent: true }, { label: '- Interest Expense', getValue: r => r.interestExpense, isNegative: true }, { label: '= Income Before Tax (EBT)', getValue: r => r.ebit - r.interestExpense, isBold: true, isSeparator: true }, { label: '- Income Tax', getValue: r => r.taxes, isNegative: true }, { label: '= Net Income', getValue: r => r.netIncome, isBold: true, isSeparator: true }, { label: 'Customers (Year End)', getValue: r => r.customers }, { label: 'Employees', getValue: r => r.employees }, ] const lineItems = standard === 'hgb' ? hgbLineItems : usgaapLineItems return ( {rows.map(r => ( ))} {lineItems.map((item, idx) => ( {rows.map(r => { const val = item.getValue(r) return ( ) })} ))}
{standard === 'hgb' ? 'GuV-Position (HGB)' : 'P&L Line Item (US GAAP)'} {r.year}
{item.label} 0 && (item.label.includes('EBIT') || item.label.includes('Net Income') || item.label.includes('Jahresueberschuss') || item.label.includes('Betriebsergebnis')) ? 'text-emerald-400' : ''} ${!item.isPercent && !item.isBold && !item.isSubRow ? 'text-white/60' : 'text-white'} `} > {item.isPercent ? `${val.toFixed(1)}%` : (item.isNegative && val > 0 ? '-' : '') + fmt(Math.abs(val)) }
) }