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

11
lib/query/keys.ts Normal file
View File

@@ -0,0 +1,11 @@
export const queryKeys = {
companyAnalysis: (ticker: string) => ['analysis', ticker] as const,
filings: (ticker: string | null, limit: number) => ['filings', ticker ?? '', limit] as const,
report: (accessionNumber: string) => ['report', accessionNumber] as const,
watchlist: () => ['watchlist'] as const,
holdings: () => ['portfolio', 'holdings'] as const,
portfolioSummary: () => ['portfolio', 'summary'] as const,
latestPortfolioInsight: () => ['portfolio', 'insights', 'latest'] as const,
task: (taskId: string) => ['tasks', 'detail', taskId] as const,
recentTasks: (limit: number) => ['tasks', 'recent', limit] as const
};

92
lib/query/options.ts Normal file
View File

@@ -0,0 +1,92 @@
import { queryOptions } from '@tanstack/react-query';
import {
getCompanyAiReport,
getCompanyAnalysis,
getLatestPortfolioInsight,
getPortfolioSummary,
getTask,
listFilings,
listHoldings,
listRecentTasks,
listWatchlist
} from '@/lib/api';
import { queryKeys } from '@/lib/query/keys';
export function companyAnalysisQueryOptions(ticker: string) {
const normalizedTicker = ticker.trim().toUpperCase();
return queryOptions({
queryKey: queryKeys.companyAnalysis(normalizedTicker),
queryFn: () => getCompanyAnalysis(normalizedTicker),
staleTime: 120_000
});
}
export function filingsQueryOptions(input: { ticker?: string; limit?: number } = {}) {
const normalizedTicker = input.ticker?.trim().toUpperCase() ?? null;
const limit = input.limit ?? 120;
return queryOptions({
queryKey: queryKeys.filings(normalizedTicker, limit),
queryFn: () => listFilings({ ticker: normalizedTicker ?? undefined, limit }),
staleTime: 60_000
});
}
export function aiReportQueryOptions(accessionNumber: string) {
const normalizedAccession = accessionNumber.trim();
return queryOptions({
queryKey: queryKeys.report(normalizedAccession),
queryFn: () => getCompanyAiReport(normalizedAccession),
staleTime: 300_000
});
}
export function watchlistQueryOptions() {
return queryOptions({
queryKey: queryKeys.watchlist(),
queryFn: () => listWatchlist(),
staleTime: 30_000
});
}
export function holdingsQueryOptions() {
return queryOptions({
queryKey: queryKeys.holdings(),
queryFn: () => listHoldings(),
staleTime: 30_000
});
}
export function portfolioSummaryQueryOptions() {
return queryOptions({
queryKey: queryKeys.portfolioSummary(),
queryFn: () => getPortfolioSummary(),
staleTime: 30_000
});
}
export function latestPortfolioInsightQueryOptions() {
return queryOptions({
queryKey: queryKeys.latestPortfolioInsight(),
queryFn: () => getLatestPortfolioInsight(),
staleTime: 30_000
});
}
export function taskQueryOptions(taskId: string) {
return queryOptions({
queryKey: queryKeys.task(taskId),
queryFn: () => getTask(taskId),
staleTime: 5_000
});
}
export function recentTasksQueryOptions(limit = 20) {
return queryOptions({
queryKey: queryKeys.recentTasks(limit),
queryFn: () => listRecentTasks(limit),
staleTime: 5_000
});
}

View File

@@ -158,3 +158,21 @@ export type CompanyAnalysis = {
filings: Filing[];
aiReports: CompanyAiReport[];
};
export type NavGroup = 'overview' | 'research' | 'portfolio';
export type NavMatchMode = 'exact' | 'prefix';
export type NavItem = {
id: string;
href: string;
label: string;
group: NavGroup;
matchMode: NavMatchMode;
preserveTicker?: boolean;
mobilePrimary?: boolean;
};
export type ActiveContext = {
pathname: string;
activeTicker: string | null;
};