- 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]
79 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|