"use client"; import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { ChevronDown, Plus, X } from "lucide-react"; import type { ColumnConfig, ColumnFormat } from "../shared/types"; import { generateTabularColumnPrompt } from "@/app/lib/mikeApi"; import { FORMAT_OPTIONS, formatLabel, formatIcon } from "./columnFormat"; import { TAG_COLORS } from "./pillUtils"; import { getPresetConfig, PROMPT_PRESETS } from "./columnPresets"; import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; interface ColumnDraft { name: string; prompt: string; format: ColumnFormat; tags: string[]; tagInput: string; } const EMPTY_DRAFT: ColumnDraft = { name: "", prompt: "", format: "text", tags: [], tagInput: "", }; interface Props { open: boolean; existingCount: number; onClose: () => void; onAdd: (cols: ColumnConfig[]) => void; editingColumn?: ColumnConfig; onSave?: (col: ColumnConfig) => void; onDelete?: () => void; } export function AddColumnModal({ open, existingCount, onClose, onAdd, editingColumn, onSave, onDelete }: Props) { const isEditing = !!editingColumn; const [columns, setColumns] = useState([{ ...EMPTY_DRAFT }]); const [generatingIndices, setGeneratingIndices] = useState([]); const [presetsOpenIndex, setPresetsOpenIndex] = useState( null, ); const presetsRef = useRef(null); useEffect(() => { if (!open) return; if (editingColumn) { setColumns([{ name: editingColumn.name, prompt: editingColumn.prompt, format: editingColumn.format ?? "text", tags: editingColumn.tags ?? [], tagInput: "", }]); } else { setColumns([{ ...EMPTY_DRAFT }]); } }, [open]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (presetsOpenIndex === null) return; function handleClickOutside(e: MouseEvent) { if ( presetsRef.current && !presetsRef.current.contains(e.target as Node) ) { setPresetsOpenIndex(null); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [presetsOpenIndex]); if (!open) return null; function resetForm() { setColumns([{ ...EMPTY_DRAFT }]); setGeneratingIndices([]); } function handleClose() { resetForm(); onClose(); } function updateColumn(index: number, patch: Partial) { setColumns((prev) => prev.map((col, i) => (i === index ? { ...col, ...patch } : col)), ); } function addAnotherColumn() { setColumns((prev) => [...prev, { ...EMPTY_DRAFT }]); } function removeColumn(index: number) { setColumns((prev) => prev.length === 1 ? [{ ...EMPTY_DRAFT }] : prev.filter((_, i) => i !== index), ); } function commitTag(index: number) { setColumns((prev) => { const col = prev[index]!; const tag = col.tagInput.trim(); if (!tag || col.tags.includes(tag)) { return prev.map((c, i) => i === index ? { ...c, tagInput: "" } : c, ); } return prev.map((c, i) => i === index ? { ...c, tags: [...c.tags, tag], tagInput: "" } : c, ); }); } function handleTagKeyDown( e: React.KeyboardEvent, index: number, ) { if (e.key === "Enter" || e.key === ",") { e.preventDefault(); commitTag(index); } else if ( e.key === "Backspace" && columns[index]!.tagInput === "" && columns[index]!.tags.length > 0 ) { updateColumn(index, { tags: columns[index]!.tags.slice(0, -1), }); } } async function autoGeneratePrompt(index: number) { const title = columns[index]?.name?.trim() ?? ""; if (!title) return; setGeneratingIndices((prev) => [...prev, index]); try { const col = columns[index]!; const { prompt } = await generateTabularColumnPrompt(title, { format: col.format, tags: col.format === "tag" ? col.tags : undefined, }); updateColumn(index, { prompt }); } finally { setGeneratingIndices((prev) => prev.filter((v) => v !== index)); } } function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (columns.some((col) => !col.name.trim() || !col.prompt.trim())) return; if (isEditing && onSave && editingColumn) { const col = columns[0]!; onSave({ index: editingColumn.index, name: col.name.trim(), prompt: col.prompt.trim(), format: col.format, tags: col.format === "tag" ? col.tags : undefined, }); } else { onAdd( columns.map((col, i) => ({ index: existingCount + i, name: col.name.trim(), prompt: col.prompt.trim(), format: col.format, tags: col.format === "tag" ? col.tags : undefined, })), ); } resetForm(); onClose(); } return createPortal(
{/* Header */}
Tabular Review {isEditing ? "Edit column" : "New column"}
{/* Body */}
{columns.map((column, index) => (
{/* Name row */}
{/* Input + preset dropdown anchored to this wrapper */}
{ const name = e.target.value; const preset = getPresetConfig(name); updateColumn(index, { name, ...(preset ? { prompt: preset.prompt, format: preset.format, tags: preset.tags ?? [], tagInput: "", } : {}), }); }} placeholder="Column name" className="flex-1 text-2xl font-serif text-gray-800 placeholder-gray-400 focus:outline-none bg-transparent" autoFocus={index === 0} /> {presetsOpenIndex === index && (
{PROMPT_PRESETS.map( (preset) => ( ), )}
)}
{columns.length > 1 && ( )}
{/* Format */}
updateColumn(index, { format: v as ColumnFormat, tags: [], tagInput: "", }) } > {FORMAT_OPTIONS.map((o) => ( {o.label} ))}
{/* Tag input */} {column.format === "tag" && (
{column.tags.map((tag, tagIdx) => ( {tag} ))} updateColumn(index, { tagInput: e.target.value, }) } onKeyDown={(e) => handleTagKeyDown(e, index) } onBlur={() => commitTag(index)} placeholder="Add tag…" className="min-w-[80px] flex-1 bg-transparent text-sm text-gray-700 placeholder-gray-400 focus:outline-none" />

Press Enter or comma to add a tag.

)} {/* Prompt */}