This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/pitch-deck/components/ui/AnnualPLTable.tsx
BreakPilot Dev b464366341
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
feat: Add staged funding model, financial compute engine, annex slides and UI enhancements
Restructure financial plan from single 200k SAFE to realistic staged funding
(25k Stammkapital, 25k Angel, 200k Wandeldarlehen, 1M Series A = 1.25M total).
Add 60-month compute engine with CAPEX/OPEX accounting, cash constraints,
hardware financing (30% upfront / 70% leasing), and revenue-based hiring caps.
Rebuild TheAskSlide with 4-event funding timeline, update i18n (DE/EN),
chat agent core messages, and add 15 new annex/technology slides with
supporting UI components (KPICard, RunwayGauge, WaterfallChart, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:20:02 +01:00

233 lines
9.5 KiB
TypeScript

'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<number, FMResult[]>()
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 (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="overflow-x-auto"
>
<table className="w-full text-[11px]">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-2 pr-4 text-white/40 font-medium min-w-[200px]">
{standard === 'hgb'
? 'GuV-Position (HGB)'
: 'P&L Line Item (US GAAP)'}
</th>
{rows.map(r => (
<th key={r.year} className="text-right py-2 px-2 text-white/50 font-semibold min-w-[80px]">
{r.year}
</th>
))}
</tr>
</thead>
<tbody>
{lineItems.map((item, idx) => (
<tr
key={idx}
className={`${item.isSeparator ? 'border-t border-white/10' : ''}`}
>
<td className={`py-1.5 pr-4
${item.isBold ? 'text-white font-semibold' : 'text-white/50'}
${item.isPercent ? 'italic text-white/30' : ''}
${item.isSubRow ? 'text-[10px] text-white/30' : ''}
`}>
{item.label}
</td>
{rows.map(r => {
const val = item.getValue(r)
return (
<td
key={r.year}
className={`text-right py-1.5 px-2 font-mono
${item.isBold ? 'font-semibold' : ''}
${item.isPercent ? 'text-white/30 italic' : ''}
${item.isSubRow ? 'text-[10px] text-white/25' : ''}
${!item.isPercent && val < 0 ? 'text-red-400/80' : ''}
${!item.isPercent && val > 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))
}
</td>
)
})}
</tr>
))}
</tbody>
</table>
</motion.div>
)
}