feat: migrate task jobs to workflow notifications + timeline

This commit is contained in:
2026-03-02 14:29:31 -05:00
parent 36c4ed2ee2
commit d81a681905
33 changed files with 2437 additions and 292 deletions

View File

@@ -9,10 +9,8 @@ import { Panel } from '@/components/ui/panel';
import { Button } from '@/components/ui/button';
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 {
queuePortfolioInsights,
queuePriceRefresh
@@ -25,7 +23,6 @@ import {
latestPortfolioInsightQueryOptions,
portfolioSummaryQueryOptions,
recentTasksQueryOptions,
taskQueryOptions,
watchlistQueryOptions
} from '@/lib/query/options';
@@ -58,7 +55,6 @@ export default function CommandCenterPage() {
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 () => {
const summaryOptions = portfolioSummaryQueryOptions();
@@ -102,30 +98,16 @@ export default function CommandCenterPage() {
}
}, [isPending, isAuthenticated, loadData]);
const trackedTask = useTaskPoller({
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();
}
});
const headerActions = (
<>
<Button
variant="secondary"
onClick={async () => {
try {
const { task } = await queuePriceRefresh();
setActiveTaskId(task.id);
const latest = await queryClient.fetchQuery(taskQueryOptions(task.id));
setState((prev) => ({ ...prev, tasks: [latest.task, ...prev.tasks] }));
await queuePriceRefresh();
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
void queryClient.invalidateQueries({ queryKey: queryKeys.portfolioSummary() });
await loadData();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to queue price refresh');
}
@@ -137,12 +119,10 @@ export default function CommandCenterPage() {
<Button
onClick={async () => {
try {
const { task } = await queuePortfolioInsights();
setActiveTaskId(task.id);
const latest = await queryClient.fetchQuery(taskQueryOptions(task.id));
setState((prev) => ({ ...prev, tasks: [latest.task, ...prev.tasks] }));
await queuePortfolioInsights();
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
void queryClient.invalidateQueries({ queryKey: queryKeys.latestPortfolioInsight() });
await loadData();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to queue AI insight');
}
@@ -169,18 +149,6 @@ export default function CommandCenterPage() {
subtitle={`Welcome back${session?.user?.name ? `, ${session.user.name}` : ''}. Review tasks, portfolio health, and AI outputs.`}
actions={headerActions}
>
{activeTaskId && trackedTask ? (
<Panel title="Live Task" subtitle={`Task ${activeTaskId.slice(0, 8)} is active.`}>
<div className="flex items-center justify-between gap-3 rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-3 py-2">
<p className="text-sm text-[color:var(--terminal-bright)]">{trackedTask.task_type.replace('_', ' ')}</p>
<StatusPill status={trackedTask.status} />
</div>
{trackedTask.error ? (
<p className="mt-3 text-sm text-[#ff9898]">{trackedTask.error}</p>
) : null}
</Panel>
) : null}
{error ? (
<Panel>
<p className="text-sm text-[#ffb5b5]">{error}</p>