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>
97 lines
3.8 KiB
TypeScript
97 lines
3.8 KiB
TypeScript
import Link from "next/link";
|
||
import { formatDistanceToNowStrict } from "date-fns";
|
||
import { cn } from "@/lib/utils";
|
||
import type { QcQueueEntry } from "@/lib/dashboard/ops";
|
||
|
||
/**
|
||
* What is waiting on someone to watch the frames, oldest first.
|
||
*
|
||
* Everything upstream of QC is worked by the farm; this is the one stage that
|
||
* only drains when a person sits down to it, which is why depth here is worth
|
||
* a panel of its own rather than a filter on the queue page.
|
||
*/
|
||
export function QcQueue({ entries, staleHours }: { entries: QcQueueEntry[]; staleHours: number }) {
|
||
const oldest = entries[0];
|
||
|
||
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">
|
||
{entries.length === 0
|
||
? "QC queue empty"
|
||
: `${entries.length} waiting on QC`}
|
||
{oldest && (
|
||
<span className="ml-2 text-xs font-normal text-zinc-500">
|
||
oldest {formatDistanceToNowStrict(oldest.since)}
|
||
</span>
|
||
)}
|
||
</h3>
|
||
<Link
|
||
href="/pipeline?status=READY_FOR_QC"
|
||
className="text-xs text-zinc-400 hover:text-amber-400"
|
||
>
|
||
Open QC →
|
||
</Link>
|
||
</div>
|
||
|
||
{entries.length === 0 ? (
|
||
<p className="px-4 py-10 text-center text-sm text-zinc-500">
|
||
Nothing is waiting on a QC pass.
|
||
</p>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-sm">
|
||
<thead>
|
||
<tr className="border-b border-zinc-800 bg-zinc-900/60 text-left text-xs text-zinc-400">
|
||
<th className="px-4 py-2.5 font-medium">Shot</th>
|
||
<th className="px-4 py-2.5 font-medium">Version</th>
|
||
<th className="px-4 py-2.5 font-medium">Waiting</th>
|
||
<th className="px-4 py-2.5 font-medium">Frames</th>
|
||
<th className="px-4 py-2.5 font-medium">Submitted by</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{entries.map((e) => {
|
||
const stale = Date.now() - e.since.getTime() > staleHours * 3600_000;
|
||
return (
|
||
<tr key={e.exportId} className="border-b border-zinc-800/60 hover:bg-zinc-900/40">
|
||
<td className="px-4 py-2.5">
|
||
<Link
|
||
href={`/pipeline/exports/${e.exportId}`}
|
||
className="font-mono text-[13px] font-medium text-white hover:text-amber-400"
|
||
>
|
||
{e.shotCode}
|
||
</Link>
|
||
<div className="text-xs text-zinc-500">
|
||
{e.projectCode}
|
||
{e.episode ? ` · ep ${e.episode}` : ""}
|
||
</div>
|
||
</td>
|
||
<td className="px-4 py-2.5 font-mono text-[13px] text-zinc-300">
|
||
{e.versionString}
|
||
</td>
|
||
<td
|
||
className={cn(
|
||
"px-4 py-2.5 tabular-nums whitespace-nowrap",
|
||
stale ? "text-amber-400" : "text-zinc-300"
|
||
)}
|
||
>
|
||
{formatDistanceToNowStrict(e.since)}
|
||
</td>
|
||
<td className="px-4 py-2.5 font-mono text-[13px] tabular-nums text-zinc-400 whitespace-nowrap">
|
||
{e.frameStart}–{e.frameEnd}
|
||
</td>
|
||
<td className="px-4 py-2.5 text-zinc-300 whitespace-nowrap">
|
||
{e.submittedBy ?? "—"}
|
||
</td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|