import { useCallback, useEffect, useState } from "react"; import { Check, X, RotateCcw } from "lucide-react"; import { useDbViewerStore } from "../../stores/dbViewerStore"; import { useUiStore } from "../../stores/uiStore"; import { useNotificationStore } from "../../stores/notificationStore"; import * as cmd from "../../lib/commands"; import { buildChangePayload, buildChangeSql } from "../../lib/changePayload"; import { isSchemaModifyingQuery } from "../../lib/utils"; import type { QueueItem, QueueStatus } from "../../stores/dbViewerStore"; const statusBg: Record = { pending: "bg-accent/5", committed: "bg-green-500/5", failed: "bg-red-500/5", cancelled: "bg-surface-raised/50", }; function formatChangeLabel(change: QueueItem): string { const schema = change.schema ?? ""; const table = change.table ?? ""; const fullName = schema ? `${schema}.${table}` : table; switch (change.type) { case "bulk_insert": return change.description ?? `Import ${change.rows?.length ?? 0} rows into ${fullName}`; case "empty_table": return `Empty Table: ${fullName}`; case "drop_table": return `Drop Table: ${fullName}`; case "rebuild_table": return change.description ?? `Rebuild ${fullName}`; case "ddl": return change.description ?? "DDL"; default: return change.table ?? "-"; } } /** Render the old → new value change for update queue items. */ function formatValueDiff(change: QueueItem): string | null { if (change.type !== "update" || !change.newData) return null; const colName = Object.keys(change.newData)[0]; if (!colName) return null; const oldVal = change.oldData && change.oldData[colName] !== undefined ? String(change.oldData[colName]) : "NULL"; const newVal = change.newData[colName] === null || change.newData[colName] === undefined ? "NULL" : String(change.newData[colName]); return `${colName}: ${oldVal} → ${newVal}`; } function capitalizeType(type: string) { return type.charAt(0).toUpperCase() + type.slice(1); } /** Badge label for a queue-item type — ddl/rebuild render uppercase. */ function badgeLabel(type: string): string { if (type === "ddl") return "DDL"; if (type === "rebuild_table") return "REBUILD"; return capitalizeType(type); } function tableRef(change: QueueItem): string { if (change.schema && change.table) return `${change.schema}.${change.table}`; return change.table ?? "-"; } export function ChangesQueuePanel({ onCommitted }: { onCommitted?: () => void } = {}) { const changesQueue = useDbViewerStore((state) => state.changesQueue); const removeChange = useDbViewerStore((state) => state.removeChange); const clearChanges = useDbViewerStore((state) => state.clearChanges); const markChangeCommitted = useDbViewerStore((state) => state.markChangeCommitted); const markChangeFailed = useDbViewerStore((state) => state.markChangeFailed); const notify = useNotificationStore((state) => state.notify); const [view, setView] = useState<"visual" | "sql">("visual"); const handleCommitAll = useCallback(async () => { const connectionId = useUiStore.getState().activeConnectionId; if (!connectionId) { notify("No active connection", "error"); return; } const pending = useDbViewerStore.getState().changesQueue.filter( (c) => c.status === "pending", ); if (pending.length === 0) return; let committedCount = 0; let treeDirty = false; for (const change of pending) { try { const payload = buildChangePayload(change); await cmd.executeChange(connectionId, payload); markChangeCommitted(change.id); committedCount++; if (change.type === "drop_table") { treeDirty = true; const st = useDbViewerStore.getState(); st.closeTabsForTable(change.schema ?? "", change.table ?? ""); } else if ( change.type === "rebuild_table" || (change.type === "ddl" && change.sql && isSchemaModifyingQuery(change.sql)) ) { treeDirty = true; } } catch (e) { const msg = e instanceof Error ? e.message : String(e); markChangeFailed(change.id, msg); notify(`Change failed: ${msg}`, "error"); break; } } if (committedCount > 0) { onCommitted?.(); notify(`${committedCount} change(s) committed`, "success"); } if (treeDirty) { const st = useDbViewerStore.getState(); void st.refreshTree(connectionId, st.currentSchema ?? undefined); } }, [markChangeCommitted, markChangeFailed, notify]); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "s") { e.preventDefault(); void handleCommitAll(); } }; document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [handleCommitAll]); if (changesQueue.length === 0) { return null; } const pendingCount = changesQueue.filter((c) => c.status === "pending").length; return (
Pending Changes
{view === "visual" ? ( changesQueue.map((change) => (
{badgeLabel(change.type)} {tableRef(change)}
{change.status === "pending" ? ( ) : change.status === "committed" ? ( ) : change.status === "failed" ? ( ) : null}
{change.type === "rebuild_table" ? ( <>
{formatChangeLabel(change)}
                    {buildChangeSql(change)}
                  
) : ( <>
{formatChangeLabel(change)}
{formatValueDiff(change) && (
{formatValueDiff(change)!.split(" → ")[0]} {formatValueDiff(change)!.split(" → ")[1]}
)} )}
)) ) : ( changesQueue.map((change) => (
              {buildChangeSql(change)}
            
)) )}
); }