'use client'; import Link from 'next/link'; import { Suspense, useCallback, useEffect, useMemo, useState } from 'react'; import { format } from 'date-fns'; import { useSearchParams } from 'next/navigation'; import { Area, AreaChart, Bar, BarChart, CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import { ChartNoAxesCombined, RefreshCcw, Search } from 'lucide-react'; import { AppShell } from '@/components/shell/app-shell'; import { MetricCard } from '@/components/dashboard/metric-card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Panel } from '@/components/ui/panel'; import { useAuthGuard } from '@/hooks/use-auth-guard'; import { getCompanyAnalysis } from '@/lib/api'; import { formatCompactCurrency, formatCurrency, formatPercent } from '@/lib/format'; import type { CompanyAnalysis } from '@/lib/types'; type FinancialSeriesPoint = { filingDate: string; filingType: '10-K' | '10-Q' | '8-K'; label: string; revenue: number | null; netIncome: number | null; totalAssets: number | null; cash: number | null; debt: number | null; netMargin: number | null; debtToAssets: number | null; }; const AXIS_CURRENCY = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', notation: 'compact', maximumFractionDigits: 1 }); function formatShortDate(value: string) { const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) { return 'Unknown'; } return format(parsed, 'MMM yyyy'); } function formatLongDate(value: string) { const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) { return 'Unknown'; } return format(parsed, 'MMM dd, yyyy'); } function ratioPercent(numerator: number | null, denominator: number | null) { if (numerator === null || denominator === null || denominator === 0) { return null; } return (numerator / denominator) * 100; } function asDisplayCurrency(value: number | null) { return value === null ? 'n/a' : formatCurrency(value); } function asDisplayPercent(value: number | null) { return value === null ? 'n/a' : formatPercent(value); } function normalizeTooltipValue(value: unknown) { if (Array.isArray(value)) { const [first] = value; return typeof first === 'number' || typeof first === 'string' ? first : null; } if (typeof value === 'number' || typeof value === 'string') { return value; } return null; } function asTooltipCurrency(value: unknown) { const normalized = normalizeTooltipValue(value); if (normalized === null) { return 'n/a'; } const numeric = Number(normalized); if (!Number.isFinite(numeric)) { return 'n/a'; } return formatCompactCurrency(numeric); } export default function FinancialsPage() { return ( Loading financial terminal...}> ); } function FinancialsPageContent() { const { isPending, isAuthenticated } = useAuthGuard(); const searchParams = useSearchParams(); const [tickerInput, setTickerInput] = useState('MSFT'); const [ticker, setTicker] = useState('MSFT'); const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fromQuery = searchParams.get('ticker'); if (!fromQuery) { return; } const normalized = fromQuery.trim().toUpperCase(); if (!normalized) { return; } setTickerInput(normalized); setTicker(normalized); }, [searchParams]); const loadFinancials = useCallback(async (symbol: string) => { setLoading(true); setError(null); try { const response = await getCompanyAnalysis(symbol); setAnalysis(response.analysis); } catch (err) { setError(err instanceof Error ? err.message : 'Unable to load financial history'); setAnalysis(null); } finally { setLoading(false); } }, []); useEffect(() => { if (!isPending && isAuthenticated) { void loadFinancials(ticker); } }, [isPending, isAuthenticated, ticker, loadFinancials]); const financialSeries = useMemo(() => { return (analysis?.financials ?? []) .slice() .sort((a, b) => Date.parse(a.filingDate) - Date.parse(b.filingDate)) .map((entry) => ({ filingDate: entry.filingDate, filingType: entry.filingType, label: formatShortDate(entry.filingDate), revenue: entry.revenue ?? null, netIncome: entry.netIncome ?? null, totalAssets: entry.totalAssets ?? null, cash: entry.cash ?? null, debt: entry.debt ?? null, netMargin: ratioPercent(entry.netIncome ?? null, entry.revenue ?? null), debtToAssets: ratioPercent(entry.debt ?? null, entry.totalAssets ?? null) })); }, [analysis?.financials]); const latestSnapshot = financialSeries[financialSeries.length - 1] ?? null; const liquidityRatio = useMemo(() => { if (!latestSnapshot || latestSnapshot.cash === null || latestSnapshot.debt === null || latestSnapshot.debt === 0) { return null; } return latestSnapshot.cash / latestSnapshot.debt; }, [latestSnapshot]); const coverage = useMemo(() => { const total = financialSeries.length; const asCoverage = (entries: number) => { if (total === 0) { return '0%'; } return `${Math.round((entries / total) * 100)}%`; }; return { total, revenue: asCoverage(financialSeries.filter((point) => point.revenue !== null).length), netIncome: asCoverage(financialSeries.filter((point) => point.netIncome !== null).length), assets: asCoverage(financialSeries.filter((point) => point.totalAssets !== null).length), cash: asCoverage(financialSeries.filter((point) => point.cash !== null).length), debt: asCoverage(financialSeries.filter((point) => point.debt !== null).length) }; }, [financialSeries]); if (isPending || !isAuthenticated) { return
Loading financial terminal...
; } return ( void loadFinancials(ticker)}> Refresh )} >
{ event.preventDefault(); const normalized = tickerInput.trim().toUpperCase(); if (!normalized) { return; } setTicker(normalized); }} > setTickerInput(event.target.value.toUpperCase())} placeholder="Ticker (AAPL)" className="max-w-xs" /> {analysis ? ( <> Open full analysis Open filings stream ) : null}
{error ? (

{error}

) : null}
= 0} />
{loading ? (

Loading statement data...

) : financialSeries.length === 0 ? (

No parsed filing metrics available yet for this ticker.

) : (
AXIS_CURRENCY.format(value)} /> asTooltipCurrency(value)} />
)}
{loading ? (

Loading balance sheet data...

) : financialSeries.length === 0 ? (

No balance sheet metrics available yet.

) : (
AXIS_CURRENCY.format(value)} /> asTooltipCurrency(value)} />
)}
{loading ? (

Loading ratio trends...

) : financialSeries.length === 0 ? (

No ratio data available yet.

) : (
`${value.toFixed(0)}%`} /> { const normalized = normalizeTooltipValue(value); if (normalized === null) { return 'n/a'; } const numeric = Number(normalized); return Number.isFinite(numeric) ? `${numeric.toFixed(2)}%` : 'n/a'; }} />
)}
Revenue coverage
{coverage.revenue}
Net income coverage
{coverage.netIncome}
Asset coverage
{coverage.assets}
Cash coverage
{coverage.cash}
Debt coverage
{coverage.debt}
{loading ? (

Loading table...

) : financialSeries.length === 0 ? (

No financial rows are available for this ticker yet.

) : (
{financialSeries.map((point) => ( ))}
Filed Form Revenue Net Income Total Assets Cash Debt Net Margin
{formatLongDate(point.filingDate)} {point.filingType} {asDisplayCurrency(point.revenue)} = 0 ? 'text-[#96f5bf]' : 'text-[#ff9f9f]'}>{asDisplayCurrency(point.netIncome)} {asDisplayCurrency(point.totalAssets)} {asDisplayCurrency(point.cash)} {asDisplayCurrency(point.debt)} {asDisplayPercent(point.netMargin)}
)}
Financial lens: revenue + margin + balance sheet strength
); }