stemedb/applications/stemedb-dashboard/src/components/scans/finding-detail-sheet.tsx
jordan c849627620 feat: add Aphoria dashboard scans and corpus UI
- Add scans panel with finding details, verdict badges, and filters
- Add corpus panel for managing knowledge sources
- Add scan cache for API state management
- Update sidebar navigation with new routes
- Extend API types for scans and corpus endpoints
- Add .aphoria/ to gitignore (contains project keys)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 15:56:49 -07:00

168 lines
6.8 KiB
TypeScript

"use client";
import type { FindingDto } from "@/lib/api";
import { VerdictBadge } from "./verdict-badge";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from "@/components/ui/sheet";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { FileCode, AlertTriangle, BookOpen, Shield } from "lucide-react";
interface FindingDetailSheetProps {
finding: FindingDto | null;
isOpen: boolean;
onClose: () => void;
}
export function FindingDetailSheet({ finding, isOpen, onClose }: FindingDetailSheetProps) {
if (!finding) return null;
return (
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
<SheetContent className="overflow-y-auto">
<SheetHeader className="pb-4">
<div className="flex items-center gap-2">
<VerdictBadge verdict={finding.verdict} />
</div>
<SheetTitle className="text-lg font-semibold break-words">
{finding.concept_path}
</SheetTitle>
<SheetDescription className="text-sm">
<span className="font-mono">.{finding.predicate}</span>
</SheetDescription>
</SheetHeader>
<div className="space-y-6">
{/* Code Location */}
<section>
<h3 className="flex items-center gap-2 text-sm font-medium text-foreground mb-3">
<FileCode className="h-4 w-4" />
Source Location
</h3>
<div className="rounded-md bg-muted p-3 space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">File:</span>
<code className="font-mono text-foreground">{finding.file}</code>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Line:</span>
<span className="font-mono text-foreground">{finding.line}</span>
</div>
<Separator className="my-2" />
<div>
<span className="text-sm text-muted-foreground block mb-1">Value in code:</span>
<code className="block bg-background p-2 rounded font-mono text-sm break-all">
{finding.code_value}
</code>
</div>
</div>
</section>
{/* Conflict Details */}
{finding.conflicts.length > 0 && (
<section>
<h3 className="flex items-center gap-2 text-sm font-medium text-foreground mb-3">
<AlertTriangle className="h-4 w-4 text-amber-500" />
Conflicting Sources ({finding.conflicts.length})
</h3>
<div className="space-y-3">
{finding.conflicts.map((conflict, index) => (
<div
key={index}
className="rounded-md border border-border bg-card p-3 space-y-2"
>
<div className="flex items-center justify-between">
<Badge variant="outline" className="text-xs">
{conflict.source_class}
</Badge>
{conflict.policy_source && (
<span className="text-xs text-muted-foreground">
via {conflict.policy_source.pack_name} v{conflict.policy_source.pack_version}
</span>
)}
</div>
<div className="text-sm">
<span className="text-muted-foreground">Expected: </span>
<code className="font-mono bg-muted px-1 rounded">{conflict.value}</code>
</div>
{conflict.citation && (
<div className="flex items-start gap-2 text-xs text-muted-foreground">
<BookOpen className="h-3 w-3 mt-0.5 flex-shrink-0" />
<span>{conflict.citation}</span>
</div>
)}
</div>
))}
</div>
</section>
)}
{/* Acknowledgment */}
{finding.acknowledgment && (
<section>
<h3 className="flex items-center gap-2 text-sm font-medium text-foreground mb-3">
<Shield className="h-4 w-4 text-blue-500" />
Acknowledged
</h3>
<div className="rounded-md bg-blue-500/10 border border-blue-500/20 p-3 space-y-2">
<div className="text-sm">
<span className="text-muted-foreground">By: </span>
<span className="text-foreground">{finding.acknowledgment.by}</span>
</div>
<div className="text-sm">
<span className="text-muted-foreground">Reason: </span>
<span className="text-foreground">{finding.acknowledgment.reason}</span>
</div>
<div className="text-xs text-muted-foreground">
{finding.acknowledgment.timestamp}
</div>
</div>
</section>
)}
{/* Debug Trace */}
{finding.trace && (
<section>
<h3 className="text-sm font-medium text-foreground mb-3">
Resolution Trace
</h3>
<div className="rounded-md bg-muted p-3 space-y-2 text-xs font-mono">
<div>
<span className="text-muted-foreground">Code claim: </span>
<span className="text-foreground">{finding.trace.code_claim}</span>
</div>
<div>
<span className="text-muted-foreground">Authority match: </span>
<span className="text-foreground">{finding.trace.authority_match}</span>
</div>
<div>
<span className="text-muted-foreground">Authority tier: </span>
<span className="text-foreground">{finding.trace.authority_tier}</span>
</div>
<Separator className="my-2" />
<div>
<span className="text-muted-foreground">Resolution: </span>
<span className="text-foreground">{finding.trace.resolution}</span>
</div>
</div>
</section>
)}
{/* Conflict Score */}
<section>
<div className="flex justify-between items-center text-sm">
<span className="text-muted-foreground">Conflict Score</span>
<span className="font-mono">{(finding.conflict_score * 100).toFixed(1)}%</span>
</div>
</section>
</div>
</SheetContent>
</Sheet>
);
}