6afbe82a29
Deploy / deploy (push) Successful in 6m51s
Completes the operations dashboard. QC queue: what is waiting on a person to watch the frames, oldest first. Every stage before QC is worked by the farm, so this is the only one that cannot drain itself - depth here is the best early warning that a delivery date is going to move, and it had no home outside a filter on the queue page. Deliveries: the last few packages, flagging FAILED and builds stuck in BUILDING. A failed build is otherwise silent - the exports stay READY_FOR_DELIVERY, nothing reaches the delivery volume, and nobody finds out until the client asks where the shots are. Whose court: versions sent to the client and unanswered, against shots with unresolved notes. Two numbers rather than one because they call for opposite responses - a fortnight-old review is a phone call, unactioned notes are a scheduling problem. The artist view now scopes to the artist throughout: the blocked list, and the counters above it, which otherwise quoted a studio-wide figure next to a personal list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
75 lines
3.1 KiB
TypeScript
75 lines
3.1 KiB
TypeScript
import Link from "next/link";
|
|
import { formatDistanceToNowStrict } from "date-fns";
|
|
import { cn } from "@/lib/utils";
|
|
import { DELIVERY_STATUS_STYLES } from "@/app/(dashboard)/pipeline/status-colors";
|
|
import type { DeliverySummary } from "@/lib/dashboard/ops";
|
|
|
|
/**
|
|
* The last few packages, and whether any of them went wrong.
|
|
*
|
|
* A failed build is otherwise silent — the exports stay READY_FOR_DELIVERY and
|
|
* nothing reaches the delivery volume, but no one finds out until the client
|
|
* asks where the shots are.
|
|
*/
|
|
export function RecentDeliveries({ deliveries }: { deliveries: DeliverySummary[] }) {
|
|
const problems = deliveries.filter((d) => d.status === "FAILED" || d.stuck).length;
|
|
|
|
return (
|
|
<div className="rounded-lg border border-zinc-800 bg-card overflow-hidden">
|
|
<div className="flex items-center justify-between gap-3 border-b border-zinc-800/60 px-4 py-3">
|
|
<h3 className="text-sm font-semibold text-white">
|
|
Recent packages
|
|
{problems > 0 && (
|
|
<span className="ml-2 text-xs font-normal text-red-400">
|
|
{problems} need{problems === 1 ? "s" : ""} attention
|
|
</span>
|
|
)}
|
|
</h3>
|
|
<Link href="/pipeline/deliveries" className="text-xs text-zinc-400 hover:text-amber-400">
|
|
All deliveries →
|
|
</Link>
|
|
</div>
|
|
|
|
{deliveries.length === 0 ? (
|
|
<p className="px-4 py-8 text-center text-sm text-zinc-500">No packages built yet.</p>
|
|
) : (
|
|
deliveries.map((d) => (
|
|
<Link
|
|
key={d.id}
|
|
href="/pipeline/deliveries"
|
|
className="flex items-center gap-3 border-b border-zinc-800/60 px-4 py-2.5 last:border-b-0 hover:bg-zinc-900/40"
|
|
>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="truncate text-[13px] text-white">
|
|
<span className="font-mono font-medium">#{d.packageNumber}</span>{" "}
|
|
<span className="text-zinc-500">
|
|
{d.projectCode}
|
|
{d.episode ? ` · ${d.episode}` : ""}
|
|
{d.kind === "MOV_REVIEW" ? " · review" : ""}
|
|
</span>
|
|
</div>
|
|
<div className="truncate text-xs text-zinc-500" title={d.errorMessage ?? undefined}>
|
|
{d.status === "FAILED" && d.errorMessage
|
|
? d.errorMessage
|
|
: `${d.itemCount} shot${d.itemCount === 1 ? "" : "s"} · ${d.atLabel} ${formatDistanceToNowStrict(d.at)} ago`}
|
|
</div>
|
|
</div>
|
|
<span
|
|
className={cn(
|
|
"shrink-0 rounded-full border px-2 py-0.5 text-xs whitespace-nowrap",
|
|
// A build that never finished reads as a problem, not as work
|
|
// still in progress.
|
|
d.stuck
|
|
? "bg-red-500/15 text-red-400 border-red-500/30"
|
|
: DELIVERY_STATUS_STYLES[d.status] ?? "bg-zinc-500/15 text-zinc-300 border-zinc-500/30"
|
|
)}
|
|
>
|
|
{d.stuck ? "STUCK" : d.status}
|
|
</span>
|
|
</Link>
|
|
))
|
|
)}
|
|
</div>
|
|
);
|
|
}
|