src/features/reviews/workspace-activity.tsx
Linked repository file from this build.
"use client";
import { Badge } from "@/components/ui/badge";
import { roleLabels, workflowLabels, type Activity } from "./types";
import type { SnapReview } from "@/programs/snap-negative/types";
function ActivityItem({ activity, notes }: { activity: Activity; notes: SnapReview["notes"] }) {
const linkedNote = activity.noteId
? notes.find((note) => note.id === activity.noteId)
: undefined;
const policyQuote = activity.policyNote?.trim();
return (
<article className="rounded-md border px-3 py-2 text-sm">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="outline">{activity.kind}</Badge>
{activity.policyDecision && (
<Badge
variant={
activity.policyDecision === "approve"
? "default"
: activity.policyDecision === "conditional"
? "secondary"
: "destructive"
}
>
PEC {activity.policyDecision}
</Badge>
)}
<time className="text-muted-foreground" dateTime={activity.createdAt}>
{new Date(activity.createdAt).toLocaleString()}
</time>
<span>
{activity.actor.name} ({roleLabels[activity.actor.role]})
</span>
</div>
<p className="mt-2">{activity.description}</p>
{activity.from && activity.to && (
<p className="mt-1 text-muted-foreground">
{workflowLabels[activity.from]} → {workflowLabels[activity.to]}
</p>
)}
{linkedNote && (
<blockquote className="mt-2 whitespace-pre-wrap border-l-2 pl-3 text-muted-foreground italic">
{linkedNote.body}
</blockquote>
)}
{policyQuote && (
<blockquote className="mt-2 whitespace-pre-wrap border-l-2 border-primary/40 pl-3 text-muted-foreground italic">
Policy note: {policyQuote}
</blockquote>
)}
</article>
);
}
export function WorkspaceActivity({ review }: { review: SnapReview }) {
const items = [...review.activity].reverse();
return (
<div className="h-full min-h-0 space-y-3 overflow-auto">
{items.length === 0 ? (
<p className="text-sm text-muted-foreground">No activity recorded yet.</p>
) : (
items.map((item) => <ActivityItem key={item.id} activity={item} notes={review.notes} />)
)}
</div>
);
}