'use client' import { motion } from 'framer-motion' import { FMResult } from '@/lib/types' interface AnnualPLTableProps { results: FMResult[] lang: 'de' | 'en' } 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 } 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 }: AnnualPLTableProps) { // 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 totalOpex = personnel + marketing + infra const ebitda = grossProfit - totalOpex const lastMonth = months[months.length - 1] 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, } }) const de = lang === 'de' const lineItems: { label: string; key: keyof AnnualRow; isBold?: boolean; isPercent?: boolean; isSeparator?: boolean; isNegative?: boolean }[] = [ { label: de ? 'Umsatzerloese' : 'Revenue', key: 'revenue', isBold: true }, { label: de ? '- Herstellungskosten (COGS)' : '- Cost of Goods Sold', key: 'cogs', isNegative: true }, { label: de ? '= Rohertrag (Gross Profit)' : '= Gross Profit', key: 'grossProfit', isBold: true, isSeparator: true }, { label: de ? ' Rohertragsmarge' : ' Gross Margin', key: 'grossMarginPct', isPercent: true }, { label: de ? '- Personalkosten' : '- Personnel', key: 'personnel', isNegative: true }, { label: de ? '- Marketing & Vertrieb' : '- Marketing & Sales', key: 'marketing', isNegative: true }, { label: de ? '- Infrastruktur' : '- Infrastructure', key: 'infra', isNegative: true }, { label: de ? '= OpEx gesamt' : '= Total OpEx', key: 'totalOpex', isBold: true, isSeparator: true, isNegative: true }, { label: 'EBITDA', key: 'ebitda', isBold: true, isSeparator: true }, { label: de ? ' EBITDA-Marge' : ' EBITDA Margin', key: 'ebitdaMarginPct', isPercent: true }, { label: de ? 'Kunden (Jahresende)' : 'Customers (Year End)', key: 'customers' }, { label: de ? 'Mitarbeiter' : 'Employees', key: 'employees' }, ] return ( {rows.map(r => ( ))} {lineItems.map((item) => ( {rows.map(r => { const val = r[item.key] as number const isNeg = val < 0 || item.isNegative return ( ) })} ))}
{de ? 'GuV-Position' : 'P&L Line Item'} {r.year}
{item.label} 0 && item.key === 'ebitda' ? 'text-emerald-400' : ''} ${!item.isPercent && !item.isBold ? 'text-white/60' : 'text-white'} `} > {item.isPercent ? `${val.toFixed(1)}%` : (item.isNegative && val > 0 ? '-' : '') + fmt(Math.abs(val)) }
) }