Add K/M/B scale toggles for financial displays

This commit is contained in:
2026-03-01 19:23:45 -05:00
parent 209b650658
commit b8309c9969
5 changed files with 519 additions and 88 deletions

View File

@@ -15,7 +15,13 @@ import { useAuthGuard } from '@/hooks/use-auth-guard';
import { useTaskPoller } from '@/hooks/use-task-poller';
import { getTask, listFilings, queueFilingAnalysis, queueFilingSync } from '@/lib/api';
import type { Filing, Task } from '@/lib/types';
import { formatCompactCurrency } from '@/lib/format';
import { formatCurrencyByScale, type NumberScaleUnit } from '@/lib/format';
const FINANCIAL_VALUE_SCALE_OPTIONS: Array<{ value: NumberScaleUnit; label: string }> = [
{ value: 'thousands', label: 'Thousands (K)' },
{ value: 'millions', label: 'Millions (M)' },
{ value: 'billions', label: 'Billions (B)' }
];
export default function FilingsPage() {
return (
@@ -39,6 +45,17 @@ function hasFinancialSnapshot(filing: Filing) {
return filing.filing_type === '10-K' || filing.filing_type === '10-Q';
}
function asScaledFinancialSnapshot(
value: number | null | undefined,
scale: NumberScaleUnit
) {
if (value === null || value === undefined) {
return 'n/a';
}
return formatCurrencyByScale(value, scale);
}
function resolveOriginalFilingUrl(filing: Filing) {
if (filing.filing_url) {
return filing.filing_url;
@@ -93,6 +110,7 @@ function FilingsPageContent() {
const [filterTickerInput, setFilterTickerInput] = useState('');
const [searchTicker, setSearchTicker] = useState('');
const [activeTask, setActiveTask] = useState<Task | null>(null);
const [financialValueScale, setFinancialValueScale] = useState<NumberScaleUnit>('millions');
useEffect(() => {
const ticker = searchParams.get('ticker');
@@ -168,6 +186,10 @@ function FilingsPageContent() {
return counts;
}, [filings]);
const selectedFinancialScaleLabel = useMemo(() => {
return FINANCIAL_VALUE_SCALE_OPTIONS.find((option) => option.value === financialValueScale)?.label ?? 'Millions (M)';
}, [financialValueScale]);
if (isPending || !isAuthenticated) {
return <div className="flex min-h-screen items-center justify-center text-sm text-[color:var(--terminal-muted)]">Opening filings stream...</div>;
}
@@ -248,7 +270,25 @@ function FilingsPageContent() {
</Panel>
</div>
<Panel title="Filing Ledger" subtitle={`${filings.length} records loaded${searchTicker ? ` for ${searchTicker}` : ''}.`}>
<Panel
title="Filing Ledger"
subtitle={`${filings.length} records loaded${searchTicker ? ` for ${searchTicker}` : ''}. Values shown in ${selectedFinancialScaleLabel}.`}
actions={(
<div className="flex flex-wrap justify-end gap-2">
{FINANCIAL_VALUE_SCALE_OPTIONS.map((option) => (
<Button
key={option.value}
type="button"
variant={option.value === financialValueScale ? 'primary' : 'ghost'}
className="px-2 py-1 text-xs"
onClick={() => setFinancialValueScale(option.value)}
>
{option.label}
</Button>
))}
</div>
)}
>
{error ? <p className="text-sm text-[#ffb5b5]">{error}</p> : null}
{loading ? (
<p className="text-sm text-[color:var(--terminal-muted)]">Fetching filings...</p>
@@ -282,7 +322,7 @@ function FilingsPageContent() {
<div className="rounded-md border border-[color:var(--line-weak)] px-2 py-1.5">
<dt className="text-[color:var(--terminal-muted)]">Financial Snapshot</dt>
<dd className="mt-1 text-[color:var(--terminal-bright)]">
{financialForm ? (revenue ? formatCompactCurrency(revenue) : 'n/a') : 'Qualitative filing'}
{financialForm ? asScaledFinancialSnapshot(revenue, financialValueScale) : 'Qualitative filing'}
</dd>
</div>
<div className="rounded-md border border-[color:var(--line-weak)] px-2 py-1.5">
@@ -351,7 +391,7 @@ function FilingsPageContent() {
</td>
<td>{filing.filing_type}</td>
<td>{formatFilingDate(filing.filing_date)}</td>
<td>{financialForm ? (revenue ? formatCompactCurrency(revenue) : 'n/a') : 'Qualitative filing'}</td>
<td>{financialForm ? asScaledFinancialSnapshot(revenue, financialValueScale) : 'Qualitative filing'}</td>
<td className="max-w-[18rem]">{filing.company_name}</td>
<td>{hasAnalysis ? 'Ready' : 'Not generated'}</td>
<td>