Files
vfxreview/components/dashboard/WhoseCourt.tsx
T
twotalesanimation 6afbe82a29
Deploy / deploy (push) Successful in 6m51s
feat(dashboard): QC queue, deliveries and whose-court panels
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>
2026-09-10 05:58:08 +02:00

71 lines
2.1 KiB
TypeScript

import Link from "next/link";
import type { WhoseCourt as WhoseCourtData } from "@/lib/dashboard/ops";
/**
* Which waits are the client's and which are ours.
*
* Two numbers rather than one, because they call for opposite responses: a
* version unanswered for a fortnight is a phone call, while notes nobody has
* picked up is a scheduling problem.
*/
export function WhoseCourt({ court }: { court: WhoseCourtData }) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 rounded-lg border border-zinc-800 bg-card overflow-hidden">
<Side
title="Waiting on the client"
subtitle="Sent for review, no verdict yet"
side={court.client}
empty="Nothing is with the client."
className="border-b border-zinc-800/60 sm:border-b-0 sm:border-r"
/>
<Side
title="Waiting on us"
subtitle="Notes in, still unresolved"
side={court.us}
empty="No open notes."
/>
</div>
);
}
function Side({
title,
subtitle,
side,
empty,
className,
}: {
title: string;
subtitle: string;
side: WhoseCourtData["client"];
empty: string;
className?: string;
}) {
return (
<div className={`px-4 py-3 ${className ?? ""}`}>
<h4 className="text-xs font-semibold text-white">{title}</h4>
<p className="text-[11px] text-zinc-500">{subtitle}</p>
<b className="mt-2 mb-2 block font-mono text-2xl font-bold leading-none tabular-nums text-white">
{side.count}
</b>
{side.items.length === 0 ? (
<p className="text-xs text-zinc-500">{empty}</p>
) : (
<ul className="flex flex-col gap-1.5">
{side.items.map((item) => (
<li key={item.id} className="flex items-baseline justify-between gap-3 text-xs">
<Link
href={item.href}
className="truncate font-mono text-[11.5px] text-zinc-200 hover:text-amber-400"
>
{item.shotCode}
</Link>
<span className="shrink-0 tabular-nums text-zinc-500">{item.label}</span>
</li>
))}
</ul>
)}
</div>
);
}