Files
vfxreview/types/index.ts
T
twotalesanimation f6a7f0ef5a feat(dashboard): show what is blocked and how the farm is doing
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>
2026-09-10 05:40:40 +02:00

323 lines
8.6 KiB
TypeScript

import { Role, ApprovalStatus, ReviewStatus, ShotStatus, ShotApprovalStatus, ShotPriority, ProjectStatus, TaskStatus, TaskType } from "@prisma/client";
// Re-export Prisma enums for convenience
export { Role, ApprovalStatus, ReviewStatus, ShotStatus, ShotApprovalStatus, ShotPriority, ProjectStatus, TaskStatus, TaskType };
// ── Annotation Types ─────────────────────────────────────────────────────────
export type AnnotationTool = "freehand" | "arrow" | "rectangle" | "circle";
export interface AnnotationPoint {
x: number; // normalized 0-1 relative to canvas width
y: number; // normalized 0-1 relative to canvas height
}
export interface AnnotationShape {
id: string;
tool: AnnotationTool;
/** For freehand: all path points. For others: [startPoint, endPoint] */
points: AnnotationPoint[];
color: string;
strokeWidth: number;
frameNumber: number;
}
export interface AnnotationDrawingData {
shapes: AnnotationShape[];
canvasWidth: number;
canvasHeight: number;
version: "1.0";
}
// ── Review Player State ──────────────────────────────────────────────────────
export interface ReviewPlayerState {
isPlaying: boolean;
currentFrame: number;
currentTime: number;
duration: number;
playbackRate: number;
volume: number;
isMuted: boolean;
isFullscreen: boolean;
isAnnotating: boolean;
selectedTool: AnnotationTool;
selectedColor: string;
strokeWidth: number;
showAnnotations: boolean;
fps: number;
totalFrames: number;
}
// ── Comment Types ────────────────────────────────────────────────────────────
export interface CommentWithReplies {
id: string;
versionId: string;
authorId: string;
frameNumber: number;
timestamp: number;
text: string;
isResolved: boolean;
createdAt: Date;
updatedAt: Date;
author: {
id: string;
name: string | null;
email: string;
image: string | null;
role: Role;
} | null;
replies: CommentReplyWithAuthor[];
annotations: AnnotationSummary[];
}
export interface CommentReplyWithAuthor {
id: string;
commentId: string;
authorId: string;
text: string;
createdAt: Date;
author: {
id: string;
name: string | null;
email: string;
image: string | null;
role: Role;
} | null;
}
export interface AnnotationSummary {
id: string;
frameNumber: number;
drawingData: unknown;
color: string;
isVisible: boolean;
authorId: string;
}
// ── Version Types ────────────────────────────────────────────────────────────
export interface VersionWithDetails {
id: string;
versionNumber: number;
shotId: string | null;
taskId: string | null;
artistId: string | null;
isClientVisible: boolean;
sharedAt: Date | null;
sharedById: string | null;
fileUrl: string;
fileName: string;
fileSize: bigint | null;
mimeType: string | null;
thumbnailUrl: string | null;
posterUrl: string | null;
proxyUrl: string | null;
fps: number;
duration: number | null;
frameCount: number | null;
width: number | null;
height: number | null;
notes: string | null;
approvalStatus: ApprovalStatus;
reviewStatus: ReviewStatus;
isLatest: boolean;
createdAt: Date;
updatedAt: Date;
artist: {
id: string;
name: string | null;
email: string;
image: string | null;
} | null;
_count?: {
comments: number;
};
approvals?: {
id: string;
status: string;
notes: string | null;
createdAt: Date;
user: { id: string; name: string | null; image: string | null };
}[];
}
// ── Shot Types ───────────────────────────────────────────────────────────────
export interface ShotWithDetails {
id: string;
shotCode: string;
/** Client-supplied naming alias — slates/overlays/delivery only, never internal paths. */
clientCode: string | null;
scene: string;
episode: string | null;
shotNumber: number;
sequence: string | null;
description: string | null;
notes: string | null;
status: ShotStatus;
priority: ShotPriority;
artistId: string | null;
projectId: string;
fps: number;
frameStart: number | null;
frameEnd: number | null;
dueDate: Date | null;
thumbnailUrl: string | null;
generatedThumbnailUrl: string | null;
generatedThumbnailFrom: string | null;
originalFootageUrl: string | null;
originalFootageKey: string | null;
shotGroupId: string | null;
shotApprovalStatus: ShotApprovalStatus;
sharedWithClient: boolean;
// EDL / pull CSV fields
sourceClip: string | null;
timecodeStart: string | null;
timecodeEnd: string | null;
clipDuration: string | null;
exrOutput: string | null;
// Sequence / picture lock timecodes
seqTimecodeStart: string | null;
seqTimecodeEnd: string | null;
// High-res deliverable
highResFilename: string | null;
// Shot-level version tracking
shotVersion: string;
// Netflix slate fields
slateType: string | null;
slateDescription: string | null;
slateVfxScope: string | null;
slateSubmissionNote: string | null;
shotGroup: { id: string; name: string } | null;
footagePlates: {
id: string;
label: string;
fileUrl: string;
fileKey: string;
fileName: string;
fileSize: bigint | string | null;
sortOrder: number;
createdAt: Date;
}[];
createdAt: Date;
updatedAt: Date;
artist: {
id: string;
name: string | null;
email: string;
image: string | null;
} | null;
versions: VersionWithDetails[];
_count?: {
versions: number;
};
}
// ── Task Types ───────────────────────────────────────────────────────────────
export interface TaskWithDetails {
id: string;
title: string;
description: string | null;
type: TaskType;
status: TaskStatus;
priority: ShotPriority;
dueDate: Date | null;
estimatedHours: number | null;
sortOrder: number;
shotId: string | null;
assetId: string | null;
assignedArtistId: string | null;
createdById: string;
projectId: string;
createdAt: Date;
updatedAt: Date;
assignedArtist: {
id: string;
name: string | null;
email: string;
image: string | null;
} | null;
createdBy: {
id: string;
name: string | null;
email: string;
};
_count?: {
versions: number;
};
}
export interface AssetWithDetails {
id: string;
projectId: string;
assetCode: string;
name: string;
description: string | null;
status: ShotStatus;
priority: ShotPriority;
leadId: string | null;
dueDate: Date | null;
createdAt: Date;
updatedAt: Date;
lead: {
id: string;
name: string | null;
email: string;
image: string | null;
} | null;
tasks: TaskWithDetails[];
_count?: {
tasks: number;
};
}
// ── Dashboard Types ──────────────────────────────────────────────────────────
/**
* Dashboard headline numbers. Every one is derived from pipeline state and
* scoped to live productions — see lib/dashboard/ops.ts. The previous shape
* carried task due/overdue counts, which read a column nothing writes.
*/
export interface DashboardStats {
blocked: number;
inQc: number;
readyToDeliver: number;
awaitingClientReview: number;
activeProjects: number;
}
// ── Notification Badge ────────────────────────────────────────────────────────
export interface NotificationItem {
id: string;
type: string;
title: string;
message: string;
data: Record<string, unknown> | null;
isRead: boolean;
createdAt: Date;
}
// ── Next-Auth session extension ──────────────────────────────────────────────
declare module "next-auth" {
interface Session {
user: {
id: string;
role: Role;
mustChangePassword: boolean;
name?: string | null;
email?: string | null;
image?: string | null;
};
}
interface User {
role: Role;
mustChangePassword?: boolean;
}
}