upgrade navigation and route prefetch responsiveness
This commit is contained in:
79
app/page.tsx
79
app/page.tsx
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import Link from 'next/link';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Activity, Bot, RefreshCw, Sparkles } from 'lucide-react';
|
||||
@@ -10,19 +11,23 @@ import { MetricCard } from '@/components/dashboard/metric-card';
|
||||
import { TaskFeed } from '@/components/dashboard/task-feed';
|
||||
import { StatusPill } from '@/components/ui/status-pill';
|
||||
import { useAuthGuard } from '@/hooks/use-auth-guard';
|
||||
import { useLinkPrefetch } from '@/hooks/use-link-prefetch';
|
||||
import { useTaskPoller } from '@/hooks/use-task-poller';
|
||||
import {
|
||||
getLatestPortfolioInsight,
|
||||
getPortfolioSummary,
|
||||
getTask,
|
||||
listFilings,
|
||||
listRecentTasks,
|
||||
listWatchlist,
|
||||
queuePortfolioInsights,
|
||||
queuePriceRefresh
|
||||
} from '@/lib/api';
|
||||
import type { PortfolioInsight, PortfolioSummary, Task } from '@/lib/types';
|
||||
import { formatCompactCurrency, formatCurrency, formatPercent } from '@/lib/format';
|
||||
import { queryKeys } from '@/lib/query/keys';
|
||||
import {
|
||||
filingsQueryOptions,
|
||||
latestPortfolioInsightQueryOptions,
|
||||
portfolioSummaryQueryOptions,
|
||||
recentTasksQueryOptions,
|
||||
taskQueryOptions,
|
||||
watchlistQueryOptions
|
||||
} from '@/lib/query/options';
|
||||
|
||||
type DashboardState = {
|
||||
summary: PortfolioSummary;
|
||||
@@ -48,22 +53,33 @@ const EMPTY_STATE: DashboardState = {
|
||||
|
||||
export default function CommandCenterPage() {
|
||||
const { isPending, isAuthenticated, session } = useAuthGuard();
|
||||
const queryClient = useQueryClient();
|
||||
const { prefetchPortfolioSurfaces } = useLinkPrefetch();
|
||||
const [state, setState] = useState<DashboardState>(EMPTY_STATE);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const summaryOptions = portfolioSummaryQueryOptions();
|
||||
const filingsOptions = filingsQueryOptions({ limit: 200 });
|
||||
const watchlistOptions = watchlistQueryOptions();
|
||||
const tasksOptions = recentTasksQueryOptions(20);
|
||||
const insightOptions = latestPortfolioInsightQueryOptions();
|
||||
|
||||
if (!queryClient.getQueryData(summaryOptions.queryKey)) {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const [summaryRes, filingsRes, watchlistRes, tasksRes, insightRes] = await Promise.all([
|
||||
getPortfolioSummary(),
|
||||
listFilings({ limit: 200 }),
|
||||
listWatchlist(),
|
||||
listRecentTasks(20),
|
||||
getLatestPortfolioInsight()
|
||||
queryClient.ensureQueryData(summaryOptions),
|
||||
queryClient.ensureQueryData(filingsOptions),
|
||||
queryClient.ensureQueryData(watchlistOptions),
|
||||
queryClient.ensureQueryData(tasksOptions),
|
||||
queryClient.ensureQueryData(insightOptions)
|
||||
]);
|
||||
|
||||
setState({
|
||||
@@ -78,7 +94,7 @@ export default function CommandCenterPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [queryClient]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending && isAuthenticated) {
|
||||
@@ -90,6 +106,10 @@ export default function CommandCenterPage() {
|
||||
taskId: activeTaskId,
|
||||
onTerminalState: () => {
|
||||
setActiveTaskId(null);
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.portfolioSummary() });
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.latestPortfolioInsight() });
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
|
||||
void queryClient.invalidateQueries({ queryKey: ['filings'] });
|
||||
void loadData();
|
||||
}
|
||||
});
|
||||
@@ -102,8 +122,10 @@ export default function CommandCenterPage() {
|
||||
try {
|
||||
const { task } = await queuePriceRefresh();
|
||||
setActiveTaskId(task.id);
|
||||
const latest = await getTask(task.id);
|
||||
const latest = await queryClient.fetchQuery(taskQueryOptions(task.id));
|
||||
setState((prev) => ({ ...prev, tasks: [latest.task, ...prev.tasks] }));
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.portfolioSummary() });
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to queue price refresh');
|
||||
}
|
||||
@@ -117,8 +139,10 @@ export default function CommandCenterPage() {
|
||||
try {
|
||||
const { task } = await queuePortfolioInsights();
|
||||
setActiveTaskId(task.id);
|
||||
const latest = await getTask(task.id);
|
||||
const latest = await queryClient.fetchQuery(taskQueryOptions(task.id));
|
||||
setState((prev) => ({ ...prev, tasks: [latest.task, ...prev.tasks] }));
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.latestPortfolioInsight() });
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to queue AI insight');
|
||||
}
|
||||
@@ -211,15 +235,34 @@ export default function CommandCenterPage() {
|
||||
<p className="panel-heading text-xs uppercase text-[color:var(--terminal-muted)]">Financials</p>
|
||||
<p className="mt-2 text-sm text-[color:var(--terminal-bright)]">Focus on multi-period filing metrics, margins, leverage, and balance sheet composition.</p>
|
||||
</Link>
|
||||
<Link className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-4 transition hover:border-[color:var(--line-strong)] hover:bg-[color:var(--panel-bright)]" href="/filings">
|
||||
<Link
|
||||
className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-4 transition hover:border-[color:var(--line-strong)] hover:bg-[color:var(--panel-bright)]"
|
||||
href="/filings"
|
||||
onMouseEnter={() => {
|
||||
void queryClient.prefetchQuery(filingsQueryOptions({ limit: 120 }));
|
||||
}}
|
||||
onFocus={() => {
|
||||
void queryClient.prefetchQuery(filingsQueryOptions({ limit: 120 }));
|
||||
}}
|
||||
>
|
||||
<p className="panel-heading text-xs uppercase text-[color:var(--terminal-muted)]">Filings</p>
|
||||
<p className="mt-2 text-sm text-[color:var(--terminal-bright)]">Sync SEC filings and trigger AI memo analysis.</p>
|
||||
</Link>
|
||||
<Link className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-4 transition hover:border-[color:var(--line-strong)] hover:bg-[color:var(--panel-bright)]" href="/portfolio">
|
||||
<Link
|
||||
className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-4 transition hover:border-[color:var(--line-strong)] hover:bg-[color:var(--panel-bright)]"
|
||||
href="/portfolio"
|
||||
onMouseEnter={() => prefetchPortfolioSurfaces()}
|
||||
onFocus={() => prefetchPortfolioSurfaces()}
|
||||
>
|
||||
<p className="panel-heading text-xs uppercase text-[color:var(--terminal-muted)]">Portfolio</p>
|
||||
<p className="mt-2 text-sm text-[color:var(--terminal-bright)]">Manage holdings and mark to market in real time.</p>
|
||||
</Link>
|
||||
<Link className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-4 transition hover:border-[color:var(--line-strong)] hover:bg-[color:var(--panel-bright)]" href="/watchlist">
|
||||
<Link
|
||||
className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-4 transition hover:border-[color:var(--line-strong)] hover:bg-[color:var(--panel-bright)]"
|
||||
href="/watchlist"
|
||||
onMouseEnter={() => prefetchPortfolioSurfaces()}
|
||||
onFocus={() => prefetchPortfolioSurfaces()}
|
||||
>
|
||||
<p className="panel-heading text-xs uppercase text-[color:var(--terminal-muted)]">Watchlist</p>
|
||||
<p className="mt-2 text-sm text-[color:var(--terminal-bright)]">Track priority tickers for monitoring and ingestion.</p>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user