"use client"; import { useState, useRef } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { importShotsFromCsv } from "@/actions/shots"; import { useToast } from "@/components/ui/use-toast"; import { Upload, AlertCircle, CheckCircle2, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; interface ParsedRow { scene: string; episode?: string; description?: string; priority?: string; fps?: number; frameStart?: number; frameEnd?: number; } interface ImportShotsDialogProps { projectId: string; projectType?: "STANDARD" | "EPISODIC"; open: boolean; onClose: () => void; onSuccess?: () => void; } const TEMPLATE_STANDARD = `scene,description,priority,fps,frameStart,frameEnd 010,Opening wide shot,NORMAL,24,1001,1100 020,Close up reaction,NORMAL,24,, 030,Action sequence,HIGH,24,1001,1250`; const TEMPLATE_EPISODIC = `scene,episode,description,priority,fps,frameStart,frameEnd 010,EP01,Opening wide shot,NORMAL,24,1001,1100 020,EP01,Close up reaction,NORMAL,24,, 010,EP02,Act two opener,HIGH,24,1001,1200`; function parseCsv(raw: string): { rows: ParsedRow[]; parseErrors: string[] } { const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean); if (lines.length < 2) return { rows: [], parseErrors: ["Need at least a header row and one data row"] }; const headers = lines[0].split(",").map((h) => h.trim().toLowerCase()); const sceneIdx = headers.indexOf("scene"); if (sceneIdx === -1) return { rows: [], parseErrors: ['Missing required "scene" column'] }; const idx = (name: string) => { const i = headers.indexOf(name); return i === -1 ? null : i; }; const rows: ParsedRow[] = []; const parseErrors: string[] = []; for (let i = 1; i < lines.length; i++) { const cells = lines[i].split(",").map((c) => c.trim()); const get = (name: string) => { const j = idx(name); return j !== null ? cells[j] ?? "" : ""; }; const scene = get("scene"); if (!scene) { parseErrors.push(`Row ${i + 1}: empty scene — skipped`); continue; } const fpsRaw = get("fps"); const fsRaw = get("framestart") || get("frameStart"); const feRaw = get("frameend") || get("frameEnd"); rows.push({ scene, episode: get("episode") || undefined, description: get("description") || undefined, priority: get("priority") || undefined, fps: fpsRaw ? Number(fpsRaw) : undefined, frameStart: fsRaw ? Number(fsRaw) : undefined, frameEnd: feRaw ? Number(feRaw) : undefined, }); } return { rows, parseErrors }; } export function ImportShotsDialog({ projectId, projectType = "STANDARD", open, onClose, onSuccess, }: ImportShotsDialogProps) { const { toast } = useToast(); const [step, setStep] = useState<"input" | "preview">("input"); const [csvText, setCsvText] = useState(""); const [parsedRows, setParsedRows] = useState([]); const [parseErrors, setParseErrors] = useState([]); const [isImporting, setIsImporting] = useState(false); const [importResult, setImportResult] = useState<{ created: string[]; errors: string[] } | null>(null); const fileInputRef = useRef(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); }; const handleParse = () => { const { rows, parseErrors: errs } = parseCsv(csvText); setParsedRows(rows); setParseErrors(errs); if (rows.length > 0) setStep("preview"); }; const handleImport = async () => { setIsImporting(true); try { const result = await importShotsFromCsv(projectId, parsedRows); setImportResult(result); if (result.created.length > 0) { toast({ title: `${result.created.length} shot${result.created.length === 1 ? "" : "s"} created` }); onSuccess?.(); } } catch (e) { toast({ title: "Import failed", description: e instanceof Error ? e.message : undefined, variant: "destructive" }); } finally { setIsImporting(false); } }; const handleClose = () => { setStep("input"); setCsvText(""); setParsedRows([]); setParseErrors([]); setImportResult(null); onClose(); }; const template = projectType === "EPISODIC" ? TEMPLATE_EPISODIC : TEMPLATE_STANDARD; return ( !v && handleClose()}> Import Shots from CSV {importResult ? ( /* Result screen */
{importResult.created.length > 0 && (

{importResult.created.length} shot{importResult.created.length === 1 ? "" : "s"} created

{importResult.created.map((code) =>
{code}
)}
)} {importResult.errors.length > 0 && (

{importResult.errors.length} warning{importResult.errors.length === 1 ? "" : "s"}

{importResult.errors.map((e, i) =>
{e}
)}
)}
) : step === "input" ? ( /* CSV input step */

Paste CSV or upload a file. Required column: scene. {projectType === "EPISODIC" && ( <> Also required: episode. )}