Phase 3b: Case Detail view with workflow timeline
New dynamic route /cases/[caseNumber] wired to the FastAPI details endpoint, using Next 16's async params pattern with React's use() hook. TanStack Query handles 5s refetchInterval so the page self-updates during long-running processing without manual polling. New components: CaseHeader (breadcrumb + meta), WorkflowTimeline (5-phase RTL pipeline view), DocumentsPanel (categorized list). Tabs split overview/documents/actions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
73
web-ui/src/components/cases/documents-panel.tsx
Normal file
73
web-ui/src/components/cases/documents-panel.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import type { CaseDetail } from "@/lib/api/cases";
|
||||
|
||||
function formatSize(bytes?: number) {
|
||||
if (!bytes) return "";
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function categoryTone(category?: string | null) {
|
||||
switch (category) {
|
||||
case "appeal": return "bg-info-bg text-info border-info/40";
|
||||
case "response": return "bg-gold-wash text-gold-deep border-gold/40";
|
||||
case "decision": return "bg-success-bg text-success border-success/40";
|
||||
case "protocol": return "bg-warn-bg text-warn border-warn/40";
|
||||
default: return "bg-rule-soft text-ink-muted border-rule";
|
||||
}
|
||||
}
|
||||
|
||||
export function DocumentsPanel({ data }: { data?: CaseDetail }) {
|
||||
const docs = data?.documents ?? [];
|
||||
|
||||
if (docs.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-12 text-ink-muted">
|
||||
<div className="text-gold text-2xl mb-2" aria-hidden>❦</div>
|
||||
<p className="text-sm">אין מסמכים בתיק זה</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="max-h-[520px]">
|
||||
<ul className="divide-y divide-rule">
|
||||
{docs.map((doc) => (
|
||||
<li
|
||||
key={doc.id}
|
||||
className="py-3 flex items-start justify-between gap-4 hover:bg-gold-wash/30 transition-colors px-2 -mx-2 rounded"
|
||||
>
|
||||
<div className="flex-1 min-w-0 space-y-0.5">
|
||||
<div className="text-ink font-medium truncate" title={doc.filename}>
|
||||
{doc.filename}
|
||||
</div>
|
||||
<div className="text-[0.72rem] text-ink-muted flex items-center gap-3">
|
||||
{doc.size_bytes && (
|
||||
<span className="tabular-nums">{formatSize(doc.size_bytes)}</span>
|
||||
)}
|
||||
{doc.uploaded_at && (
|
||||
<span>
|
||||
{new Date(doc.uploaded_at).toLocaleDateString("he-IL")}
|
||||
</span>
|
||||
)}
|
||||
{doc.status && doc.status !== "ready" && (
|
||||
<span className="text-warn">{doc.status}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{doc.category && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`rounded-full px-2 py-0.5 text-[0.7rem] ${categoryTone(doc.category)}`}
|
||||
>
|
||||
{doc.category}
|
||||
</Badge>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user