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

@@ -8,14 +8,12 @@ import { AppShell } from '@/components/shell/app-shell';
import { Panel } from '@/components/ui/panel';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { StatusPill } from '@/components/ui/status-pill';
import { useLinkPrefetch } from '@/hooks/use-link-prefetch';
import { useAuthGuard } from '@/hooks/use-auth-guard';
import { useTaskPoller } from '@/hooks/use-task-poller';
import { deleteWatchlistItem, queueFilingSync, upsertWatchlistItem } from '@/lib/api';
import type { Task, WatchlistItem } from '@/lib/types';
import type { WatchlistItem } from '@/lib/types';
import { queryKeys } from '@/lib/query/keys';
import { taskQueryOptions, watchlistQueryOptions } from '@/lib/query/options';
import { watchlistQueryOptions } from '@/lib/query/options';
type FormState = {
ticker: string;
@@ -31,7 +29,6 @@ export default function WatchlistPage() {
const [items, setItems] = useState<WatchlistItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeTask, setActiveTask] = useState<Task | null>(null);
const [form, setForm] = useState<FormState>({ ticker: '', companyName: '', sector: '' });
const loadWatchlist = useCallback(async () => {
@@ -59,17 +56,6 @@ export default function WatchlistPage() {
}
}, [isPending, isAuthenticated, loadWatchlist]);
const polledTask = useTaskPoller({
taskId: activeTask?.id ?? null,
onTerminalState: () => {
setActiveTask(null);
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
void queryClient.invalidateQueries({ queryKey: ['filings'] });
}
});
const liveTask = polledTask ?? activeTask;
const submit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -90,10 +76,9 @@ export default function WatchlistPage() {
const queueSync = async (ticker: string) => {
try {
const { task } = await queueFilingSync({ ticker, limit: 20 });
const latest = await queryClient.fetchQuery(taskQueryOptions(task.id));
setActiveTask(latest.task);
await queueFilingSync({ ticker, limit: 20 });
void queryClient.invalidateQueries({ queryKey: queryKeys.recentTasks(20) });
void queryClient.invalidateQueries({ queryKey: ['filings'] });
} catch (err) {
setError(err instanceof Error ? err.message : `Failed to queue sync for ${ticker}`);
}
@@ -108,15 +93,6 @@ export default function WatchlistPage() {
title="Watchlist"
subtitle="Track symbols, company context, and trigger filing ingestion jobs from one surface."
>
{liveTask ? (
<Panel title="Queue Status">
<div className="flex items-center justify-between 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)]">{liveTask.task_type}</p>
<StatusPill status={liveTask.status} />
</div>
</Panel>
) : null}
<div className="grid grid-cols-1 gap-6 xl:grid-cols-[1.6fr_1fr]">
<Panel title="Symbols" subtitle="Your monitored universe.">
{error ? <p className="text-sm text-[#ffb5b5]">{error}</p> : null}