diff --git a/pitch-deck/app/api/data/route.ts b/pitch-deck/app/api/data/route.ts index cffd15a..0be22e3 100644 --- a/pitch-deck/app/api/data/route.ts +++ b/pitch-deck/app/api/data/route.ts @@ -39,6 +39,7 @@ export async function GET() { metrics: map.metrics || [], funding: (map.funding || [])[0] || null, products: map.products || [], + fp_scenarios: map.fm_scenarios || [], }) } diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx index f744a3e..ff13c67 100644 --- a/pitch-deck/components/PitchDeck.tsx +++ b/pitch-deck/components/PitchDeck.tsx @@ -72,14 +72,10 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, const [fabOpen, setFabOpen] = useState(false) const isWandeldarlehen = (data?.funding?.instrument || '').toLowerCase() === 'wandeldarlehen' - // For version previews: use the version's default FM scenario instead of base table default - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fmScenarios = (previewData as any)?.fm_scenarios as Array<{ id: string; is_default?: boolean }> | undefined - const preferredScenarioId = fmScenarios?.[0]?.is_default - ? fmScenarios[0].id - : fmScenarios?.length === 1 - ? fmScenarios[0].id - : null + // Derive fp_scenario IDs from version snapshot (fm_scenarios stores fp_scenario IDs directly) + const fpScenarios = data?.fp_scenarios || [] + const fpBaseScenarioId = fpScenarios.find(s => s.is_default)?.id ?? fpScenarios[0]?.id ?? null + const preferredScenarioId = fpBaseScenarioId // Skip cap-table slide for Wandeldarlehen versions useEffect(() => { @@ -163,7 +159,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, /> ) case 'executive-summary': - return + return case 'cover': return case 'problem': @@ -189,7 +185,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, case 'team': return case 'financials': - return + return case 'the-ask': return case 'cap-table': @@ -200,7 +196,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, case 'ai-qa': return case 'annex-assumptions': - return + return case 'annex-architecture': return case 'annex-gtm': @@ -216,7 +212,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, case 'annex-strategy': return case 'annex-finanzplan': - return + return case 'annex-glossary': return case 'risks': diff --git a/pitch-deck/components/slides/AssumptionsSlide.tsx b/pitch-deck/components/slides/AssumptionsSlide.tsx index 2d8b525..d9af730 100644 --- a/pitch-deck/components/slides/AssumptionsSlide.tsx +++ b/pitch-deck/components/slides/AssumptionsSlide.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState } from 'react' -import { Language } from '@/lib/types' +import { Language, FpScenarioRef } from '@/lib/types' import { t } from '@/lib/i18n' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' @@ -13,6 +13,7 @@ interface AssumptionsSlideProps { investorId?: string | null preferredScenarioId?: string | null isWandeldarlehen?: boolean + fpScenarios?: FpScenarioRef[] } interface SheetRow { @@ -72,7 +73,7 @@ async function loadScenarioKPIs(scenarioId: string | null): Promise { async function load() { - const baseId = isWandeldarlehen ? 'c0000000-0000-0000-0000-000000000200' : null - const bearId = isWandeldarlehen ? 'd0000000-0000-0000-0000-000000000201' : 'd0000000-0000-0000-0000-000000000301' - const bullId = isWandeldarlehen ? 'd0000000-0000-0000-0000-000000000202' : 'd0000000-0000-0000-0000-000000000302' + const scenarios = fpScenarios || [] + const find = (role: 'bear' | 'bull' | 'base') => { + if (role === 'base') return scenarios.find(s => s.is_default)?.id ?? null + return scenarios.find(s => s.name.toLowerCase().includes(role))?.id ?? null + } + const baseId = find('base') + const bearId = find('bear') + const bullId = find('bull') const [bear, base, bull] = await Promise.all([loadScenarioKPIs(bearId), loadScenarioKPIs(baseId), loadScenarioKPIs(bullId)]) setScenarioData({ bear, base, bull }) } load() - }, [isWandeldarlehen]) + }, [fpScenarios]) const bear = scenarioData?.bear || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' } const base = scenarioData?.base || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' } diff --git a/pitch-deck/components/slides/ExecutiveSummarySlide.tsx b/pitch-deck/components/slides/ExecutiveSummarySlide.tsx index 6b3fb99..a73f24d 100644 --- a/pitch-deck/components/slides/ExecutiveSummarySlide.tsx +++ b/pitch-deck/components/slides/ExecutiveSummarySlide.tsx @@ -16,15 +16,16 @@ interface ExecutiveSummarySlideProps { investorId?: string | null preferredScenarioId?: string | null isWandeldarlehen?: boolean + fpBaseScenarioId?: string | null } -export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId, isWandeldarlehen }: ExecutiveSummarySlideProps) { +export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId, isWandeldarlehen, fpBaseScenarioId }: ExecutiveSummarySlideProps) { const i = t(lang) const es = i.executiveSummary const de = lang === 'de' // Unternehmensentwicklung from fp_* tables (source of truth) - const { kpis: fpKPIs } = useFpKPIs(isWandeldarlehen) + const { kpis: fpKPIs } = useFpKPIs(fpBaseScenarioId) // Pipeline stats from DB const [pipelineStats, setPipelineStats] = useState>({}) diff --git a/pitch-deck/components/slides/FinancialsSlide.tsx b/pitch-deck/components/slides/FinancialsSlide.tsx index f067acf..45187b3 100644 --- a/pitch-deck/components/slides/FinancialsSlide.tsx +++ b/pitch-deck/components/slides/FinancialsSlide.tsx @@ -25,9 +25,10 @@ interface FinancialsSlideProps { investorId: string | null preferredScenarioId?: string | null isWandeldarlehen?: boolean + fpBaseScenarioId?: string | null } -export default function FinancialsSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen }: FinancialsSlideProps) { +export default function FinancialsSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen, fpBaseScenarioId }: FinancialsSlideProps) { const i = t(lang) const fm = useFinancialModel(investorId, preferredScenarioId) const [activeTab, setActiveTab] = useState('overview') @@ -38,7 +39,7 @@ export default function FinancialsSlide({ lang, investorId, preferredScenarioId, const lastResult = activeResults?.results[activeResults.results.length - 1] // KPI cards from fp_* tables (source of truth) - const { last: fpLast, kpis: fpKPIs } = useFpKPIs(isWandeldarlehen) + const { last: fpLast, kpis: fpKPIs } = useFpKPIs(fpBaseScenarioId) const kpiArr = fpLast?.arr || summary?.final_arr || 0 const kpiCustomers = fpLast?.customers || summary?.final_customers || 0 const kpiEbit = fpKPIs?.y2029?.ebit // First profitable year diff --git a/pitch-deck/components/slides/FinanzplanSlide.tsx b/pitch-deck/components/slides/FinanzplanSlide.tsx index 7d68a9c..af98b00 100644 --- a/pitch-deck/components/slides/FinanzplanSlide.tsx +++ b/pitch-deck/components/slides/FinanzplanSlide.tsx @@ -1,7 +1,7 @@ 'use client' import { useCallback, useEffect, useState } from 'react' -import { Language } from '@/lib/types' +import { Language, FpScenarioRef } from '@/lib/types' import ProjectionFooter from '../ui/ProjectionFooter' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' @@ -20,14 +20,16 @@ interface FinanzplanSlideProps { investorId?: string | null preferredScenarioId?: string | null isWandeldarlehen?: boolean + fpBaseScenarioId?: string | null + fpScenarios?: FpScenarioRef[] } -export default function FinanzplanSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen }: FinanzplanSlideProps) { +export default function FinanzplanSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen, fpBaseScenarioId, fpScenarios }: FinanzplanSlideProps) { const [sheets, setSheets] = useState([]) const [scenarios, setScenarios] = useState([]) const [openCats, setOpenCats] = useState>(new Set()) const toggleCat = (cat: string) => setOpenCats(prev => { const n = new Set(prev); n.has(cat) ? n.delete(cat) : n.add(cat); return n }) - const [selectedScenarioId, setSelectedScenarioId] = useState('') + const [selectedScenarioId, setSelectedScenarioId] = useState(fpBaseScenarioId ?? '') const [activeSheet, setActiveSheet] = useState('guv') const [rows, setRows] = useState([]) const [loading, setLoading] = useState(false) @@ -96,18 +98,19 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId, loadKPIs() }, [selectedScenarioId]) - // Load sheet list + scenarios + // Load sheet list; populate scenario selector from version data or API fallback useEffect(() => { fetch('/api/finanzplan', { cache: 'no-store' }) .then(r => r.json()) .then(data => { setSheets(data.sheets || []) - const scens: FpScenario[] = data.scenarios || [] + // Use version fp_scenarios if available, else fall back to API list + const scens: FpScenario[] = fpScenarios && fpScenarios.length > 0 + ? fpScenarios.map(s => ({ id: s.id, name: s.name, is_default: s.is_default ?? false, color: s.color ?? '#6366f1', description: s.description ?? '' })) + : data.scenarios || [] setScenarios(scens) - // Pick scenario: Wandeldarlehen version → WD scenario, otherwise default if (!selectedScenarioId) { - const wdScenario = isWandeldarlehen ? scens.find(s => s.name.toLowerCase().includes('wandeldarlehen') && !s.name.toLowerCase().includes('bear') && !s.name.toLowerCase().includes('bull')) : null - const def = wdScenario ?? scens.find(s => s.is_default) ?? scens[0] + const def = scens.find(s => s.is_default) ?? scens[0] if (def) setSelectedScenarioId(def.id) } }) diff --git a/pitch-deck/lib/hooks/useFpKPIs.ts b/pitch-deck/lib/hooks/useFpKPIs.ts index 9158971..8f18adb 100644 --- a/pitch-deck/lib/hooks/useFpKPIs.ts +++ b/pitch-deck/lib/hooks/useFpKPIs.ts @@ -31,14 +31,14 @@ interface SheetRow { * Loads annual KPIs directly from fp_* tables (source of truth). * Returns a map of year keys (y2026-y2030) to KPI objects. */ -export function useFpKPIs(isWandeldarlehen?: boolean) { +export function useFpKPIs(fpBaseScenarioId?: string | null) { const [kpis, setKpis] = useState>({}) const [loading, setLoading] = useState(true) useEffect(() => { async function load() { try { - const param = isWandeldarlehen ? '?scenarioId=c0000000-0000-0000-0000-000000000200' : '' + const param = fpBaseScenarioId ? `?scenarioId=${fpBaseScenarioId}` : '' const [guvRes, liqRes, persRes, kundenRes] = await Promise.all([ fetch(`/api/finanzplan/guv${param}`, { cache: 'no-store' }), fetch(`/api/finanzplan/liquiditaet${param}`, { cache: 'no-store' }), @@ -102,7 +102,7 @@ export function useFpKPIs(isWandeldarlehen?: boolean) { setLoading(false) } load() - }, [isWandeldarlehen]) + }, [fpBaseScenarioId]) // Use of Funds: compute spending breakdown m8-m24 (funding period) const [useOfFunds, setUseOfFunds] = useState>([]) @@ -110,7 +110,7 @@ export function useFpKPIs(isWandeldarlehen?: boolean) { useEffect(() => { async function loadUoF() { try { - const param = isWandeldarlehen ? '?scenarioId=c0000000-0000-0000-0000-000000000200' : '' + const param = fpBaseScenarioId ? `?scenarioId=${fpBaseScenarioId}` : '' const [persRes, betriebRes, investRes] = await Promise.all([ fetch(`/api/finanzplan/personalkosten${param}`, { cache: 'no-store' }), fetch(`/api/finanzplan/betriebliche${param}`, { cache: 'no-store' }), @@ -168,7 +168,7 @@ export function useFpKPIs(isWandeldarlehen?: boolean) { } catch { /* ignore */ } } loadUoF() - }, [isWandeldarlehen]) + }, [fpBaseScenarioId]) const last = kpis.y2030 return { kpis, loading, last, useOfFunds } diff --git a/pitch-deck/lib/types.ts b/pitch-deck/lib/types.ts index 17d17bf..cc1eae4 100644 --- a/pitch-deck/lib/types.ts +++ b/pitch-deck/lib/types.ts @@ -114,6 +114,14 @@ export interface PitchProduct { operating_cost_eur: number } +export interface FpScenarioRef { + id: string + name: string + is_default?: boolean + color?: string + description?: string +} + export interface PitchData { company: PitchCompany team: PitchTeamMember[] @@ -125,6 +133,7 @@ export interface PitchData { metrics: PitchMetric[] funding: PitchFunding products: PitchProduct[] + fp_scenarios?: FpScenarioRef[] } // Financial Model Types