"use client"; import { useState, useTransition, useCallback, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; import Image from "next/image"; import Link from "next/link"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { updateShotNotes, bulkUpdateDueDates } from "@/actions/shots"; import { useToast } from "@/components/ui/use-toast"; import { Film, Clock, CheckCircle2, AlertCircle, Calendar, CalendarDays, ChevronDown, ChevronRight, Loader2, X, ShieldCheck, Eye, } from "lucide-react"; import { format } from "date-fns"; type ShotRow = { id: string; shotCode: string; scene: string; episode: string | null; shotNumber: number; status: string; priority: string; dueDate: Date | string | null; thumbnailUrl: string | null; notes: string | null; description: string | null; artist: { id: string; name: string | null; image: string | null; email: string } | null; }; interface Project { id: string; name: string; code: string; projectType: string; } interface ShotStatusClientProps { projects: Project[]; selectedProjectId: string | null; selectedProject: Project | null; shots: ShotRow[]; episodeDueDates: { episode: string; dueDate: Date | string }[]; canManage: boolean; } const SCROLL_KEY = 'shot-status-scroll'; const STATUS_CONFIG: Record = { WAITING: { label: "Waiting", color: "bg-zinc-500/10 text-zinc-400 border-zinc-500/20", icon: Clock }, IN_PROGRESS: { label: "In Progress", color: "bg-blue-500/10 text-blue-400 border-blue-500/20", icon: Film }, INTERNAL_REVIEW: { label: "Internal Review", color: "bg-purple-500/10 text-purple-400 border-purple-500/20", icon: AlertCircle }, READY_FOR_CLIENT: { label: "Ready for Client", color: "bg-sky-500/10 text-sky-400 border-sky-500/20", icon: ShieldCheck }, CLIENT_REVIEW: { label: "Client Review", color: "bg-indigo-500/10 text-indigo-400 border-indigo-500/20", icon: Eye }, REVISIONS: { label: "Revisions", color: "bg-orange-500/10 text-orange-400 border-orange-500/20", icon: AlertCircle }, COMPLETE: { label: "Complete", color: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20", icon: CheckCircle2 }, }; // ── IndeterminateCheckbox ────────────────────────────────────────────────────── function IndeterminateCheckbox({ checked, indeterminate, onChange, className, }: { checked: boolean; indeterminate?: boolean; onChange: (e: React.ChangeEvent) => void; className?: string; }) { const ref = useRef(null); useEffect(() => { if (ref.current) ref.current.indeterminate = !!indeterminate; }, [indeterminate]); return ( ); } // ── BulkDueDateDialog ───────────────────────────────────────────────────────── function BulkDueDateDialog({ open, onClose, onApply, count, }: { open: boolean; onClose: () => void; onApply: (date: string, includeTasks: boolean) => Promise; count: number; }) { const [date, setDate] = useState(""); const [includeTasks, setIncludeTasks] = useState(false); const [isPending, startTransition] = useTransition(); useEffect(() => { if (open) { setDate(""); setIncludeTasks(false); } }, [open]); const handleApply = () => { if (!date) return; startTransition(async () => { await onApply(date, includeTasks); }); }; return ( { if (!o && !isPending) onClose(); }}> Change Due Date

Applying to {count}{" "} shot{count !== 1 ? "s" : ""}

setDate(e.target.value)} className="w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 [color-scheme:dark] focus:border-zinc-500 focus:outline-none focus:ring-1 focus:ring-zinc-500" />
); } // ── NotesCell ───────────────────────────────────────────────────────────────── function NotesCell({ shot, canManage, }: { shot: ShotRow; canManage: boolean; }) { const [value, setValue] = useState(shot.notes ?? ""); const [saved, setSaved] = useState(true); const [isPending, startTransition] = useTransition(); const { toast } = useToast(); const handleBlur = useCallback(() => { if (value === (shot.notes ?? "")) return; startTransition(async () => { try { await updateShotNotes(shot.id, value); setSaved(true); toast({ title: "Notes saved" }); } catch { toast({ title: "Failed to save notes", variant: "destructive" }); } }); }, [value, shot.id, shot.notes, toast]); if (!canManage) { return ( {shot.notes ?? } ); } return (