Phase 3a: shadcn init + Home/Case List view

Initialize shadcn/ui (radix-nova preset) and wire its semantic tokens
to the editorial navy/cream/gold palette so primitives inherit the
judicial voice without per-component overrides.

Replace the Phase 2 live-probe with a real dashboard: KPI tiles,
conic-gradient status donut (ported from the vanilla renderHero),
and a TanStack Table cases list with search + sort. Add useCase(n)
hook with 5s staleTime/refetchInterval to replace the old manual
polling loop when Case Detail ships next.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 16:04:30 +00:00
parent 0ee8e723bd
commit 51064f3a03
25 changed files with 6513 additions and 247 deletions

View File

@@ -34,6 +34,23 @@ export type Case = {
expected_outcome?: string | null;
created_at?: string;
updated_at?: string;
/* Present when loaded with detail=true */
document_count?: number;
processing_count?: number;
committee_type?: string | null;
hearing_date?: string | null;
};
export type CaseDetail = Case & {
documents?: Array<{
id: number | string;
filename: string;
category?: string | null;
status?: string;
uploaded_at?: string;
size_bytes?: number;
}>;
blocks?: Array<{ code: string; status?: string; char_count?: number }>;
};
export const casesKeys = {
@@ -52,3 +69,15 @@ export function useCases(detail = false) {
}),
});
}
export function useCase(caseNumber: string | undefined) {
return useQuery({
queryKey: casesKeys.detail(caseNumber ?? ""),
queryFn: ({ signal }) =>
apiRequest<CaseDetail>(`/api/cases/${caseNumber}`, { signal }),
enabled: Boolean(caseNumber),
/* Replaces the old 5s polling from vanilla index.html */
staleTime: 5_000,
refetchInterval: 5_000,
});
}

6
web-ui/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}