Files
twotalesanimation 7caf41e17c
Deploy / deploy (push) Successful in 6m17s
Slates
2026-08-07 07:34:10 +02:00

191 lines
7.9 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import Image from "next/image";
import { Film, Save } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/components/ui/use-toast";
import { updateShotSlate } from "@/actions/shots";
import type { ShotWithDetails } from "@/types";
interface Props {
shot: ShotWithDetails;
projectName: string;
onSaved: () => void;
}
/** Left-side slate row: label + editable/static value */
function SlateRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="flex gap-0 border-b border-zinc-800/60 last:border-0">
<div className="w-36 shrink-0 py-3 px-4 text-right text-[11px] font-semibold text-zinc-500 uppercase tracking-widest self-start pt-3.5">
{label}
</div>
<div className="flex-1 py-2.5 px-3 text-sm text-zinc-100">
{children}
</div>
</div>
);
}
/** Right-side metadata row */
function MetaRow({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-center justify-between border-b border-zinc-800/50 last:border-0 py-1.5 px-3">
<span className="text-[11px] text-zinc-500">{label}</span>
<span className="text-[11px] text-zinc-300 text-right max-w-[55%] truncate">{value || "—"}</span>
</div>
);
}
export function ShotSlateTab({ shot, projectName, onSaved }: Props) {
const { toast } = useToast();
const [isPending, startTransition] = useTransition();
const [slateType, setSlateType] = useState(shot.slateType ?? "");
const [slateDescription, setSlateDescription] = useState(shot.slateDescription ?? shot.description ?? "");
const [slateVfxScope, setSlateVfxScope] = useState(shot.slateVfxScope ?? "");
const [slateSubmissionNote, setSlateSubmissionNote] = useState(shot.slateSubmissionNote ?? "");
const today = new Date();
const dateStr = `${today.getFullYear()}/${String(today.getMonth() + 1).padStart(2, "0")}/${String(today.getDate()).padStart(2, "0")}`;
const versionName = `${shot.shotCode}_cmp_TT_${shot.shotVersion ?? "v001"}`;
const frames =
shot.frameStart != null && shot.frameEnd != null
? String(shot.frameEnd - shot.frameStart + 1)
: "—";
const handleSave = () => {
startTransition(async () => {
try {
await updateShotSlate(shot.id, {
slateType: slateType.trim() || null,
slateDescription: slateDescription.trim() || null,
slateVfxScope: slateVfxScope.trim() || null,
slateSubmissionNote: slateSubmissionNote.trim() || null,
});
toast({ title: "Slate saved" });
onSaved();
} catch (e) {
toast({ title: "Failed to save", description: e instanceof Error ? e.message : undefined, variant: "destructive" });
}
});
};
return (
<div className="space-y-4">
{/* Slate document */}
<div className="rounded-xl overflow-hidden border border-zinc-800 bg-zinc-950 max-w-4xl">
{/* Top accent bar */}
<div className="h-1 bg-red-600" />
<div className="flex">
{/* ── Left column: editorial data ─────────────────────────────── */}
<div className="flex-1 border-r border-zinc-800">
{/* Header row */}
<div className="flex border-b border-zinc-800">
<div className="flex-1 px-4 py-3 border-r border-zinc-800">
<p className="text-[10px] text-zinc-500 uppercase tracking-widest mb-0.5">Show</p>
<p className="text-base font-bold text-white tracking-wide">{projectName.toUpperCase()}</p>
</div>
<div className="px-4 py-3 flex items-center gap-2">
<p className="text-[10px] text-zinc-500 uppercase tracking-widest">Submitting For</p>
<span className="ml-1 px-2 py-0.5 rounded bg-red-600/20 border border-red-600/40 text-red-400 text-xs font-bold tracking-widest">REVIEW</span>
</div>
</div>
{/* Auto-filled rows */}
<SlateRow label="Version Name">
<span className="font-mono text-amber-400">{versionName}</span>
</SlateRow>
<SlateRow label="Date">
<span className="text-zinc-300">{dateStr}</span>
</SlateRow>
{/* Editable rows */}
<SlateRow label="Shot Type">
<Input
value={slateType}
onChange={(e) => setSlateType(e.target.value)}
placeholder="e.g. 2D COMP"
className="h-7 bg-zinc-900 border-zinc-700 text-sm font-medium text-zinc-100 placeholder:text-zinc-600"
/>
</SlateRow>
<SlateRow label="Shot Description">
<Textarea
value={slateDescription}
onChange={(e) => setSlateDescription(e.target.value)}
placeholder={shot.description ?? "Shot description for slate…"}
rows={2}
className="bg-zinc-900 border-zinc-700 text-sm text-zinc-100 placeholder:text-zinc-600 resize-none min-h-[56px]"
/>
</SlateRow>
<SlateRow label="VFX Scope">
<Textarea
value={slateVfxScope}
onChange={(e) => setSlateVfxScope(e.target.value)}
placeholder="VFX scope of work…"
rows={3}
className="bg-zinc-900 border-zinc-700 text-sm text-zinc-100 placeholder:text-zinc-600 resize-none min-h-[72px]"
/>
</SlateRow>
<SlateRow label="Submission Note">
<Input
value={slateSubmissionNote}
onChange={(e) => setSlateSubmissionNote(e.target.value)}
placeholder="e.g. Done"
className="h-7 bg-zinc-900 border-zinc-700 text-sm text-zinc-100 placeholder:text-zinc-600"
/>
</SlateRow>
</div>
{/* ── Right column: thumbnail + metadata ──────────────────────── */}
<div className="w-56 shrink-0 flex flex-col">
{/* Thumbnail */}
<div className="aspect-video bg-zinc-900 border-b border-zinc-800 overflow-hidden flex items-center justify-center">
{shot.thumbnailUrl ? (
<Image
src={shot.thumbnailUrl}
alt={shot.shotCode}
width={224}
height={126}
className="object-cover w-full h-full"
/>
) : (
<Film className="h-8 w-8 text-zinc-700" />
)}
</div>
{/* Metadata */}
<div className="flex-1 text-right">
<div className="px-3 py-2 border-b border-zinc-800">
<p className="text-[10px] text-zinc-500 mb-0.5">Vendor</p>
<p className="text-[11px] text-zinc-200 font-medium leading-tight">TWO TALES ANIMATION (PTY) LTD</p>
</div>
<MetaRow label="Shot Name" value={shot.shotCode} />
<MetaRow label="Episode" value={shot.episode ?? "—"} />
<MetaRow label="Seq Name" value={shot.sequence ?? "—"} />
<MetaRow label="Scene" value={shot.scene} />
<MetaRow label="Frames" value={frames} />
<MetaRow label="Media Color" value="w/SHOW LUT" />
</div>
</div>
</div>
</div>
{/* Save button */}
<div className="flex justify-end max-w-4xl">
<Button onClick={handleSave} disabled={isPending} className="gap-2">
<Save className="h-4 w-4" />
{isPending ? "Saving…" : "Save Slate"}
</Button>
</div>
</div>
);
}