import React, { useState, useEffect, useRef } from 'react'; // ── Constants ────────────────────────────────────────────────────────────── const COL_TYPES = [ { id: 'text', label: 'Text', badge: 'T' }, { id: 'textarea', label: 'Long Text', badge: '¶' }, { id: 'number', label: 'Number', badge: '#' }, { id: 'email', label: 'Email', badge: '@' }, { id: 'url', label: 'URL', badge: '🔗' }, { id: 'date', label: 'Date', badge: '📅' }, { id: 'datetime', label: 'Date+Time', badge: '⏱' }, { id: 'boolean', label: 'Boolean', badge: '✓' }, ]; const api = (path, opts = {}) => fetch(`${window.wpbotAutomator.apiUrl}${path}`, { headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.wpbotAutomator.nonce, ...( opts.headers || {} ), }, ...opts, }).then(r => r.json()); // ── Root app ──────────────────────────────────────────────────────────────── const TablesApp = () => { const [view, setView] = useState('list'); // 'list' | 'records' const [tables, setTables] = useState([]); const [loading, setLoading] = useState(true); const [activeTable, setActive] = useState(null); const [showCreate, setShowCreate] = useState(false); const loadTables = async () => { setLoading(true); try { const data = await api('/tables'); setTables(Array.isArray(data) ? data : []); } finally { setLoading(false); } }; useEffect(() => { loadTables(); }, []); const openRecords = (table) => { setActive(table); setView('records'); }; const backToList = () => { setView('list'); setActive(null); loadTables(); }; const handleDelete = async (table) => { if (!window.confirm(`Delete table "${table.name}" and all its data? This cannot be undone.`)) return; await api(`/tables/${table.id}`, { method: 'DELETE' }); loadTables(); }; if (view === 'records' && activeTable) { return ; } return (
{/* Header */}

Tables

Create and manage database tables. Use [wpbot_table id="X"] to display on any page.

{/* Tables grid */} {loading ? (
Loading tables...
) : tables.length === 0 ? (

No tables yet

Create your first table to start managing data.

) : (
{tables.map(t => ( openRecords(t)} onDelete={() => handleDelete(t)} /> ))}
)}
{showCreate && ( setShowCreate(false)} onCreated={() => { setShowCreate(false); loadTables(); }} /> )}
); }; // ── Table card ────────────────────────────────────────────────────────────── const TableCard = ({ table, onOpen, onDelete }) => (

{table.name}

{table.columns?.length || 0} columns · {table.record_count ?? 0} records

{/* Column pills */}
{(table.columns || []).slice(0, 5).map(col => ( {col.name} ))} {(table.columns?.length || 0) > 5 && ( +{table.columns.length - 5} more )}
{/* Shortcode */}
{`[wpbot_table id="${table.id}"]`}
); // ── Create table modal ─────────────────────────────────────────────────────── const CreateTableModal = ({ onClose, onCreated }) => { const [name, setName] = useState(''); const [columns, setCols] = useState([{ id: 1, name: '', type: 'text', required: false }]); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const nextId = useRef(2); const addCol = () => { setCols(c => [...c, { id: nextId.current++, name: '', type: 'text', required: false }]); }; const removeCol = (id) => setCols(c => c.filter(col => col.id !== id)); const updateCol = (id, patch) => setCols(c => c.map(col => col.id === id ? { ...col, ...patch } : col)); const handleSubmit = async () => { if (!name.trim()) { setError('Table name is required.'); return; } const validCols = columns.filter(c => c.name.trim()); if (!validCols.length) { setError('At least one column with a name is required.'); return; } setError(''); setSaving(true); try { const res = await api('/tables', { method: 'POST', body: JSON.stringify({ name: name.trim(), columns: validCols }), }); if (res.success) { onCreated(); } else { setError(res.message || 'Failed to create table.'); } } finally { setSaving(false); } }; return (
{/* Header */}

Create New Table

