caaa333f02
Deploy / deploy (push) Failing after 19m11s
Two independent features landing together: Generated shot thumbnails — a shot can now carry a thumbnail promoted from a render export (Shot.generatedThumbnailUrl/generatedThumbnailFrom) alongside the existing brief-supplied thumbnailUrl, which always takes display precedence (lib/shot-thumbnail.ts) so a promoted frame can only ever fill a gap, never overwrite the brief. Shows with pipelineConfig.autoShotThumbnail promote automatically on every finished export (lib/render-pipeline/preview.ts); promoteExportThumbnail/ clearGeneratedThumbnail (actions/shots.ts) are the manual equivalent for everyone else. Worker-side seek logic for picking the promoted frame is covered by RenderWorker/VFXReviewWorker.Tests/ThumbnailSeekTests.cs. Bulk shot delete — the shot-status page's selection bar gets a "Bulk Actions" dropdown (alongside the existing Change Due Date) with Delete Shots. bulkDeleteShots() (actions/shots.ts) deletes one at a time rather than in a single statement: exports_shotId_fkey is ON DELETE RESTRICT by design, so a shot with renders or deliveries on record is protected and gets skipped and reported by name rather than aborting the whole selection or being silently destroyed along with its history. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
323 lines
8.5 KiB
TypeScript
323 lines
8.5 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 ──────────────────────────────────────────────────────────
|
|
|
|
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;
|
|
}
|
|
}
|