import { redirect, notFound } from 'next/navigation' import pool from '@/lib/db' import { getAdminFromCookie } from '@/lib/admin-auth' import { Language, PitchData, PitchCompany, PitchTeamMember, PitchFinancial, PitchMarket, PitchCompetitor, PitchFeature, PitchMilestone, PitchMetric, PitchFunding, PitchProduct, FpScenarioRef, FMResult, FMAssumption, } from '@/lib/types' import { finanzplanToFMResults } from '@/lib/finanzplan/adapter' import PrintDeck from './_components/PrintDeck' interface Ctx { params: Promise<{ versionId: string }> searchParams: Promise<{ financial?: string; lang?: string }> } export default async function PitchPrintPage({ params, searchParams }: Ctx) { const admin = await getAdminFromCookie() if (!admin) redirect('/pitch-admin/login') const { versionId } = await params const { financial: finParam, lang: langParam } = await searchParams const financial = finParam === 'true' || finParam === '1' const lang: Language = langParam === 'en' ? 'en' : 'de' // Version metadata const verRes = await pool.query( `SELECT name FROM pitch_versions WHERE id = $1`, [versionId], ) if (verRes.rows.length === 0) notFound() const versionName: string = verRes.rows[0].name // Version data tables (snapshot) const dataRes = await pool.query( `SELECT table_name, data FROM pitch_version_data WHERE version_id = $1`, [versionId], ) const map: Record = {} for (const row of dataRes.rows) { map[row.table_name] = typeof row.data === 'string' ? JSON.parse(row.data) : row.data } if (Object.keys(map).length === 0) { return (

Version has no data

Please make sure the version has been populated with pitch data before exporting.

← Back to version
) } const pitchData: PitchData = { company: ((map.company || [])[0] || {}) as PitchCompany, team: (map.team || []) as PitchTeamMember[], financials: (map.financials || []) as PitchFinancial[], market: (map.market || []) as PitchMarket[], competitors: (map.competitors || []) as PitchCompetitor[], features: (map.features || []) as PitchFeature[], milestones: (map.milestones || []) as PitchMilestone[], metrics: (map.metrics || []) as PitchMetric[], funding: ((map.funding || [])[0] || {}) as PitchFunding, products: (map.products || []) as PitchProduct[], fp_scenarios: (map.fm_scenarios || []) as FpScenarioRef[], } // Always fetch FM results + assumptions so the standard PDF can render the // annex-finanzplan slide. The `financial` flag only adds the extra detail // P&L page and the cap-table page. // // Data source: the live `fp_*` tables (same as the interactive deck), bridged // to FMResult[] via finanzplanToFMResults. The legacy `pitch_fm_results` table // is no longer populated by the current pipeline. let fmResults: FMResult[] = [] let fmAssumptions: FMAssumption[] = [] const scenarios = (map.fm_scenarios || []) as FpScenarioRef[] const defaultScenario = scenarios.find(s => s.is_default) ?? scenarios[0] ?? null // Snapshot stores fp_scenario IDs under `fm_scenarios`; fall back to the live // default fp scenario if the snapshot is empty (older versions). let scenarioId: string | null = defaultScenario?.id ? String(defaultScenario.id) : null if (!scenarioId) { const liveRes = await pool.query(`SELECT id FROM fp_scenarios WHERE is_default = true LIMIT 1`) scenarioId = liveRes.rows[0]?.id ? String(liveRes.rows[0].id) : null } if (scenarioId) { try { const fpResponse = await finanzplanToFMResults(pool, scenarioId) fmResults = fpResponse.results } catch { fmResults = [] } } const rawAssumptions = (map.fm_assumptions || []) as Array> fmAssumptions = rawAssumptions.map(a => ({ ...a, value: typeof a.value === 'string' ? JSON.parse(a.value as string) : a.value, })) as FMAssumption[] return ( ) }