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>
71 lines
2.1 KiB
TypeScript
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>
|
|
);
|
|
}
|