import React, { useState, useEffect, useRef } from 'react';
import { Plus, Upload, FileText, Globe, Download, X, Table, Trash2, Database, Braces, Code, Eye, Minimize2, Maximize2, Search, ArrowUpDown, AlertTriangle, Edit3, ChevronUp, ChevronDown } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CodeMirrorEditor from '../components/CodeMirrorEditor';
import StructuredEditor from "../components/StructuredEditor";
import SEO from '../components/SEO';
import RelatedTools from '../components/RelatedTools';
import Papa from "papaparse";
const TableEditor = () => {
const exportCardRef = useRef(null);
const [data, setData] = useState([]);
const [columns, setColumns] = useState([]);
// Sync table data to localStorage for navigation guard
useEffect(() => {
localStorage.setItem('tableEditorData', JSON.stringify(data));
}, [data]);
const [inputText, setInputText] = useState("");
const [url, setUrl] = useState("");
const [isLoading, setIsLoading] = useState(false);
// eslint-disable-next-line no-unused-vars
const [error, setError] = useState("");
const [useFirstRowAsHeader, setUseFirstRowAsHeader] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const [sortConfig, setSortConfig] = useState({ key: null, direction: "asc" });
const [editingCell, setEditingCell] = useState(null);
const [selectedRows, setSelectedRows] = useState(new Set());
const [activeTab, setActiveTab] = useState("create");
const [selectedColumns, setSelectedColumns] = useState(new Set());
const [editingHeader, setEditingHeader] = useState(null);
const [objectEditorModal, setObjectEditorModal] = useState(null);
const [exportTab, setExportTab] = useState("json");
const [jsonFormat, setJsonFormat] = useState("pretty"); // 'pretty' or 'minify'
// Multi-table management state
const [tableRegistry, setTableRegistry] = useState({}); // { tableName: { data, columns, modified, originalSchema, originalData } }
const [availableTables, setAvailableTables] = useState([]); // List of discovered table names
const [currentTable, setCurrentTable] = useState(""); // Currently active table name
const [originalFileName, setOriginalFileName] = useState(""); // For export naming
const [isTableFullscreen, setIsTableFullscreen] = useState(false); // For fullscreen table view
const [frozenColumns, setFrozenColumns] = useState(0); // Number of columns to freeze on horizontal scroll
const [columnWidths, setColumnWidths] = useState({}); // Store custom column widths
const [resizing, setResizing] = useState(null); // Track which column is being resized
const [showClearConfirmModal, setShowClearConfirmModal] = useState(false); // For clear confirmation modal
const [showInputChangeModal, setShowInputChangeModal] = useState(false); // For input method change confirmation
const [pendingTabChange, setPendingTabChange] = useState(null); // Store pending tab change
const [createNewCompleted, setCreateNewCompleted] = useState(false); // Track if user completed Create New step
const [pasteCollapsed, setPasteCollapsed] = useState(false); // Track if paste input is collapsed
const [pasteDataSummary, setPasteDataSummary] = useState(null); // Summary of pasted data
const [urlDataSummary, setUrlDataSummary] = useState(null); // Summary of URL fetched data
const [fileDataSummary, setFileDataSummary] = useState(null); // Summary of file uploaded data
const [exportExpanded, setExportExpanded] = useState(false); // Track if export section is expanded
const [usageTipsExpanded, setUsageTipsExpanded] = useState(false); // Track if usage tips is expanded
// SQL Export specific state
const [sqlTableName, setSqlTableName] = useState(""); // Table name for SQL export
const [sqlPrimaryKey, setSqlPrimaryKey] = useState(""); // Primary key column for SQL export
// Button feedback states
const [copiedButton, setCopiedButton] = useState(null);
const [downloadedButton, setDownloadedButton] = useState(null);
// Helper function to check if user has data that would be lost
const hasUserData = () => {
// Check if there are multiple tables (imported data)
if (availableTables.length > 0) {
return true;
}
// Check if there's actual user data (not just initial empty structure)
if (data.length === 0) {
return false;
}
// Check if it's just the initial empty table structure
const isInitialEmptyTable =
data.length === 1 &&
columns.length === 3 &&
columns.some((col) => col.name === "id") &&
columns.some((col) => col.name === "name") &&
columns.some((col) => col.name === "value") &&
data[0] &&
Object.values(data[0]).every((val) => val === "" || val === data[0].id);
if (isInitialEmptyTable) {
return false;
}
// Check if all rows are empty
const hasNonEmptyData = data.some((row) =>
Object.entries(row).some(
([key, value]) =>
key !== "id" && value !== null && value !== undefined && value !== "",
),
);
return hasNonEmptyData;
};
// Check if current data has been modified from initial state
const hasModifiedData = () => {
// Check if there are multiple tables (imported data)
if (availableTables.length > 0) {
return true;
}
// Check if there's actual user data (not just initial empty structure)
if (data.length === 0) {
return false;
}
// Check if it's just the initial empty table structure
const isInitialEmptyTable =
data.length === 1 &&
columns.length === 3 &&
columns.some((col) => col.name === "id") &&
columns.some((col) => col.name === "name") &&
columns.some((col) => col.name === "value") &&
data[0] &&
Object.values(data[0]).every((val) => val === "" || val === data[0].id);
if (isInitialEmptyTable) {
return false;
}
// Check if it's the sample data (unchanged)
const isSampleData =
data.length === 4 &&
columns.length === 5 &&
columns.some((col) => col.name === "id") &&
columns.some((col) => col.name === "name") &&
columns.some((col) => col.name === "email") &&
columns.some((col) => col.name === "age") &&
columns.some((col) => col.name === "city") &&
currentTable === "sample_data";
if (isSampleData) {
// Check if sample data is unchanged
const expectedSampleData = [
{
id: 1,
name: "John Doe",
email: "john@example.com",
age: 30,
city: "New York",
},
{
id: 2,
name: "Jane Smith",
email: "jane@example.com",
age: 25,
city: "Los Angeles",
},
{
id: 3,
name: "Bob Johnson",
email: "bob@example.com",
age: 35,
city: "Chicago",
},
{
id: 4,
name: "Alice Brown",
email: "alice@example.com",
age: 28,
city: "Houston",
},
];
const isUnchangedSample = data.every((row, index) => {
const expected = expectedSampleData[index];
return (
expected &&
row.col_0 === expected.id &&
row.col_1 === expected.name &&
row.col_2 === expected.email &&
row.col_3 === expected.age &&
row.col_4 === expected.city
);
});
return !isUnchangedSample;
}
// Any other data is considered modified
return true;
};
// Handle tab change with confirmation if data exists
const handleTabChange = (newTab) => {
// For Create New tab, use more specific logic
if (newTab === "create" && activeTab !== "create") {
if (hasModifiedData()) {
setPendingTabChange(newTab);
setShowInputChangeModal(true);
} else {
setActiveTab(newTab);
setCreateNewCompleted(false);
}
} else if (hasUserData() && activeTab !== newTab) {
setPendingTabChange(newTab);
setShowInputChangeModal(true);
} else {
// No data or same tab, proceed directly
setActiveTab(newTab);
// If clicking Create New again after completion, show the options again
if (newTab === "create" && createNewCompleted) {
setCreateNewCompleted(false);
}
// Don't auto-create table for 'create' tab - let user choose Start Empty or Load Sample
}
};
// Clear all data function
const clearAllData = () => {
setData([]);
setColumns([]);
setTableRegistry({});
setAvailableTables([]);
setCurrentTable("");
setOriginalFileName("");
setCreateNewCompleted(false);
};
// Confirm input method change and clear data
const confirmInputChange = () => {
// Handle special Create New button actions
if (pendingTabChange === "create_empty") {
clearAllData();
createEmptyTable();
setCreateNewCompleted(true);
} else if (pendingTabChange === "create_sample") {
clearAllData();
// Load sample data
const sampleData = [
{
id: 1,
name: "John Doe",
email: "john@example.com",
age: 30,
city: "New York",
},
{
id: 2,
name: "Jane Smith",
email: "jane@example.com",
age: 25,
city: "Los Angeles",
},
{
id: 3,
name: "Bob Johnson",
email: "bob@example.com",
age: 35,
city: "Chicago",
},
{
id: 4,
name: "Alice Brown",
email: "alice@example.com",
age: 28,
city: "Houston",
},
];
const sampleColumns = [
{ id: "col_0", name: "id" },
{ id: "col_1", name: "name" },
{ id: "col_2", name: "email" },
{ id: "col_3", name: "age" },
{ id: "col_4", name: "city" },
];
const formattedData = sampleData.map((row, index) => ({
id: `row_${index}`,
col_0: row.id,
col_1: row.name,
col_2: row.email,
col_3: row.age,
col_4: row.city,
}));
setData(formattedData);
setColumns(sampleColumns);
setCurrentTable("sample_data");
setOriginalFileName("sample_data");
setCreateNewCompleted(true);
} else {
// Handle regular tab switches
clearAllData();
setActiveTab(pendingTabChange);
// If switching to create tab, reset completion state to show options
if (pendingTabChange === "create") {
setCreateNewCompleted(false);
}
}
// Close modal
setShowInputChangeModal(false);
setPendingTabChange(null);
};
// SQL parsing functions for multi-table support
const parseMultiTableSQL = (sqlContent) => {
const tables = {};
const lines = sqlContent.split("\n");
let currentTable = null;
let currentSchema = "";
let insideCreateTable = false;
let insideInsert = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// Detect CREATE TABLE statements
const createTableMatch = line.match(/CREATE TABLE\s+`?(\w+)`?\s*\(/i);
if (createTableMatch) {
currentTable = createTableMatch[1];
currentSchema = line + "\n";
insideCreateTable = true;
if (!tables[currentTable]) {
tables[currentTable] = {
name: currentTable,
schema: "",
data: [],
columns: [],
rowCount: 0,
};
}
continue;
}
// Continue collecting CREATE TABLE schema
if (insideCreateTable) {
currentSchema += line + "\n";
if (line.includes(");") || line.includes(") ENGINE")) {
insideCreateTable = false;
if (currentTable && tables[currentTable]) {
tables[currentTable].schema = currentSchema;
}
}
continue;
}
// Detect INSERT statements
const insertMatch = line.match(
/INSERT INTO\s+`?(\w+)`?\s*(?:\([^)]+\))?\s*VALUES/i,
);
if (insertMatch) {
currentTable = insertMatch[1];
insideInsert = true;
if (!tables[currentTable]) {
tables[currentTable] = {
name: currentTable,
schema: "",
data: [],
columns: [],
rowCount: 0,
};
}
// Extract column names from INSERT statement
const columnsMatch = line.match(/INSERT INTO\s+`?\w+`?\s*\(([^)]+)\)/i);
if (columnsMatch && tables[currentTable].columns.length === 0) {
const columnNames = columnsMatch[1]
.split(",")
.map((col) => col.trim().replace(/`/g, ""));
tables[currentTable].columns = columnNames.map((name, index) => ({
id: `col_${index}`,
name: name,
}));
}
// Extract VALUES data
const valuesMatch = line.match(/VALUES\s*(.+)/i);
if (valuesMatch) {
const valuesStr = valuesMatch[1];
const rows = parseInsertValues(valuesStr);
tables[currentTable].data.push(...rows);
tables[currentTable].rowCount += rows.length;
}
continue;
}
// Continue collecting INSERT data from multi-line statements
if (
insideInsert &&
currentTable &&
line.includes("(") &&
line.includes(")")
) {
const rows = parseInsertValues(line);
tables[currentTable].data.push(...rows);
tables[currentTable].rowCount += rows.length;
if (line.endsWith(";")) {
insideInsert = false;
}
}
}
return tables;
};
const parseInsertValues = (valuesStr) => {
const rows = [];
// Better parsing: find complete tuples first, then parse values respecting quotes
const tuples = [];
let currentTuple = "";
let parenCount = 0;
let inString = false;
let stringChar = "";
for (let i = 0; i < valuesStr.length; i++) {
const char = valuesStr[i];
const prevChar = i > 0 ? valuesStr[i - 1] : "";
if (!inString && (char === '"' || char === "'")) {
inString = true;
stringChar = char;
} else if (inString && char === stringChar && prevChar !== "\\") {
inString = false;
stringChar = "";
}
if (!inString) {
if (char === "(") parenCount++;
if (char === ")") parenCount--;
}
currentTuple += char;
if (!inString && parenCount === 0 && char === ")") {
tuples.push(currentTuple.trim());
currentTuple = "";
// Skip comma and whitespace
while (
i + 1 < valuesStr.length &&
(valuesStr[i + 1] === "," || valuesStr[i + 1] === " ")
) {
i++;
}
}
}
tuples.forEach((tuple) => {
// Remove outer parentheses
const innerContent = tuple.replace(/^\(|\)$/g, "").trim();
// Parse values respecting quotes and nested structures
const values = [];
let currentValue = "";
let inString = false;
let stringChar = "";
let parenCount = 0;
for (let i = 0; i < innerContent.length; i++) {
const char = innerContent[i];
const prevChar = i > 0 ? innerContent[i - 1] : "";
if (!inString && (char === '"' || char === "'")) {
inString = true;
stringChar = char;
currentValue += char;
continue;
} else if (inString && char === stringChar && prevChar !== "\\") {
inString = false;
stringChar = "";
currentValue += char;
continue;
}
if (!inString) {
if (char === "(" || char === "{" || char === "[") parenCount++;
if (char === ")" || char === "}" || char === "]") parenCount--;
if (char === "," && parenCount === 0) {
values.push(currentValue.trim());
currentValue = "";
continue;
}
}
currentValue += char;
}
if (currentValue.trim()) {
values.push(currentValue.trim());
}
const processedValues = values.map((val) => {
val = String(val).trim();
// Remove quotes and handle NULL
if (val === "NULL") return "";
if (val.startsWith("'") && val.endsWith("'")) {
let unquoted = val.slice(1, -1);
// Handle escaped JSON properly (same logic as main parser)
if (unquoted.includes('\\"') || unquoted.includes("\\'")) {
// Try to detect if this is escaped JSON
const isEscapedJson = (str) => {
const unescaped = str.replace(/\\"/g, '"').replace(/\\'/g, "'");
const trimmed = unescaped.trim();
if (
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"))
) {
try {
JSON.parse(trimmed);
return true;
} catch (e) {
return false;
}
}
return false;
};
// If it's escaped JSON, unescape it
if (isEscapedJson(unquoted)) {
unquoted = unquoted.replace(/\\"/g, '"').replace(/\\'/g, "'");
} else {
// Only unescape single quotes for non-JSON strings
unquoted = unquoted.replace(/''/g, "'");
}
} else {
// Only unescape single quotes, preserve JSON structure
unquoted = unquoted.replace(/''/g, "'");
}
return unquoted;
}
return val;
});
const rowObj = {};
processedValues.forEach((value, index) => {
rowObj[`col_${index}`] = value;
});
rowObj.id = Date.now() + Math.random(); // Generate unique ID
rows.push(rowObj);
});
return rows;
};
// Detect structured data formats in cell values
const detectCellFormat = (value) => {
if (!value || typeof value !== "string" || value.length < 2) return null;
const trimmed = value.trim();
// JSON Object detection
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
try {
JSON.parse(trimmed);
return { type: "json", subtype: "object", icon: Braces };
} catch {
return null; // If it's not valid JSON, don't treat it as JSON
}
}
// JSON Array detection
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
try {
JSON.parse(trimmed);
return { type: "json", subtype: "array", icon: Braces };
} catch {
return null; // If it's not valid JSON, don't treat it as JSON
}
}
// PHP Serialized detection
if (trimmed.match(/^[aOs]:\d+:/)) {
return { type: "php_serialized", subtype: "serialized", icon: Code };
}
// Base64 JSON detection (common in APIs)
if (trimmed.match(/^[A-Za-z0-9+/]+=*$/) && trimmed.length > 20) {
try {
const decoded = atob(trimmed);
JSON.parse(decoded);
return { type: "base64_json", subtype: "encoded", icon: Eye };
} catch {}
}
return null;
};
// Open Object Editor modal for structured data
const openObjectEditor = (rowId, columnId, value, format) => {
const column = columns.find((c) => c.id === columnId);
// Value should already be properly unescaped by the SQL parser
setObjectEditorModal({
rowId,
columnId,
rowIndex: data.findIndex((r) => r.id === rowId) + 1,
columnName: column?.name || "Unknown",
originalValue: value,
currentValue: value,
format,
isValid: true,
});
};
// Close Object Editor modal
const closeObjectEditor = () => {
setObjectEditorModal(null);
};
// Apply changes from Object Editor back to table
const applyObjectEditorChanges = (newValue) => {
if (!objectEditorModal) return;
const { rowId, columnId } = objectEditorModal;
setData(
data.map((row) =>
row.id === rowId ? { ...row, [columnId]: newValue } : row,
),
);
closeObjectEditor();
};
// Parse CSV/TSV data
const parseData = (text, hasHeaders = true) => {
const result = Papa.parse(text.trim(), {
header: false,
skipEmptyLines: true,
delimiter: text.includes("\t") ? "\t" : ",",
});
if (result.errors.length > 0) {
throw new Error(result.errors[0].message);
}
const rows = result.data;
if (rows.length === 0) {
throw new Error("No data found");
}
let headers;
let dataRows;
if (hasHeaders && rows.length > 0) {
headers = rows[0].map((header, index) => ({
id: `col_${index}`,
name: header || `Column ${index + 1}`,
type: "text",
}));
dataRows = rows.slice(1);
} else {
headers = rows[0].map((_, index) => ({
id: `col_${index}`,
name: `Column ${index + 1}`,
type: "text",
}));
dataRows = rows;
}
const tableData = dataRows.map((row, index) => {
const rowData = { id: `row_${index}` };
headers.forEach((header, colIndex) => {
rowData[header.id] = row[colIndex] || "";
});
return rowData;
});
setColumns(headers);
setData(tableData);
setError("");
return tableData.length; // Return actual row count
};
// Parse SQL data
const parseSqlData = (text) => {
try {
// Clean the SQL text - remove comments and unnecessary lines
const cleanLines = text
.split("\n")
.map((line) => line.trim())
.filter(
(line) =>
line &&
!line.startsWith("--") &&
!line.startsWith("/*") &&
!line.startsWith("*/") &&
!line.startsWith("SET ") &&
!line.startsWith("START ") &&
!line.startsWith("COMMIT") &&
!line.startsWith("/*!") &&
!line.startsWith("CREATE ") &&
!line.startsWith("ALTER ") &&
!line.startsWith("DROP ") &&
line !== ";",
);
// Join lines and split by semicolons to handle multi-line INSERT statements
const cleanText = cleanLines.join(" ");
const statements = cleanText
.split(";")
.map((stmt) => stmt.trim())
.filter((stmt) => stmt);
// Look for INSERT statements
const insertStatements = statements.filter(
(stmt) =>
stmt.toLowerCase().includes("insert into") &&
stmt.toLowerCase().includes("values"),
);
if (insertStatements.length === 0) {
throw new Error(
"No INSERT statements found. Please provide SQL with INSERT INTO ... VALUES statements.",
);
}
// Parse the first INSERT to get table structure
const firstInsert = insertStatements[0];
// Extract table name and columns
const tableMatch = firstInsert.match(
/insert\s+into\s+`?(\w+)`?\s*\(([^)]+)\)/i,
);
if (!tableMatch) {
throw new Error(
"Could not parse table structure. Expected format: INSERT INTO table (col1, col2) VALUES ...",
);
}
const columnsPart = tableMatch[2];
// Parse column names
const columnNames = columnsPart
.split(",")
.map((col) => col.trim().replace(/[`'"]/g, ""))
.filter((col) => col);
// Create headers
const headers = columnNames.map((name, index) => ({
id: `col_${index}`,
name: name,
type: "text",
}));
// Parse all INSERT statements for data
const allRows = [];
insertStatements.forEach((statement, statementIndex) => {
// Extract VALUES part
const valuesMatch = statement.match(/values\s*(.+)$/i);
if (!valuesMatch) return;
let valuesStr = valuesMatch[1].trim();
// Handle multiple value sets in one INSERT
// Split by ), ( but be careful with nested parentheses
const valueSets = [];
let currentSet = "";
let parenCount = 0;
let inString = false;
let stringChar = "";
for (let i = 0; i < valuesStr.length; i++) {
const char = valuesStr[i];
const prevChar = i > 0 ? valuesStr[i - 1] : "";
if (!inString && (char === '"' || char === "'")) {
inString = true;
stringChar = char;
} else if (inString && char === stringChar && prevChar !== "\\") {
inString = false;
stringChar = "";
}
if (!inString) {
if (char === "(") parenCount++;
if (char === ")") parenCount--;
}
currentSet += char;
if (!inString && parenCount === 0 && char === ")") {
valueSets.push(currentSet.trim());
currentSet = "";
// Skip comma and whitespace
while (
i + 1 < valuesStr.length &&
(valuesStr[i + 1] === "," || valuesStr[i + 1] === " ")
) {
i++;
}
}
}
// Parse each value set
valueSets.forEach((valueSet, setIndex) => {
// Remove outer parentheses
valueSet = valueSet.replace(/^\(|\)$/g, "").trim();
if (valueSet.endsWith(";")) {
valueSet = valueSet.slice(0, -1).trim();
}
// Parse individual values
const values = [];
let currentValue = "";
let inString = false;
let stringChar = "";
let parenCount = 0;
for (let i = 0; i < valueSet.length; i++) {
const char = valueSet[i];
const prevChar = i > 0 ? valueSet[i - 1] : "";
if (!inString && (char === '"' || char === "'")) {
inString = true;
stringChar = char;
continue; // Don't include quote in value
} else if (inString && char === stringChar && prevChar !== "\\") {
inString = false;
stringChar = "";
continue; // Don't include quote in value
}
if (!inString) {
if (char === "(") parenCount++;
if (char === ")") parenCount--;
if (char === "," && parenCount === 0) {
values.push(currentValue.trim());
currentValue = "";
continue;
}
}
currentValue += char;
}
if (currentValue.trim()) {
values.push(currentValue.trim());
}
// Create row data
if (values.length === headers.length) {
const rowData = { id: `row_${allRows.length}` };
headers.forEach((header, index) => {
let value = values[index] || "";
// Handle NULL values
if (value.toLowerCase() === "null") {
value = "";
}
// Clean up the value - handle escaped JSON properly
if (value.includes('\\"') || value.includes("\\'")) {
// Try to detect if this is escaped JSON
const isEscapedJson = (str) => {
// Check if it looks like escaped JSON (starts with '{' or '[' after unescaping)
const unescaped = str
.replace(/\\"/g, '"')
.replace(/\\'/g, "'");
const trimmed = unescaped.trim();
if (
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"))
) {
try {
JSON.parse(trimmed);
return true;
} catch (e) {
return false;
}
}
return false;
};
// If it's escaped JSON, unescape it
if (isEscapedJson(value)) {
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
} else {
// Only unescape single quotes for non-JSON strings
if (value.includes("\\'")) {
value = value.replace(/\\'/g, "'");
}
}
}
rowData[header.id] = value;
});
allRows.push(rowData);
}
});
});
if (allRows.length === 0) {
throw new Error(
"No data rows could be parsed from the SQL statements.",
);
}
setColumns(headers);
setData(allRows);
setError("");
} catch (err) {
setError(`Failed to parse SQL: ${err.message}`);
}
};
// Parse JSON data
const parseJsonData = (text) => {
const jsonData = JSON.parse(text);
if (!Array.isArray(jsonData)) {
throw new Error("JSON must be an array of objects");
}
if (jsonData.length === 0) {
throw new Error("Array is empty");
}
// Extract columns from first object
const firstItem = jsonData[0];
const headers = Object.keys(firstItem).map((key, index) => ({
id: `col_${index}`,
name: key,
type: typeof firstItem[key] === "number" ? "number" : "text",
}));
// Convert to table format
const tableData = jsonData.map((item, index) => {
const rowData = { id: `row_${index}` };
headers.forEach((header) => {
rowData[header.id] = item[header.name] || "";
});
return rowData;
});
setColumns(headers);
setData(tableData);
setError("");
return tableData.length; // Return row count for summary
};
// Handle text input
const handleTextInput = () => {
if (!inputText.trim()) {
setError("Please enter some data");
setPasteCollapsed(false);
return;
}
const trimmed = inputText.trim();
let format = '';
let rowCount = 0;
try {
// Try to detect format
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
// JSON array
rowCount = parseJsonData(trimmed);
format = 'JSON';
} else if (
trimmed.toLowerCase().includes("insert into") &&
trimmed.toLowerCase().includes("values")
) {
// SQL INSERT statements
parseSqlData(trimmed);
format = 'SQL';
// Get row count from state after parse
rowCount = data.length;
} else {
// CSV/TSV
parseData(trimmed, useFirstRowAsHeader);
format = trimmed.includes('\t') ? 'TSV' : 'CSV';
// Get row count from state after parse
rowCount = data.length;
}
// Collapse input and show summary
setPasteDataSummary({
format: format,
size: inputText.length,
rows: rowCount || data.length // Use rowCount if available, fallback to data.length
});
setPasteCollapsed(true);
setCreateNewCompleted(true);
setError('');
} catch (err) {
// Keep input expanded on error
setPasteCollapsed(false);
setError(err.message || 'Failed to parse data');
}
};
// Fetch data from URL
const fetchUrlData = async () => {
if (!url.trim()) {
setError("Please enter a valid URL");
return;
}
setIsLoading(true);
setError("");
try {
const response = await fetch(url.trim());
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get("content-type") || "";
const text = await response.text();
let format = '';
let rowCount = 0;
if (contentType.includes("application/json") || url.includes(".json")) {
rowCount = parseJsonData(text);
format = 'JSON';
} else {
rowCount = parseData(text, useFirstRowAsHeader);
format = text.includes('\t') ? 'TSV' : 'CSV';
}
// Set summary for URL fetch
setUrlDataSummary({
format: format,
size: text.length,
rows: rowCount,
url: url.trim()
});
setCreateNewCompleted(true);
} catch (err) {
setError(`Failed to fetch data: ${err.message}`);
setUrlDataSummary(null);
} finally {
setIsLoading(false);
}
};
// Multi-table management functions
const switchToTable = (tableName) => {
if (!tableName || !tableRegistry[tableName]) return;
// Save current table state if we have one
if (currentTable && tableRegistry[currentTable]) {
setTableRegistry((prev) => ({
...prev,
[currentTable]: {
...prev[currentTable],
data: data,
columns: columns,
modified: true,
},
}));
}
// Load new table
const tableData = tableRegistry[tableName];
setCurrentTable(tableName);
setData(tableData.data);
setColumns(tableData.columns);
setSearchTerm("");
setSelectedRows(new Set());
setSelectedColumns(new Set());
};
const initializeTablesFromSQL = (sqlContent, fileName = "") => {
const discoveredTables = parseMultiTableSQL(sqlContent);
const tableNames = Object.keys(discoveredTables);
if (tableNames.length === 0) {
console.error("❌ No tables found in SQL file");
setError("No tables found in SQL file");
return;
}
// Initialize table registry
const registry = {};
tableNames.forEach((tableName) => {
const tableInfo = discoveredTables[tableName];
registry[tableName] = {
data: tableInfo.data,
columns: tableInfo.columns,
modified: false,
originalSchema: tableInfo.schema,
originalData: [...tableInfo.data],
rowCount: tableInfo.rowCount,
};
});
setTableRegistry(registry);
setAvailableTables(tableNames);
setOriginalFileName(fileName.replace(/\.[^/.]+$/, "")); // Remove extension
// Load first table by default
const firstTable = tableNames[0];
setCurrentTable(firstTable);
setData(registry[firstTable].data);
setColumns(registry[firstTable].columns);
};
// Handle file upload
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (!file) return;
setIsLoading(true);
setError("");
const reader = new FileReader();
reader.onload = (e) => {
try {
const content = e.target.result;
let format = '';
let rowCount = 0;
// Check if it's SQL file for multi-table support
if (file.name.toLowerCase().endsWith(".sql")) {
initializeTablesFromSQL(content, file.name);
format = 'SQL';
// For SQL, we need to wait for state update, use a timeout
setTimeout(() => {
setFileDataSummary({
format: format,
size: content.length,
rows: data.length,
filename: file.name
});
}, 100);
} else if (file.name.toLowerCase().endsWith(".json")) {
rowCount = parseJsonData(content);
format = 'JSON';
setFileDataSummary({
format: format,
size: content.length,
rows: rowCount,
filename: file.name
});
} else {
rowCount = parseData(content, useFirstRowAsHeader);
format = file.name.toLowerCase().endsWith(".tsv") ? 'TSV' : 'CSV';
setFileDataSummary({
format: format,
size: content.length,
rows: rowCount,
filename: file.name
});
}
setCreateNewCompleted(true);
} catch (err) {
console.error("❌ File upload error:", err);
setError("Failed to read file: " + err.message);
setFileDataSummary(null);
} finally {
setIsLoading(false);
}
};
reader.readAsText(file);
};
// Filter and sort data
const filteredAndSortedData = React.useMemo(() => {
let filtered = data;
// Apply search filter
if (searchTerm) {
filtered = data.filter((row) =>
columns.some((col) =>
String(row[col.id] || "")
.toLowerCase()
.includes(searchTerm.toLowerCase()),
),
);
}
// Apply sorting
if (sortConfig.key) {
filtered = [...filtered].sort((a, b) => {
const aVal = a[sortConfig.key] || "";
const bVal = b[sortConfig.key] || "";
if (sortConfig.direction === "asc") {
return aVal.toString().localeCompare(bVal.toString());
} else {
return bVal.toString().localeCompare(aVal.toString());
}
});
}
return filtered;
}, [data, searchTerm, sortConfig, columns]);
// Handle sorting
const handleSort = (columnId) => {
setSortConfig((prev) => ({
key: columnId,
direction:
prev.key === columnId && prev.direction === "asc" ? "desc" : "asc",
}));
};
// Add new row
const addRow = () => {
const newRow = { id: `row_${Date.now()}` };
columns.forEach((col) => {
newRow[col.id] = "";
});
setData([...data, newRow]);
};
// Column resize functions
const getColumnWidth = (columnId) => {
return columnWidths[columnId] || 150; // Default width
};
const handleResizeStart = (e, columnId) => {
e.preventDefault();
const startX = e.clientX;
const startWidth = getColumnWidth(columnId);
setResizing({ columnId, startX, startWidth });
const handleMouseMove = (e) => {
if (!resizing && resizing?.columnId === columnId) return;
const deltaX = e.clientX - startX;
const newWidth = Math.max(50, startWidth + deltaX); // Minimum width of 50px
setColumnWidths(prev => ({
...prev,
[columnId]: newWidth
}));
};
const handleMouseUp = () => {
setResizing(null);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
// Add new column
const addColumn = () => {
const newColumnId = `col_${Date.now()}`;
const newColumn = {
id: newColumnId,
name: `Column ${columns.length + 1}`,
type: "text",
};
setColumns([...columns, newColumn]);
// Add empty values for the new column in all existing rows
setData(
data.map((row) => ({
...row,
[newColumnId]: "",
})),
);
// Auto-trigger header editing for the new column
setEditingHeader(newColumnId);
// Auto-scroll to the right to show the new column and focus on header input
setTimeout(() => {
// Try multiple selectors to find the table container
const selectors = [
'[class*="overflow-auto"][class*="max-h-"]',
'.overflow-auto',
'div[class*="overflow-auto"]'
];
let tableContainer = null;
for (const selector of selectors) {
tableContainer = document.querySelector(selector);
if (tableContainer) break;
}
if (tableContainer) {
// Check if horizontal scrolling is needed
const needsScroll = tableContainer.scrollWidth > tableContainer.clientWidth;
if (needsScroll) {
// Smooth scroll to the far right to show the new column
tableContainer.scrollTo({
left: tableContainer.scrollWidth - tableContainer.clientWidth,
behavior: 'smooth'
});
}
}
// Focus on the header input field after scroll
setTimeout(() => {
const headerInput = document.querySelector(`input[value="${newColumn.name}"]`);
if (headerInput) {
headerInput.focus();
headerInput.select(); // Select all text for easy replacement
}
}, 100);
}, 200);
};
// Delete selected rows
const deleteSelectedRows = () => {
setData(data.filter((row) => !selectedRows.has(row.id)));
setSelectedRows(new Set());
};
// Delete selected columns
const deleteSelectedColumns = () => {
const remainingColumns = columns.filter(
(col) => !selectedColumns.has(col.id),
);
setColumns(remainingColumns);
// Remove deleted column data from all rows
setData(
data.map((row) => {
const newRow = { ...row };
selectedColumns.forEach((colId) => {
delete newRow[colId];
});
return newRow;
}),
);
setSelectedColumns(new Set());
};
// Handle cell edit with proper event handling
const handleCellEdit = (rowId, columnId, value) => {
setData(
data.map((row) =>
row.id === rowId ? { ...row, [columnId]: value } : row,
),
);
};
// Handle cell key events
const handleCellKeyDown = (e, rowId, columnId) => {
if (e.key === "Enter" || e.key === "Escape") {
setEditingCell(null);
}
};
// Handle header edit
const handleHeaderEdit = (columnId, newName) => {
setColumns(
columns.map((col) =>
col.id === columnId ? { ...col, name: newName } : col,
),
);
};
// Handle header key events
const handleHeaderKeyDown = (e, columnId) => {
if (e.key === "Enter" || e.key === "Escape") {
setEditingHeader(null);
}
};
// Export functions
const getExportData = (format) => {
if (data.length === 0) return "";
switch (format) {
case "json":
// Convert data to use column names instead of column IDs
// Use Object.fromEntries with columns.map to preserve column order
const jsonData = data.map((row) => {
return Object.fromEntries(
columns.map((col) => [col.name, row[col.id] || ""]),
);
});
return jsonFormat === "pretty"
? JSON.stringify(jsonData, null, 2)
: JSON.stringify(jsonData);
case "csv":
return Papa.unparse({
fields: columns.map((col) => col.name),
data: data.map((row) => columns.map((col) => row[col.id] || "")),
});
case "tsv":
return Papa.unparse(
{
fields: columns.map((col) => col.name),
data: data.map((row) => columns.map((col) => row[col.id] || "")),
},
{ delimiter: "\t" },
);
case "sql":
return generateMultiTableSQL();
default:
return "";
}
};
const downloadFile = (content, filename, mimeType) => {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const generateMultiTableSQL = () => {
// Get current table state without triggering re-render
const currentTableData =
currentTable && tableRegistry[currentTable]
? {
...tableRegistry[currentTable],
data: data,
columns: columns,
modified: true,
}
: null;
const sqlParts = [];
const dbName = originalFileName || "database";
// Add header comment
sqlParts.push(`-- Database: ${dbName}`);
sqlParts.push(`-- Generated by Table Editor`);
sqlParts.push(`-- Export Date: ${new Date().toISOString()}`);
sqlParts.push("");
// If we have multiple tables, export all of them
if (availableTables.length > 1) {
sqlParts.push(`-- Tables: ${availableTables.join(", ")}`);
sqlParts.push("");
availableTables.forEach((tableName) => {
const tableData =
tableName === currentTable && currentTableData
? currentTableData
: tableRegistry[tableName];
if (!tableData) return;
const actualData = tableData.data;
const actualColumns = tableData.columns;
const isModified = tableData.modified;
sqlParts.push(`-- ================================================`);
sqlParts.push(
`-- Table: ${tableName} (${actualData.length} rows, ${actualColumns.length} columns)`,
);
sqlParts.push(`-- Status: ${isModified ? "Modified" : "Original"}`);
sqlParts.push(`-- ================================================`);
sqlParts.push("");
// Add DROP TABLE
sqlParts.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
sqlParts.push("");
// Add CREATE TABLE (use original schema if available)
if (tableData.originalSchema) {
sqlParts.push(tableData.originalSchema);
} else {
// Generate intelligent CREATE TABLE based on data analysis
sqlParts.push(
generateCreateTableStatement(
tableName,
actualColumns,
sqlPrimaryKey,
actualData,
),
);
}
sqlParts.push("");
// Add INSERT statements if there's data
if (actualData.length > 0) {
const columnNames = actualColumns.map((col) => col.name);
const insertStatements = actualData.map((row) => {
const values = actualColumns.map((col) => {
const value = row[col.id] || "";
if (value === "" || value === null || value === undefined)
return "NULL";
return typeof value === "string"
? `'${value.replace(/'/g, "''")}'`
: value;
});
return `INSERT INTO \`${tableName}\` (\`${columnNames.join("`, `")}\`) VALUES (${values.join(", ")});`;
});
sqlParts.push(
`INSERT INTO \`${tableName}\` (\`${columnNames.join("`, `")}\`) VALUES`,
);
insertStatements.forEach((stmt, index) => {
const values = stmt.match(/VALUES \((.+)\);/)[1];
sqlParts.push(
`(${values})${index === insertStatements.length - 1 ? ";" : ","}`,
);
});
} else {
sqlParts.push(`-- No data for table ${tableName}`);
}
sqlParts.push("");
});
} else {
// Single table export
const tableName =
sqlTableName || currentTable || originalFileName || "table_data";
const columnNames = columns.map((col) => col.name);
sqlParts.push(`-- Table: ${tableName}`);
sqlParts.push("");
sqlParts.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
sqlParts.push("");
// Use original schema if available
const singleTableData = currentTableData || tableRegistry[tableName];
if (singleTableData?.originalSchema) {
sqlParts.push(singleTableData.originalSchema);
} else {
sqlParts.push(
generateCreateTableStatement(tableName, columns, sqlPrimaryKey),
);
}
sqlParts.push("");
if (data.length > 0) {
const insertStatements = data.map((row) => {
const values = columns.map((col) => {
const value = row[col.id] || "";
if (value === "" || value === null || value === undefined)
return "NULL";
return typeof value === "string"
? `'${value.replace(/'/g, "''")}'`
: value;
});
return `(${values.join(", ")})`;
});
sqlParts.push(
`INSERT INTO \`${tableName}\` (\`${columnNames.join("`, `")}\`) VALUES`,
);
insertStatements.forEach((values, index) => {
sqlParts.push(
`${values}${index === insertStatements.length - 1 ? ";" : ","}`,
);
});
}
}
return sqlParts.join("\n");
};
// Intelligent column type detection for SQL export
const analyzeColumnData = (columnId, tableData) => {
const values = tableData
.map((row) => row[columnId])
.filter((val) => val !== null && val !== undefined && val !== "");
if (values.length === 0) {
return { type: "VARCHAR(100)", nullable: true, analysis: "Empty column" };
}
let maxLength = 0;
let hasJson = false;
let hasDate = false;
let hasNumber = false;
let hasEmail = false;
let hasUrl = false;
let hasPhone = false;
let allIntegers = true;
let dateFormats = new Set();
values.forEach((val) => {
const strVal = String(val);
maxLength = Math.max(maxLength, strVal.length);
// JSON detection
if (
(strVal.startsWith("{") && strVal.endsWith("}")) ||
(strVal.startsWith("[") && strVal.endsWith("]"))
) {
try {
JSON.parse(strVal);
hasJson = true;
} catch {}
}
// Date detection (various formats)
const datePatterns = [
/^\d{1,2}\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{4}$/i, // "17 Mar 2022"
/^\d{4}-\d{2}-\d{2}$/, // "2022-03-17"
/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/, // "2022-03-17 10:30:00"
/^\d{1,2}\/\d{1,2}\/\d{4}$/, // "3/17/2022"
/^\d{2}-\d{2}-\d{4}$/, // "17-03-2022"
];
datePatterns.forEach((pattern, index) => {
if (pattern.test(strVal)) {
hasDate = true;
dateFormats.add(index);
}
});
// Number detection
if (!isNaN(strVal) && strVal.trim() !== "") {
hasNumber = true;
if (!Number.isInteger(Number(strVal))) {
allIntegers = false;
}
} else {
allIntegers = false;
}
// Email detection
if (strVal.includes("@") && strVal.includes(".")) {
hasEmail = true;
}
// URL detection
if (strVal.startsWith("http://") || strVal.startsWith("https://")) {
hasUrl = true;
}
// Phone detection (basic)
if (/^[+\d\s\-()]{10,}$/.test(strVal)) {
hasPhone = true;
}
});
const nullableCount = tableData.length - values.length;
const nullable = nullableCount > 0;
// Determine best column type
if (hasJson) {
return {
type: "JSON",
nullable,
analysis: `JSON data detected (max length: ${maxLength})`,
fallbackType:
maxLength > 65535
? "LONGTEXT"
: maxLength > 16777215
? "MEDIUMTEXT"
: "TEXT",
};
}
if (hasNumber && values.every((val) => !isNaN(val) && String(val).trim() !== "")) {
if (allIntegers) {
const maxVal = Math.max(...values.map((v) => Math.abs(Number(v))));
if (maxVal < 128)
return {
type: "TINYINT",
nullable,
analysis: "Small integers (-128 to 127)",
};
if (maxVal < 32768)
return {
type: "SMALLINT",
nullable,
analysis: "Small integers (-32,768 to 32,767)",
};
if (maxVal < 2147483648)
return { type: "INT", nullable, analysis: "Standard integers" };
return { type: "BIGINT", nullable, analysis: "Large integers" };
} else {
return { type: "DECIMAL(10,2)", nullable, analysis: "Decimal numbers" };
}
}
if (hasDate && dateFormats.size === 1) {
const formatNames = ["TEXT", "DATE", "DATETIME", "DATE", "DATE"];
const detectedFormat = Array.from(dateFormats)[0];
return {
type: formatNames[detectedFormat] || "TEXT",
nullable,
analysis: `Date format detected (pattern ${detectedFormat})`,
};
}
// Text-based analysis
if (maxLength <= 50) {
const size = Math.max(50, Math.ceil(maxLength * 1.2));
let analysis = `Short text (max: ${maxLength})`;
if (hasEmail) analysis += ", emails detected";
if (hasPhone) analysis += ", phone numbers detected";
return { type: `VARCHAR(${size})`, nullable, analysis };
}
if (maxLength <= 255) {
const size = Math.max(100, Math.ceil(maxLength * 1.2));
let analysis = `Medium text (max: ${maxLength})`;
if (hasUrl) analysis += ", URLs detected";
return { type: `VARCHAR(${size})`, nullable, analysis };
}
if (maxLength <= 1000) {
const size = Math.ceil(maxLength * 1.2);
return {
type: `VARCHAR(${size})`,
nullable,
analysis: `Long text (max: ${maxLength})`,
};
}
if (maxLength <= 65535) {
return {
type: "TEXT",
nullable,
analysis: `Very long text (max: ${maxLength})`,
};
}
if (maxLength <= 16777215) {
return {
type: "MEDIUMTEXT",
nullable,
analysis: `Extra long text (max: ${maxLength})`,
};
}
return {
type: "LONGTEXT",
nullable,
analysis: `Extremely long text (max: ${maxLength})`,
};
};
const generateCreateTableStatement = (
tableName,
tableColumns,
primaryKeyColumn = null,
tableData = data,
) => {
const analysisResults = [];
const columnDefs = tableColumns.map((col) => {
const analysis = analyzeColumnData(col.id, tableData);
analysisResults.push({ column: col.name, ...analysis });
let def = ` \`${col.name}\` ${analysis.type}`;
// Add PRIMARY KEY if this column is selected as primary key
if (primaryKeyColumn && col.name === primaryKeyColumn) {
def += " NOT NULL PRIMARY KEY";
} else {
def += analysis.nullable ? " DEFAULT NULL" : " NOT NULL";
}
return def;
});
// Store analysis for user display
window.lastSqlAnalysis = analysisResults;
return `CREATE TABLE \`${tableName}\` (\n${columnDefs.join(",\n")}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`;
};
const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
console.error("Failed to copy:", err);
}
};
const createEmptyTable = () => {
const newTableName = "new_table";
const initialColumns = [
{ id: "col_0", name: "id" },
{ id: "col_1", name: "name" },
{ id: "col_2", name: "value" },
];
// Create one empty row to make the table visible
const initialData = [
{
id: Date.now(),
col_0: "",
col_1: "",
col_2: "",
},
];
setData(initialData);
setColumns(initialColumns);
setCurrentTable(newTableName);
setAvailableTables([newTableName]);
setTableRegistry({
[newTableName]: {
data: [],
columns: initialColumns,
modified: true,
originalSchema: "",
originalData: [],
rowCount: 0,
},
});
setInputText("");
setUrl("");
setError("");
setSearchTerm("");
setSortConfig({ key: null, direction: "asc" });
setSelectedRows(new Set());
setSelectedColumns(new Set());
setOriginalFileName("");
};
const clearData = () => {
// Show confirmation modal
setShowClearConfirmModal(true);
};
return (
<>
Choose how you'd like to begin working with your data
💡 Tip: You can always import data later
using the URL, Paste, or Open tabs, or start editing
directly in the table below.
Invalid Data: {error}
🔒 Privacy: Your data stays in your
browser. We don't store or upload anything - just help you
open, edit, and export your files locally.
{data.length} rows, {columns.length} columns
{data.length} rows, {columns.length} columns
Auto-detected column types based on your data:
Smart Detection: JSON →
JSON/LONGTEXT, Numbers → INT/DECIMAL, Dates →
DATE/DATETIME, Text → VARCHAR(optimized size),
URLs/Emails → Appropriate sizing
What this schema does:
🔒 Data Safety Policy:
📥 How to use:
📝 Input Methods: 🎯 Table Management: 🚀 Data Operations: 📤 Export Options:
Start Building Your Table
{availableTables.length > 1 ? "Multi-Table Database" : "Table Editor"}
{availableTables.length === 1 && (
{filteredAndSortedData.map((row) => (
0
? "sticky left-0 z-20 bg-blue-50 dark:!bg-blue-900"
: ""
}`}
style={{ width: '40px', maxWidth: '40px', minWidth: '40px' }}
>
0
}
onChange={(e) => {
if (e.target.checked) {
setSelectedRows(
new Set(
filteredAndSortedData.map((row) => row.id),
),
);
} else {
setSelectedRows(new Set());
}
}}
className="rounded border-gray-300 dark:border-gray-600"
/>
{columns.map((column, index) => {
const isFrozen = index < frozenColumns;
const leftOffset = isFrozen
? 40 + columns.slice(0, index).reduce((acc, col) => acc + getColumnWidth(col.id), 0)
: 0;
return (
);
})}
{/* System Column - Add Column */}
))}
{/* System Row - Add Row */}
0
? "sticky left-0 z-10 bg-blue-50 dark:!bg-blue-900"
: ""
}`}
style={{ width: '40px', maxWidth: '40px', minWidth: '40px' }}
>
{
const newSelected = new Set(selectedRows);
if (e.target.checked) {
newSelected.add(row.id);
} else {
newSelected.delete(row.id);
}
setSelectedRows(newSelected);
}}
className="rounded border-gray-300 dark:border-gray-600"
/>
{columns.map((column, index) => {
const isFrozen = index < frozenColumns;
const leftOffset = isFrozen
? 40 + columns.slice(0, index).reduce((acc, col) => acc + getColumnWidth(col.id), 0)
: 0;
return (
{editingCell?.rowId === row.id &&
editingCell?.columnId === column.id ? (
(() => {
const cellValue = String(row[column.id] || "");
const isLongValue =
cellValue.length > 100 ||
cellValue.includes("\n");
if (isLongValue) {
return (
);
})}
{/* Empty cell for system column alignment */}
{/* Empty cell to align with system column header */}
SQL Export Settings
🔍 Intelligent Schema Analysis
📋 SQL Schema Information
mysql -u user -p database < file.sql
💡 Usage Tips
{usageTipsExpanded ?
This action cannot be undone
Are you sure you want to clear all table data?
Warning: This action cannot be undone. All your work will be lost.
Invalid or unparseable data
{error &&{error}
}Row {modal.rowIndex} • Column: {modal.columnName} • Format:{" "} {modal.format.type.replace("_", " ")}
{/* Format info */}This will clear all current data
{newMethod === "create_empty" || newMethod === "create_sample" ? ( <> Using {getMethodName(newMethod)} will clear all current data. > ) : ( <> Switching from {getMethodName(currentMethod)}{" "} to {getMethodName(newMethod)} will clear all current data. > )}
Tip: Consider exporting your current data before switching methods to avoid losing your work.