wulf-pulse/components/mobile/AnalyzerFeedRow.tsx
lorentz 9c1a74009b feat(06-02): add AnalyzerStagePips, ConfidenceBadge, AnalyzerRowSkeleton, AnalyzerFeedRow components
- AnalyzerStagePips: 3-dot stage indicator with caret separators, sr-only accessibility label
- ConfidenceBadge: High/Medium/Low buckets (0.85/0.65) with green/amber/slate tones, dark mode
- AnalyzerRowSkeleton: Card-wrapped skeleton matching row shape (no border-l-4 per D-11)
- AnalyzerFeedRow: Full card row with header/title/summary/footer, Link to /mobile/analyzer/[id]
2026-05-03 21:30:17 -04:00

79 lines
2.9 KiB
TypeScript

'use client';
/* AnalyzerFeedRow — phase 06 (ANL-02).
* Purpose: Feed row card — header (ticket# + time-ago), title (1-line truncate),
* summary (2-line clamp), footer (stage pips left + confidence/review right).
* Entire card is a Link to /mobile/analyzer/[id]. Per D-10..D-12, D-17.
* Props: AnalyzerFeedRow type from @/app/api/mobile/analyzer/feed/route. */
import Link from 'next/link';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { AnalyzerStagePips } from '@/components/mobile/AnalyzerStagePips';
import { ConfidenceBadge } from '@/components/mobile/ConfidenceBadge';
import type { AnalyzerFeedRow as AnalyzerFeedRowType } from '@/app/api/mobile/analyzer/feed/route';
function relTime(ts: string | null): string {
if (!ts) return '—';
const diff = Date.now() - new Date(ts).getTime();
const m = Math.floor(diff / 60000);
if (m < 60) return `${m}m ago`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}h ago`;
return `${Math.floor(h / 24)}d ago`;
}
export interface AnalyzerFeedRowProps {
row: AnalyzerFeedRowType;
}
export function AnalyzerFeedRow({ row }: AnalyzerFeedRowProps) {
return (
<Link href={`/mobile/analyzer/${row.id}`} className="block">
<Card className="py-0 gap-0 cursor-pointer hover:bg-muted/50 active:bg-muted/50 transition-colors">
<CardContent className="px-4 py-4 space-y-1.5">
{/* Line 1 — header row */}
<div className="flex justify-between items-center">
<span className="bg-muted rounded px-1.5 py-0.5 text-[10px] font-mono font-semibold">
{row.ticketNumber}
</span>
<span className="text-[10px] text-muted-foreground">
{relTime(row.completedAt)}
</span>
</div>
{/* Line 2 — title */}
<p className="text-sm font-semibold leading-snug truncate">
{row.title}
</p>
{/* Line 3 — summary clamp (2 lines, fallback "—") */}
<p className="text-xs text-muted-foreground line-clamp-2">
{row.summary ?? '—'}
</p>
{/* Footer — pips left, badges right */}
<div className="flex justify-between items-center mt-1">
<AnalyzerStagePips
haikuUsed={row.haikuUsed}
sonnetUsed={row.sonnetUsed}
opusUsed={row.opusUsed}
/>
<div className="flex gap-2 items-center">
<ConfidenceBadge score={row.confidenceScore} />
{row.needsHumanReview && (
<Badge
variant="outline"
className="text-[10px] px-1.5 py-0.5 border-0 bg-destructive/10 text-destructive"
aria-label="Needs human review"
>
Review
</Badge>
)}
</div>
</div>
</CardContent>
</Card>
</Link>
);
}