"use client"; import { useState, useCallback, useRef } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/components/ui/use-toast"; import { cn } from "@/lib/utils"; import { ArrowLeft, Upload, AlertCircle, CheckCircle2, RefreshCw, SkipForward, FilePlus2, Pencil, Film, } from "lucide-react"; import { parseEdlCsv } from "@/lib/edl-utils"; import type { EdlImportRow } from "@/lib/edl-utils"; import { importShotsFromEdl } from "@/actions/shots"; interface EdlImportClientProps { projectId: string; projectName: string; existingShotCodes: Set; } type Step = "input" | "preview" | "result"; const ACTION_STYLES = { create: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20", update: "bg-blue-500/10 text-blue-400 border-blue-500/20", skip: "bg-zinc-500/10 text-zinc-400 border-zinc-500/20", }; const ACTION_ICONS = { create: FilePlus2, update: Pencil, skip: SkipForward, }; export function EdlImportClient({ projectId, projectName, existingShotCodes }: EdlImportClientProps) { const router = useRouter(); const { toast } = useToast(); const fileInputRef = useRef(null); const [step, setStep] = useState("input"); const [csvText, setCsvText] = useState(""); const [rows, setRows] = useState([]); const [parseErrors, setParseErrors] = useState([]); const [importing, setImporting] = useState(false); const [result, setResult] = useState<{ created: string[]; updated: string[]; skipped: string[]; errors: string[] } | null>(null); const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => setCsvText(ev.target?.result as string ?? ""); reader.readAsText(file); e.target.value = ""; }; const handleParse = useCallback(() => { const { rows: parsed, errors } = parseEdlCsv(csvText); setParseErrors(errors); // Determine action for each row based on existing shots const withActions: EdlImportRow[] = parsed.map((row) => ({ ...row, action: existingShotCodes.has(row.shotCode) ? "update" : "create", })); setRows(withActions); if (withActions.length > 0) setStep("preview"); }, [csvText, existingShotCodes]); const toggleAction = (idx: number) => { setRows((prev) => prev.map((r, i) => { if (i !== idx) return r; const cycle: EdlImportRow["action"][] = ["create", "update", "skip"]; const next = cycle[(cycle.indexOf(r.action) + 1) % cycle.length]; return { ...r, action: next }; }) ); }; const setAllAction = (action: EdlImportRow["action"]) => { setRows((prev) => prev.map((r) => ({ ...r, action }))); }; const handleImport = async () => { setImporting(true); try { const res = await importShotsFromEdl(projectId, rows); setResult(res); setStep("result"); if (res.created.length + res.updated.length > 0) { toast({ title: `Import complete`, description: `${res.created.length} created, ${res.updated.length} updated`, }); } } catch (e) { toast({ title: "Import failed", description: e instanceof Error ? e.message : undefined, variant: "destructive" }); } finally { setImporting(false); } }; const handleReset = () => { setStep("input"); setCsvText(""); setRows([]); setParseErrors([]); setResult(null); }; const counts = { create: rows.filter((r) => r.action === "create").length, update: rows.filter((r) => r.action === "update").length, skip: rows.filter((r) => r.action === "skip").length, }; return (
{/* Breadcrumb */}
Projects / {projectName} / Import VFX Pull
{/* Header */}

Import VFX Pull CSV

Paste or upload a Colorfront VFX pull CSV to create or update shots

{/* ── STEP 1: Input ─────────────────────────────────────────────────── */} {step === "input" && (

Paste CSV or upload file