@@ -4,8 +4,9 @@ import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
ChevronLeft, ChevronRight, Copy, Plus, Check, Loader2,
|
||||
Trash2, AlertCircle, ChevronDown, ChevronUp, Link2, X,
|
||||
Trash2, AlertCircle, ChevronDown, ChevronUp, Link2, X, BookImage,
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
@@ -201,6 +202,86 @@ function SectionHeader({ title }: { title: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Linked shot panel ────────────────────────────────────────────────────────
|
||||
|
||||
interface LinkedShot {
|
||||
id: string;
|
||||
shotCode: string;
|
||||
scene: string | null;
|
||||
episode: string | null;
|
||||
thumbnailUrl: string | null;
|
||||
references: { id: string; fileUrl: string; fileName: string; label: string | null }[];
|
||||
}
|
||||
|
||||
function LinkedShotPanel({ shotId }: { shotId: string }) {
|
||||
const { data, isLoading } = useQuery<{ shot: LinkedShot }>({
|
||||
queryKey: ["linked-shot", shotId],
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/shoot-log/shots/${shotId}`);
|
||||
if (!res.ok) throw new Error("Failed");
|
||||
return res.json();
|
||||
},
|
||||
staleTime: 60_000,
|
||||
enabled: !!shotId,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-xs text-zinc-500 mt-2">
|
||||
<Loader2 className="h-3 w-3 animate-spin" /> Loading shot data…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const shot = data?.shot;
|
||||
if (!shot) return null;
|
||||
|
||||
const hasContent = shot.thumbnailUrl || shot.references.length > 0;
|
||||
if (!hasContent) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-3 rounded-lg border border-zinc-800 bg-zinc-900/50 p-3 space-y-3">
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
|
||||
<BookImage className="h-3 w-3" />
|
||||
Shot Reference — {shot.shotCode}
|
||||
</div>
|
||||
|
||||
{shot.thumbnailUrl && (
|
||||
<div className="relative w-full max-w-xs aspect-[2.39] rounded-md overflow-hidden border border-zinc-700">
|
||||
<Image
|
||||
src={shot.thumbnailUrl}
|
||||
alt={shot.shotCode}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="320px"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shot.references.length > 0 && (
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{shot.references.map((ref) => (
|
||||
<div key={ref.id} className="relative aspect-square rounded-md overflow-hidden border border-zinc-700 bg-zinc-950 group">
|
||||
<Image
|
||||
src={ref.fileUrl}
|
||||
alt={ref.label ?? ref.fileName}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="100px"
|
||||
/>
|
||||
{ref.label && (
|
||||
<div className="absolute bottom-0 inset-x-0 bg-black/70 text-[9px] text-zinc-300 truncate px-1 py-0.5">
|
||||
{ref.label}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Shot selector ────────────────────────────────────────────────────────────
|
||||
|
||||
interface ShotOption {
|
||||
@@ -440,6 +521,9 @@ export function TakeEditorPanel({
|
||||
value={fields.pipelineShotId}
|
||||
onChange={(id) => set("pipelineShotId", id)}
|
||||
/>
|
||||
{fields.pipelineShotId && (
|
||||
<LinkedShotPanel shotId={fields.pipelineShotId} />
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{/* Camera */}
|
||||
|
||||
Reference in New Issue
Block a user