"use client"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/components/ui/use-toast"; import { DELIVERABLES, DELIVERABLE_LABEL, parseShotCodes, type Deliverable } from "@/lib/render-pipeline/delivery-request-state"; const SELECT_CLASS = "w-full rounded-md border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-200"; interface ProjectOption { id: string; name: string; code: string; } /** * Log what a client asked for. The ask arrives over chat, so this has to be * quicker than deciding to do it later: a project, the shots, who asked. * * With `preset` the shots are already chosen (the shot-status selection); * otherwise they come from an episode or a pasted list of codes. */ export function LogRequestDialog({ open, onClose, onLogged, preset, defaultProjectId, }: { open: boolean; onClose: () => void; onLogged: () => void; preset?: { projectId: string; shotIds: string[] }; defaultProjectId?: string; }) { const { toast } = useToast(); const [projects, setProjects] = useState([]); const [projectId, setProjectId] = useState(""); const [episodes, setEpisodes] = useState<{ episode: string; shotCount: number }[]>([]); const [mode, setMode] = useState<"episode" | "codes">("episode"); const [episode, setEpisode] = useState(""); const [codes, setCodes] = useState(""); const [deliverable, setDeliverable] = useState("EXR"); const [requestedBy, setRequestedBy] = useState(""); const [dueDate, setDueDate] = useState(""); const [note, setNote] = useState(""); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); useEffect(() => { if (!open) return; setProjectId(preset?.projectId ?? defaultProjectId ?? ""); setMode("episode"); setEpisode(""); setCodes(""); setDeliverable("EXR"); setRequestedBy(""); setDueDate(""); setNote(""); setError(null); if (preset) return; fetch("/api/projects") .then((res) => (res.ok ? res.json() : { projects: [] })) .then((body) => setProjects((body.projects ?? body) as ProjectOption[])) .catch(() => setProjects([])); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); useEffect(() => { setEpisodes([]); setEpisode(""); if (!open || preset || !projectId) return; let stale = false; fetch(`/api/delivery-requests/options?projectId=${encodeURIComponent(projectId)}`) .then((res) => (res.ok ? res.json() : { episodes: [] })) .then((body) => { if (!stale) setEpisodes(body.episodes ?? []); }) .catch(() => {}); return () => { stale = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, projectId]); const codeCount = parseShotCodes(codes).length; const hasShots = preset ? preset.shotIds.length > 0 : mode === "episode" ? Boolean(episode) : codeCount > 0; const canSave = Boolean(projectId) && hasShots && requestedBy.trim().length > 0 && !saving; async function save() { setSaving(true); setError(null); const res = await fetch("/api/delivery-requests", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ projectId, ...(preset ? { shotIds: preset.shotIds } : mode === "episode" ? { episode } : { shotCodes: codes }), deliverable, requestedBy, dueDate: dueDate || null, note: note || null, }), }); const body = await res.json().catch(() => ({})); setSaving(false); if (!res.ok) { // Shown in place rather than as a toast: an unmatched code has to be // fixed in the text right above it. setError(body.error ?? res.statusText); return; } toast({ title: `Request #${body.request.requestNumber} logged`, description: `${body.request.lineCount} shot${body.request.lineCount === 1 ? "" : "s"} awaiting delivery`, }); onLogged(); onClose(); } return ( !o && !saving && onClose()}> Log delivery request What the client asked for. It stays under Awaiting delivery until every shot has been uploaded and the link shared.
{preset ? (

{preset.shotIds.length} selected shot{preset.shotIds.length === 1 ? "" : "s"}

) : ( <>
{mode === "episode" ? ( ) : (