@@ -28,12 +28,15 @@ import {
|
||||
ExternalLink,
|
||||
XCircle,
|
||||
FileVideo,
|
||||
Pencil,
|
||||
Check,
|
||||
} from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { ShotWithDetails } from "@/types";
|
||||
import { ShotSettingsTab } from "@/components/shots/ShotSettingsTab";
|
||||
import { FootageViewer } from "@/components/shots/FootageViewer";
|
||||
import { HighResUploadDialog } from "@/components/shots/HighResUploadDialog";
|
||||
import { duplicateShot, internallyApproveShot, shareWithClient, unshareFromClient, unapproveShot } from "@/actions/shots";
|
||||
import { duplicateShot, internallyApproveShot, shareWithClient, unshareFromClient, unapproveShot, updateShotVersion } from "@/actions/shots";
|
||||
|
||||
const STATUS_CONFIG: Record<
|
||||
string,
|
||||
@@ -87,6 +90,9 @@ export default function ShotDetailPage() {
|
||||
const [isActioning, setIsActioning] = useState(false);
|
||||
const [highResDialogOpen, setHighResDialogOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<"tasks" | "reviews" | "footage" | "settings">("tasks");
|
||||
const [editingVersion, setEditingVersion] = useState(false);
|
||||
const [versionInput, setVersionInput] = useState("");
|
||||
const [savingVersion, setSavingVersion] = useState(false);
|
||||
|
||||
const fetchShot = async () => {
|
||||
try {
|
||||
@@ -171,6 +177,26 @@ export default function ShotDetailPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveVersion = async () => {
|
||||
if (!shot) return;
|
||||
const trimmed = versionInput.trim().toLowerCase();
|
||||
if (!/^v\d{3}$/.test(trimmed)) {
|
||||
toast({ title: "Invalid format", description: "Version must be v### (e.g. v001)", variant: "destructive" });
|
||||
return;
|
||||
}
|
||||
setSavingVersion(true);
|
||||
try {
|
||||
await updateShotVersion(shot.id, trimmed);
|
||||
toast({ title: `Version set to ${trimmed}` });
|
||||
setEditingVersion(false);
|
||||
fetchShot();
|
||||
} catch (e) {
|
||||
toast({ title: "Failed", description: e instanceof Error ? e.message : undefined, variant: "destructive" });
|
||||
} finally {
|
||||
setSavingVersion(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicate = async () => {
|
||||
if (!shot) return;
|
||||
setIsDuplicating(true);
|
||||
@@ -237,6 +263,55 @@ export default function ShotDetailPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
<h1 className="text-2xl font-bold font-mono">{shot.shotCode}</h1>
|
||||
|
||||
{/* Shot version badge — click to edit for managers */}
|
||||
{canManage ? (
|
||||
editingVersion ? (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Input
|
||||
autoFocus
|
||||
value={versionInput}
|
||||
onChange={(e) => setVersionInput(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleSaveVersion();
|
||||
if (e.key === "Escape") setEditingVersion(false);
|
||||
}}
|
||||
className="h-7 w-20 font-mono text-xs px-2"
|
||||
placeholder="v001"
|
||||
maxLength={4}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSaveVersion}
|
||||
disabled={savingVersion}
|
||||
className="text-emerald-400 hover:text-emerald-300 disabled:opacity-50"
|
||||
title="Save version"
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingVersion(false)}
|
||||
className="text-zinc-500 hover:text-zinc-300"
|
||||
title="Cancel"
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => { setVersionInput(shot.shotVersion ?? "v001"); setEditingVersion(true); }}
|
||||
className="group flex items-center gap-1 font-mono text-sm px-2 py-0.5 rounded border border-zinc-700 bg-zinc-800/60 text-zinc-300 hover:border-amber-500/50 hover:text-amber-400 transition-colors"
|
||||
title="Click to edit version"
|
||||
>
|
||||
{shot.shotVersion ?? "v001"}
|
||||
<Pencil className="h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</button>
|
||||
)
|
||||
) : (
|
||||
<span className="font-mono text-sm px-2 py-0.5 rounded border border-zinc-700 bg-zinc-800/60 text-zinc-300">
|
||||
{shot.shotVersion ?? "v001"}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{shot.sequence && (
|
||||
<span className="text-sm text-muted-foreground">Seq: {shot.sequence}</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user