f6a7f0ef5a
The dashboard was a generic task-manager view of a studio that does not work
that way. Two of its five headline numbers counted Task.dueDate, which no
pipeline code writes - the only writer is a manual field on the task detail
page, so those cards read zero by construction. The rest were unscoped and
cumulative ("approved" across every project ever finished), the activity feed
was the notification table, and the shot queue sampled 15 rows by updatedAt,
a column the farm touches constantly. Nothing about failed exports, QC depth
or the render farm appeared at all, so a RENDER_FAILED was only visible if
someone thought to open the queue and filter for it.
Replaced with pipeline state, which the system maintains on its own:
- Blocked queue: the four failed statuses plus exports that stopped
progressing, each with the reason read off the actual cause - the failing
validation check, the QC note, the aerender error, or the machine that
claimed it and went quiet.
- Farm strip: machines, queue depth by job type, oldest unclaimed job, and
worker-version drift, since a worker's version decides which fixes it has.
- Five counters that can go down, scoped to live productions, each linking to
the rows it counted.
The queue page ignored ?status=, so the counters had nowhere to link; it now
takes an initial filter from the URL. Stat cards are hidden from CLIENT users
- the old ones were generic version counts, these are internal pipeline state.
Thresholds live in SystemConfig (dashboard.qcStaleHours, .stalledJobHours) so
they can be tuned without a redeploy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
import Link from "next/link";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
import {
|
|
AlertTriangle,
|
|
ClipboardCheck,
|
|
PackageCheck,
|
|
Eye,
|
|
TrendingUp,
|
|
} from "lucide-react";
|
|
import type { DashboardStats } from "@/types";
|
|
|
|
interface StatsCardsProps {
|
|
stats: DashboardStats;
|
|
}
|
|
|
|
/**
|
|
* Five numbers that can go down. Each one links to the page that lists what it
|
|
* counts — a figure with no way through to the rows behind it is wallpaper.
|
|
*/
|
|
export function StatsCards({ stats }: StatsCardsProps) {
|
|
const cards = [
|
|
{
|
|
label: "Blocked",
|
|
value: stats.blocked,
|
|
href: "#blocked",
|
|
icon: AlertTriangle,
|
|
color: "text-red-400",
|
|
bg: "bg-red-500/10",
|
|
// The only card that earns emphasis when it is non-zero: everything else
|
|
// here is a queue depth, this one is work that has stopped.
|
|
emphasise: stats.blocked > 0,
|
|
},
|
|
{
|
|
label: "In QC",
|
|
value: stats.inQc,
|
|
href: "/pipeline?status=READY_FOR_QC",
|
|
icon: ClipboardCheck,
|
|
color: "text-amber-400",
|
|
bg: "bg-amber-500/10",
|
|
},
|
|
{
|
|
label: "Ready to deliver",
|
|
value: stats.readyToDeliver,
|
|
href: "/pipeline/deliveries",
|
|
icon: PackageCheck,
|
|
color: "text-green-400",
|
|
bg: "bg-green-500/10",
|
|
},
|
|
{
|
|
label: "With the client",
|
|
value: stats.awaitingClientReview,
|
|
href: "/playlist",
|
|
icon: Eye,
|
|
color: "text-blue-400",
|
|
bg: "bg-blue-500/10",
|
|
},
|
|
{
|
|
label: "Active projects",
|
|
value: stats.activeProjects,
|
|
href: "/projects",
|
|
icon: TrendingUp,
|
|
color: "text-zinc-400",
|
|
bg: "bg-zinc-500/10",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
{cards.map((card) => {
|
|
const Icon = card.icon;
|
|
return (
|
|
<Link key={card.label} href={card.href} className="group rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500">
|
|
<Card
|
|
className={cn(
|
|
"h-full transition-colors group-hover:border-zinc-700",
|
|
card.emphasise && "border-red-500/40"
|
|
)}
|
|
>
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className={cn("p-2 rounded-md", card.bg)}>
|
|
<Icon className={cn("h-4 w-4", card.color)} />
|
|
</div>
|
|
</div>
|
|
<p
|
|
className={cn(
|
|
"text-3xl font-bold tabular-nums",
|
|
card.emphasise ? "text-red-400" : "text-white"
|
|
)}
|
|
>
|
|
{card.value}
|
|
</p>
|
|
<p className="text-sm text-zinc-400 mt-0.5">{card.label}</p>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|