"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"; import { AppShell } from "@/components/shell/app-shell"; import { Panel } from "@/components/ui/panel"; import { Button } from "@/components/ui/button"; import { TaskFeed } from "@/components/dashboard/task-feed"; import { IndexCardRow } from "@/components/dashboard/index-card-row"; import { useAuthGuard } from "@/hooks/use-auth-guard"; import { useLinkPrefetch } from "@/hooks/use-link-prefetch"; import { queuePortfolioInsights, queuePriceRefresh } from "@/lib/api"; import { buildGraphingHref } from "@/lib/graphing/catalog"; 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, watchlistQueryOptions, } from "@/lib/query/options"; type DashboardState = { summary: PortfolioSummary; filingsCount: number; watchlistCount: number; tasks: Task[]; latestInsight: PortfolioInsight | null; }; const EMPTY_STATE: DashboardState = { summary: { positions: 0, total_value: "0", total_gain_loss: "0", total_cost_basis: "0", avg_return_pct: "0", }, filingsCount: 0, watchlistCount: 0, tasks: [], latestInsight: null, }; export default function CommandCenterPage() { const { isPending, isAuthenticated, session } = useAuthGuard(); const queryClient = useQueryClient(); const { prefetchPortfolioSurfaces } = useLinkPrefetch(); const [state, setState] = useState(EMPTY_STATE); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const loadData = useCallback(async () => { 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([ queryClient.ensureQueryData(summaryOptions), queryClient.ensureQueryData(filingsOptions), queryClient.ensureQueryData(watchlistOptions), queryClient.ensureQueryData(tasksOptions), queryClient.ensureQueryData(insightOptions), ]); setState({ summary: summaryRes.summary, filingsCount: filingsRes.filings.length, watchlistCount: watchlistRes.items.length, tasks: tasksRes.tasks, latestInsight: insightRes.insight, }); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load dashboard"); } finally { setLoading(false); } }, [queryClient]); useEffect(() => { if (!isPending && isAuthenticated) { void loadData(); } }, [isPending, isAuthenticated, loadData]); const headerActions = (
); const signedGain = useMemo(() => { const gain = Number(state.summary.total_gain_loss ?? 0); return gain >= 0 ? `+${formatCurrency(gain)}` : formatCurrency(gain); }, [state.summary.total_gain_loss]); const indexCards = useMemo( () => [ { label: "Portfolio Value", value: formatCurrency(state.summary.total_value), delta: formatCompactCurrency(state.summary.total_cost_basis), }, { label: "Unrealized P&L", value: signedGain, delta: formatPercent(state.summary.avg_return_pct), positive: Number(state.summary.total_gain_loss) >= 0, }, { label: "Filings", value: String(state.filingsCount), delta: "Last 200", }, { label: "Coverage", value: String(state.watchlistCount), delta: `${state.summary.positions} active`, }, ], [state, signedGain], ); if (isPending || !isAuthenticated) { return (
Booting secure terminal...
); } return ( {error ? (
{error}
) : null}

Recent Tasks

Queue Processor
{loading ? (

Loading...

) : ( )}

AI Brief

{state.latestInsight ? (
{state.latestInsight.provider} :: {state.latestInsight.model}
) : null}
{loading ? (

Loading...

) : state.latestInsight ? (

{state.latestInsight.content}

) : (

No AI brief yet. Queue one from the action bar.

)}

Quick Links

Overview

Company analysis, price, valuation, and developments.

Financials

Multi-period metrics, margins, and balance sheet.

Graphing

Compare normalized metrics across companies.

{ void queryClient.prefetchQuery( filingsQueryOptions({ limit: 120 }), ); }} onFocus={() => { void queryClient.prefetchQuery( filingsQueryOptions({ limit: 120 }), ); }} >

Filings

SEC filings and AI memo analysis.

prefetchPortfolioSurfaces()} onFocus={() => prefetchPortfolioSurfaces()} >

Portfolio

Manage positions and mark to market.

prefetchPortfolioSurfaces()} onFocus={() => prefetchPortfolioSurfaces()} >

Coverage

Track research status and filing freshness.

Runtime: {loading ? "syncing" : "stable"}
); }