upgrade navigation and route prefetch responsiveness

This commit is contained in:
2026-03-01 20:45:08 -05:00
parent d6895f185f
commit dc84f34fe9
17 changed files with 1208 additions and 142 deletions

View File

@@ -1,5 +1,6 @@
'use client';
import { useQueryClient } from '@tanstack/react-query';
import Link from 'next/link';
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { format } from 'date-fns';
@@ -25,12 +26,14 @@ 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 { useLinkPrefetch } from '@/hooks/use-link-prefetch';
import {
formatCurrencyByScale,
formatPercent,
type NumberScaleUnit
} from '@/lib/format';
import { queryKeys } from '@/lib/query/keys';
import { companyAnalysisQueryOptions } from '@/lib/query/options';
import type { CompanyAnalysis } from '@/lib/types';
type StatementPeriodPoint = {
@@ -76,6 +79,16 @@ const FINANCIAL_VALUE_SCALE_OPTIONS: Array<{ value: NumberScaleUnit; label: stri
{ value: 'billions', label: 'Billions (B)' }
];
const CHART_TEXT = '#e8fff8';
const CHART_MUTED = '#b4ced9';
const CHART_GRID = 'rgba(126, 217, 255, 0.24)';
const CHART_TOOLTIP_BG = 'rgba(6, 17, 24, 0.95)';
const CHART_TOOLTIP_BORDER = 'rgba(123, 255, 217, 0.45)';
function renderLegendLabel(value: string) {
return <span style={{ color: CHART_TEXT }}>{value}</span>;
}
function formatShortDate(value: string) {
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) {
@@ -380,6 +393,8 @@ export default function FinancialsPage() {
function FinancialsPageContent() {
const { isPending, isAuthenticated } = useAuthGuard();
const searchParams = useSearchParams();
const queryClient = useQueryClient();
const { prefetchResearchTicker } = useLinkPrefetch();
const [tickerInput, setTickerInput] = useState('MSFT');
const [ticker, setTicker] = useState('MSFT');
@@ -405,11 +420,16 @@ function FinancialsPageContent() {
}, [searchParams]);
const loadFinancials = useCallback(async (symbol: string) => {
setLoading(true);
const options = companyAnalysisQueryOptions(symbol);
if (!queryClient.getQueryData(options.queryKey)) {
setLoading(true);
}
setError(null);
try {
const response = await getCompanyAnalysis(symbol);
const response = await queryClient.ensureQueryData(options);
setAnalysis(response.analysis);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unable to load financial history');
@@ -417,7 +437,7 @@ function FinancialsPageContent() {
} finally {
setLoading(false);
}
}, []);
}, [queryClient]);
useEffect(() => {
if (!isPending && isAuthenticated) {
@@ -535,8 +555,15 @@ function FinancialsPageContent() {
<AppShell
title="Financials"
subtitle="Explore 10-K and 10-Q fundamentals, profitability, and balance sheet dynamics by ticker."
activeTicker={analysis?.company.ticker ?? ticker}
actions={(
<Button variant="secondary" onClick={() => void loadFinancials(ticker)}>
<Button
variant="secondary"
onClick={() => {
void queryClient.invalidateQueries({ queryKey: queryKeys.companyAnalysis(ticker.trim().toUpperCase()) });
void loadFinancials(ticker);
}}
>
<RefreshCcw className="size-4" />
Refresh
</Button>
@@ -566,10 +593,20 @@ function FinancialsPageContent() {
</Button>
{analysis ? (
<>
<Link href={`/analysis?ticker=${analysis.company.ticker}`} className="text-sm text-[color:var(--accent)] hover:text-[color:var(--accent-strong)]">
<Link
href={`/analysis?ticker=${analysis.company.ticker}`}
onMouseEnter={() => prefetchResearchTicker(analysis.company.ticker)}
onFocus={() => prefetchResearchTicker(analysis.company.ticker)}
className="text-sm text-[color:var(--accent)] hover:text-[color:var(--accent-strong)]"
>
Open full analysis
</Link>
<Link href={`/filings?ticker=${analysis.company.ticker}`} className="text-sm text-[color:var(--accent)] hover:text-[color:var(--accent-strong)]">
<Link
href={`/filings?ticker=${analysis.company.ticker}`}
onMouseEnter={() => prefetchResearchTicker(analysis.company.ticker)}
onFocus={() => prefetchResearchTicker(analysis.company.ticker)}
className="text-sm text-[color:var(--accent)] hover:text-[color:var(--accent-strong)]"
>
Open filings stream
</Link>
</>
@@ -648,11 +685,36 @@ function FinancialsPageContent() {
<div className="h-[330px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartSeries}>
<CartesianGrid strokeDasharray="2 2" stroke="rgba(126, 217, 255, 0.2)" />
<XAxis dataKey="label" minTickGap={20} stroke="#8cb6c5" fontSize={12} />
<YAxis stroke="#8cb6c5" fontSize={12} tickFormatter={(value: number) => asAxisCurrencyTick(value, financialValueScale)} />
<Tooltip formatter={(value) => asTooltipCurrency(value, financialValueScale)} />
<Legend />
<CartesianGrid strokeDasharray="2 2" stroke={CHART_GRID} />
<XAxis
dataKey="label"
minTickGap={20}
stroke={CHART_MUTED}
fontSize={12}
axisLine={{ stroke: CHART_MUTED }}
tickLine={{ stroke: CHART_MUTED }}
tick={{ fill: CHART_MUTED }}
/>
<YAxis
stroke={CHART_MUTED}
fontSize={12}
axisLine={{ stroke: CHART_MUTED }}
tickLine={{ stroke: CHART_MUTED }}
tick={{ fill: CHART_MUTED }}
tickFormatter={(value: number) => asAxisCurrencyTick(value, financialValueScale)}
/>
<Tooltip
formatter={(value) => asTooltipCurrency(value, financialValueScale)}
contentStyle={{
backgroundColor: CHART_TOOLTIP_BG,
border: `1px solid ${CHART_TOOLTIP_BORDER}`,
borderRadius: '0.75rem'
}}
labelStyle={{ color: CHART_TEXT }}
itemStyle={{ color: CHART_TEXT }}
cursor={{ fill: 'rgba(104, 255, 213, 0.08)' }}
/>
<Legend wrapperStyle={{ paddingTop: '0.75rem' }} formatter={renderLegendLabel} />
<Bar dataKey="revenue" name="Revenue" fill="#68ffd5" radius={[4, 4, 0, 0]} />
<Bar dataKey="netIncome" name="Net Income" fill="#5fd3ff" radius={[4, 4, 0, 0]} />
</BarChart>
@@ -676,11 +738,36 @@ function FinancialsPageContent() {
<stop offset="95%" stopColor="#68ffd5" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="2 2" stroke="rgba(126, 217, 255, 0.2)" />
<XAxis dataKey="label" minTickGap={20} stroke="#8cb6c5" fontSize={12} />
<YAxis stroke="#8cb6c5" fontSize={12} tickFormatter={(value: number) => asAxisCurrencyTick(value, financialValueScale)} />
<Tooltip formatter={(value) => asTooltipCurrency(value, financialValueScale)} />
<Legend />
<CartesianGrid strokeDasharray="2 2" stroke={CHART_GRID} />
<XAxis
dataKey="label"
minTickGap={20}
stroke={CHART_MUTED}
fontSize={12}
axisLine={{ stroke: CHART_MUTED }}
tickLine={{ stroke: CHART_MUTED }}
tick={{ fill: CHART_MUTED }}
/>
<YAxis
stroke={CHART_MUTED}
fontSize={12}
axisLine={{ stroke: CHART_MUTED }}
tickLine={{ stroke: CHART_MUTED }}
tick={{ fill: CHART_MUTED }}
tickFormatter={(value: number) => asAxisCurrencyTick(value, financialValueScale)}
/>
<Tooltip
formatter={(value) => asTooltipCurrency(value, financialValueScale)}
contentStyle={{
backgroundColor: CHART_TOOLTIP_BG,
border: `1px solid ${CHART_TOOLTIP_BORDER}`,
borderRadius: '0.75rem'
}}
labelStyle={{ color: CHART_TEXT }}
itemStyle={{ color: CHART_TEXT }}
cursor={{ fill: 'rgba(104, 255, 213, 0.08)' }}
/>
<Legend wrapperStyle={{ paddingTop: '0.75rem' }} formatter={renderLegendLabel} />
<Area type="monotone" dataKey="totalAssets" name="Total Assets" stroke="#68ffd5" fill="url(#assetsGradient)" strokeWidth={2} />
<Line type="monotone" dataKey="cash" name="Cash" stroke="#8bd3ff" strokeWidth={2} dot={false} />
<Line type="monotone" dataKey="debt" name="Debt" stroke="#ffd08a" strokeWidth={2} dot={false} />
@@ -701,9 +788,24 @@ function FinancialsPageContent() {
<div className="h-[300px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={chartSeries}>
<CartesianGrid strokeDasharray="2 2" stroke="rgba(126, 217, 255, 0.2)" />
<XAxis dataKey="label" minTickGap={20} stroke="#8cb6c5" fontSize={12} />
<YAxis stroke="#8cb6c5" fontSize={12} tickFormatter={(value: number) => `${value.toFixed(0)}%`} />
<CartesianGrid strokeDasharray="2 2" stroke={CHART_GRID} />
<XAxis
dataKey="label"
minTickGap={20}
stroke={CHART_MUTED}
fontSize={12}
axisLine={{ stroke: CHART_MUTED }}
tickLine={{ stroke: CHART_MUTED }}
tick={{ fill: CHART_MUTED }}
/>
<YAxis
stroke={CHART_MUTED}
fontSize={12}
axisLine={{ stroke: CHART_MUTED }}
tickLine={{ stroke: CHART_MUTED }}
tick={{ fill: CHART_MUTED }}
tickFormatter={(value: number) => `${value.toFixed(0)}%`}
/>
<Tooltip
formatter={(value) => {
const normalized = normalizeTooltipValue(value);
@@ -714,8 +816,16 @@ function FinancialsPageContent() {
const numeric = Number(normalized);
return Number.isFinite(numeric) ? `${numeric.toFixed(2)}%` : 'n/a';
}}
contentStyle={{
backgroundColor: CHART_TOOLTIP_BG,
border: `1px solid ${CHART_TOOLTIP_BORDER}`,
borderRadius: '0.75rem'
}}
labelStyle={{ color: CHART_TEXT }}
itemStyle={{ color: CHART_TEXT }}
cursor={{ stroke: 'rgba(104, 255, 213, 0.35)', strokeWidth: 1 }}
/>
<Legend />
<Legend wrapperStyle={{ paddingTop: '0.75rem' }} formatter={renderLegendLabel} />
<Line type="monotone" dataKey="netMargin" name="Net Margin" stroke="#68ffd5" strokeWidth={2} dot={false} connectNulls />
<Line type="monotone" dataKey="debtToAssets" name="Debt / Assets" stroke="#ffb980" strokeWidth={2} dot={false} connectNulls />
</LineChart>