import {Eye, RefreshCcw, Search, X} from 'lucide-react'; import {FormEvent, useEffect, useMemo, useState} from 'react'; import { getAdminDatabaseTableRows, getAdminDatabaseTables, } from '../api/adminApiClient'; import type { AdminDatabaseTableRowPayload, AdminDatabaseTableRowsResponse, } from '../api/adminApiTypes'; import {handlePageError} from './pageUtils'; interface AdminDatabaseTablesPageProps { token: string; onUnauthorized: (message?: string) => void; } export function AdminDatabaseTablesPage({ token, onUnauthorized, }: AdminDatabaseTablesPageProps) { const [tables, setTables] = useState([]); const [tableName, setTableName] = useState(() => readHashTableName()); const [search, setSearch] = useState(''); const [filters, setFilters] = useState(''); const [limit, setLimit] = useState('100'); const [result, setResult] = useState(null); const [detailRow, setDetailRow] = useState(null); const [errorMessage, setErrorMessage] = useState(''); const [isLoadingTables, setIsLoadingTables] = useState(false); const [isLoadingRows, setIsLoadingRows] = useState(false); useEffect(() => { void loadTables(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]); useEffect(() => { const nextTableName = readHashTableName(); if (nextTableName) { setTableName(nextTableName); } const handleHashChange = () => { const tableFromHash = readHashTableName(); if (tableFromHash) { setTableName(tableFromHash); void refreshRows(tableFromHash); } }; window.addEventListener('hashchange', handleHashChange); return () => window.removeEventListener('hashchange', handleHashChange); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (tables.length && !tableName) { setTableName(tables[0] ?? ''); } }, [tableName, tables]); useEffect(() => { if (tableName) { void refreshRows(tableName); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [tableName]); const visibleColumns = useMemo(() => { const columns = result?.columns ?? []; if (columns.length) { return columns; } const firstRow = result?.rows[0]; return firstRow ? Object.keys(firstRow.cells) : []; }, [result]); async function loadTables() { setIsLoadingTables(true); setErrorMessage(''); try { const response = await getAdminDatabaseTables(token); setTables(response.tables); if (response.fetchErrors.length) { setErrorMessage(response.fetchErrors.join(';')); } } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsLoadingTables(false); } } async function refreshRows(nextTableName = tableName) { const normalizedTableName = nextTableName.trim(); if (!normalizedTableName) { return; } setIsLoadingRows(true); setErrorMessage(''); try { const response = await getAdminDatabaseTableRows(token, normalizedTableName, { search, filters, limit: parseLimit(limit), }); setResult(response); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsLoadingRows(false); } } function handleSearch(event: FormEvent) { event.preventDefault(); void refreshRows(); } function handleTableChange(nextTableName: string) { setTableName(nextTableName); const nextHash = `#tables?table=${encodeURIComponent(nextTableName)}`; if (window.location.hash !== nextHash) { window.location.hash = nextHash; } } return (

表查询

SpacetimeDB 行数据

{errorMessage ? (
{errorMessage}
) : null}

{result?.tableName || tableName || '数据行'}

{result?.totalReturned ?? 0} 条
{visibleColumns.map((column) => ( ))} {result?.rows.length ? ( result.rows.map((row, rowIndex) => ( setDetailRow(row)} > {visibleColumns.map((column) => ( ))} )) ) : ( )}
{column}详情
{formatCellValue(row.cells[column])}
暂无数据
{detailRow ? (

行详情

              {JSON.stringify(detailRow.cells, null, 2)}
            
) : null}
); } function readHashTableName() { const hash = window.location.hash; const queryIndex = hash.indexOf('?'); if (queryIndex < 0) { return ''; } return new URLSearchParams(hash.slice(queryIndex + 1)).get('table')?.trim() ?? ''; } function parseLimit(value: string) { const parsed = Number.parseInt(value.trim(), 10); return Number.isFinite(parsed) ? parsed : 100; } function buildRowKey(row: AdminDatabaseTableRowPayload, rowIndex: number) { const firstValue = Object.values(row.cells)[0]; return `${rowIndex}-${String(firstValue ?? '')}`; } function formatCellValue(value: unknown) { if (value === null || typeof value === 'undefined' || value === '') { return '-'; } if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { return String(value); } return
{JSON.stringify(value, null, 2)}
; }