import { IconTable, IconEye, IconSearch, IconDatabaseOff, IconX, } from "@tabler/icons-react"; /** * Left sidebar for the database admin: a mode toggle (Table Editor / SQL * Editor), a debounced search box, and a scrollable list of tables and views * with row counts. */ import { useEffect, useMemo, useRef, useState } from "react"; import type { DbAdminTableSummary } from "../../db-admin/types.js"; import { cn } from "../utils.js"; export interface TableBrowserProps { tables: DbAdminTableSummary[]; selected: string | null; onSelect: (table: string) => void; mode: "table" | "sql"; onModeChange: (mode: "table" | "sql") => void; } function formatCount(count: number | null): string { if (count === null) return ""; if (count < 1000) return String(count); if (count < 1_000_000) return `${(count / 1000).toFixed(count < 10_000 ? 1 : 0)}k`; return `${(count / 1_000_000).toFixed(1)}M`; } function useDebounced(value: string, delay = 150): string { const [debounced, setDebounced] = useState(value); useEffect(() => { const id = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(id); }, [value, delay]); return debounced; } export function TableBrowser({ tables, selected, onSelect, mode, onModeChange, }: TableBrowserProps) { const [search, setSearch] = useState(""); const debouncedSearch = useDebounced(search); const inputRef = useRef(null); const filtered = useMemo(() => { const q = debouncedSearch.trim().toLowerCase(); const list = q ? tables.filter((t) => t.name.toLowerCase().includes(q)) : tables; return [...list].sort((a, b) => a.name.localeCompare(b.name)); }, [tables, debouncedSearch]); return (
{/* Mode toggle */}
onModeChange("table")} > Table Editor onModeChange("sql")} > SQL Editor
{/* Search */}
setSearch(e.target.value)} placeholder="Search tables…" spellCheck={false} className={cn( "h-9 w-full rounded-md border border-input bg-background pl-8 pr-8 text-sm", "ring-offset-background placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", )} /> {search && ( )}
{/* Table list */}
{filtered.length === 0 ? ( ) : (
    {filtered.map((t) => { const isSelected = t.name === selected; const Icon = t.type === "view" ? IconEye : IconTable; return (
  • ); })}
)}
); } function ModeButton({ active, onClick, children, }: { active: boolean; onClick: () => void; children: React.ReactNode; }) { return ( ); } function EmptyState({ hasSearch }: { hasSearch: boolean }) { return (

{hasSearch ? "No matches" : "No tables yet"}

{hasSearch ? "Try a different search term." : "Tables will appear here once created."}

); }