{/* Body */}
{/* Table name */}
setName(e.target.value)} placeholder="e.g. Customers, Products, Contacts" className="w-full border border-border rounded-lg px-3 py-2 text-sm bg-background focus:outline-none focus:ring-2 focus:ring-primary/30" />
{/* Columns */}
{columns.map(col => (
updateCol(col.id, { name: e.target.value })} placeholder="Column name" className="flex-1 border border-border rounded-lg px-3 py-2 text-sm bg-background focus:outline-none" />
))}
{error &&

{error}

}
{/* Footer */}
); }; // ── Records manager ────────────────────────────────────────────────────────── const RecordsManager = ({ table, onBack }) => { const [records, setRecords] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [loading, setLoading] = useState(true); const [filterCol, setFilterCol] = useState(''); const [filterVal, setFilterVal] = useState(''); const [appliedFilter, setApplied] = useState({ col: '', val: '' }); const [showAddRow, setShowAdd] = useState(false); const [editRow, setEditRow] = useState(null); const [showImport, setShowImport] = useState(false); const perPage = 50; const loadRecords = async (pg = 1, fCol = appliedFilter.col, fVal = appliedFilter.val) => { setLoading(true); try { const params = new URLSearchParams({ page: pg, per_page: perPage }); if (fCol && fVal) { params.set('filter_col', fCol); params.set('filter_val', fVal); } const data = await api(`/tables/${table.id}/records?${params}`); setRecords(data.records || []); setTotal(data.total || 0); setTotalPages(data.total_pages || 1); setPage(pg); } finally { setLoading(false); } }; useEffect(() => { loadRecords(1); }, []); const applyFilter = () => { setApplied({ col: filterCol, val: filterVal }); loadRecords(1, filterCol, filterVal); }; const clearFilter = () => { setFilterCol(''); setFilterVal(''); setApplied({ col: '', val: '' }); loadRecords(1, '', ''); }; const handleDelete = async (rowId) => { if (!window.confirm('Delete this record?')) return; await api(`/tables/${table.id}/records/${rowId}`, { method: 'DELETE' }); loadRecords(page); }; const handleExport = async () => { const data = await api(`/tables/${table.id}/export`); if (!data.csv) return; const blob = new Blob([data.csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = data.filename || 'export.csv'; a.click(); URL.revokeObjectURL(url); }; return (
{/* Header */}

{table.name}

{total} records · {`[wpbot_table id="${table.id}"]`}

{/* Filters */}
Filter by: setFilterVal(e.target.value)} onKeyDown={e => e.key === 'Enter' && applyFilter()} placeholder="Filter value…" className="border border-border rounded-lg px-3 py-1.5 text-sm bg-background focus:outline-none w-48" /> {(appliedFilter.col || appliedFilter.val) && ( )} {total} records
{/* Table */}
{table.columns?.map(col => ( ))} {loading ? ( ) : records.length === 0 ? ( ) : records.map(rec => ( {table.columns?.map(col => ( ))} ))}
# {col.name} {col.type} Actions
Loading…
No records found.
{rec.id} {col.type === 'boolean' ? (rec[col.slug] == 1 ? '✓' : '✗') : rec[col.slug] ?? ''}
{/* Pagination */} {totalPages > 1 && (
Page {page} of {totalPages}
)}
{/* Add / Edit record modal */} {(showAddRow || editRow) && ( { setShowAdd(false); setEditRow(null); }} onSaved={() => { setShowAdd(false); setEditRow(null); loadRecords(page); }} /> )} {/* CSV import modal */} {showImport && ( setShowImport(false)} onImported={() => { setShowImport(false); loadRecords(1); }} /> )}
); }; // ── Record add / edit modal ────────────────────────────────────────────────── const RecordModal = ({ table, record, onClose, onSaved }) => { const isEdit = !!record; const initial = {}; table.columns?.forEach(col => { initial[col.slug] = isEdit ? (record[col.slug] ?? '') : (col.type === 'boolean' ? false : ''); }); const [form, setForm] = useState(initial); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const set = (slug, val) => setForm(f => ({ ...f, [slug]: val })); const handleSave = async () => { setSaving(true); setError(''); try { const url = isEdit ? `/tables/${table.id}/records/${record.id}` : `/tables/${table.id}/records`; const method = isEdit ? 'PUT' : 'POST'; const res = await api(url, { method, body: JSON.stringify({ data: form }) }); if (res.success) { onSaved(); } else { setError(res.message || 'Failed to save record.'); } } finally { setSaving(false); } }; return (

{isEdit ? 'Edit Record' : 'Add Record'}

{table.columns?.map(col => (
{col.type === 'boolean' ? ( ) : col.type === 'textarea' ? (