import { useState } from "react"; import { open } from "@tauri-apps/plugin-dialog"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { AnimatedModal } from "../ui/AnimatedModal"; import { Button } from "../ui/Button"; import { Select } from "../ui/Select"; import { normalizeImport, coerceRow } from "../../lib/importNormalize"; const MAX_ROWS = 100_000; const MAX_BYTES = 100 * 1024 * 1024; const SKIP = ""; export interface ImportDialogProps { open: boolean; schema: string; table: string; columns: string[]; onStage: (change: { type: "bulk_insert"; schema: string; table: string; columns: string[]; rows: unknown[][]; description: string; }) => void; onClose: () => void; } export function ImportDialog({ open: isOpen, schema, table, columns, onStage, onClose, }: ImportDialogProps) { const [parsed, setParsed] = useState<{ headers: string[]; rows: string[][] } | null>(null); const [mapping, setMapping] = useState>({}); const [error, setError] = useState(null); const chooseFile = async () => { try { const path = await open({ filters: [{ name: "Data", extensions: ["csv", "json"] }], }); if (!path || Array.isArray(path)) return; const text = await readTextFile(path as string); if (text.length > MAX_BYTES) { setError("File exceeds 100 MB limit"); return; } const { headers, rows } = normalizeImport(text); if (rows.length > MAX_ROWS) { setError(`File has ${rows.length.toLocaleString()} rows; limit is ${MAX_ROWS.toLocaleString()}`); return; } setParsed({ headers, rows }); setMapping( Object.fromEntries( headers.map((h, i) => [h, columns[i] ?? SKIP]), ), ); setError(null); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }; const stage = () => { if (!parsed) return; const selected = parsed.headers .map((header) => ({ header, col: mapping[header] })) .filter(({ col }) => col && col !== SKIP); const targetColumns = selected.map(({ col }) => col); const dataRows = parsed.rows.map((row) => selected.map(({ header }) => { const idx = parsed.headers.indexOf(header); return coerceRow(row[idx]); }), ); onStage({ type: "bulk_insert", schema, table, columns: targetColumns, rows: dataRows, description: `Import ${dataRows.length.toLocaleString()} rows into ${schema}.${table}`, }); onClose(); }; const mappingOptions = [ { value: SKIP, label: "" }, ...columns.map((c) => ({ value: c, label: c })), ]; const previewRows = parsed ? parsed.rows.slice(0, 100) : []; return (

Import into {schema}.{table}

CSV or JSON, up to 100 MB / 100,000 rows
{error && (
{error}
)} {parsed && (

Preview ({parsed.rows.length.toLocaleString()} rows × {parsed.headers.length} columns)

{parsed.headers.map((header) => (
{header}