b151e8cc52
Deploy / deploy (push) Successful in 7m25s
Clients sometimes ship footage under a naming convention that doesn't match our own shot codes. Adds an optional Shot.clientCode, editable on the Slate tab: when set, it replaces shotCode only in what the client actually sees — slate identity, overlay burn-in, and delivered EXR/MOV file and folder names in Netflix packages — with our own version string flatly appended (no "_cmp_TT_" studio jargon mixed in). Internal render paths, EXR filenames on the SAN, comp names, and DB lookups are untouched. Shots without a client code render identically to before. Delivery packages denormalize clientCode at build time, same as the existing shotCode/versionString snapshot, so a later shot edit can't retroactively change already-built delivery history. The worker now renames delivered EXR frames and the delivery MOV using the existing frame-number parser (SequencePattern) so only the base name changes — frame numbers and padding survive exactly. This is the one piece of the change that could plausibly ship the wrong bytes, so it's covered by new unit tests (renaming, padding, MOV rename, checksum-survives-rename, and a graceful fallback for an unparseable frame number) rather than resting on manual verification alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
321 lines
8.4 KiB
TypeScript
321 lines
8.4 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;
|
|
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 ──────────────────────────────────────────────────────────
|
|
|
|
export interface DashboardStats {
|
|
awaitingReview: number;
|
|
needsRevisions: number;
|
|
approved: number;
|
|
overdue: number;
|
|
activeProjects: number;
|
|
// Task widgets
|
|
tasksDueToday: number;
|
|
tasksInReview: number;
|
|
tasksOverdue: number;
|
|
myTasksCount: 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;
|
|
}
|
|
}
|