/** * TableSelector - Header component for switching between sand tables * * Displays the current table and provides a dropdown to switch between * discovered tables or add new ones manually. */ import { useState } from 'react' import { useTable, type Table } from '@/contexts/TableContext' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Badge } from '@/components/ui/badge' import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { toast } from 'sonner' import { Layers, Plus, Check, Wifi, WifiOff, Pencil, Trash2, } from 'lucide-react' export function TableSelector() { const { tables, activeTable, setActiveTable, addTable, removeTable, updateTableName, } = useTable() const [isOpen, setIsOpen] = useState(false) const [showAddDialog, setShowAddDialog] = useState(false) const [showRenameDialog, setShowRenameDialog] = useState(false) const [newTableUrl, setNewTableUrl] = useState('') const [newTableName, setNewTableName] = useState('') const [renameTable, setRenameTable] = useState(null) const [renameValue, setRenameValue] = useState('') const [isAdding, setIsAdding] = useState(false) const handleSelectTable = (table: Table) => { if (table.id !== activeTable?.id) { setActiveTable(table) toast.success(`Switched to ${table.name}`) } setIsOpen(false) } const handleAddTable = async () => { if (!newTableUrl.trim()) { toast.error('Please enter a URL') return } setIsAdding(true) try { // Ensure URL has protocol let url = newTableUrl.trim() if (!url.startsWith('http://') && !url.startsWith('https://')) { url = `http://${url}` } const table = await addTable(url, newTableName.trim() || undefined) if (table) { toast.success(`Added ${table.name}`) setShowAddDialog(false) setNewTableUrl('') setNewTableName('') } else { toast.error('Failed to add table. Check the URL and try again.') } } finally { setIsAdding(false) } } const handleRename = async () => { if (!renameTable || !renameValue.trim()) return await updateTableName(renameTable.id, renameValue.trim()) toast.success('Table renamed') setShowRenameDialog(false) setRenameTable(null) setRenameValue('') } const handleRemove = (table: Table) => { if (table.isCurrent) { toast.error("Can't remove the current table") return } removeTable(table.id) toast.success(`Removed ${table.name}`) } const openRenameDialog = (table: Table) => { setRenameTable(table) setRenameValue(table.name) setShowRenameDialog(true) } // Always show if there are tables or discovering // This allows users to manually add tables even with just one return ( <>
{/* Header */}
Sand Tables
{/* Table list */}
{tables.map(table => (
handleSelectTable(table)} > {/* Status indicator */} {table.isOnline ? ( ) : ( )} {/* Name and info */}
{table.name} {table.isCurrent && ( This )}
{table.host || new URL(table.url).hostname}
{/* Actions - always visible on mobile, hover on desktop */}
{!table.isCurrent && ( )}
{/* Selected indicator - far right */} {activeTable?.id === table.id && ( )}
))}
{/* Add table button */}
{/* Add Table Dialog */} Add Table Manually
setNewTableUrl(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleAddTable()} />

Enter the IP address and port of the table's backend

setNewTableName(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleAddTable()} />
{/* Rename Dialog */} Rename Table
setRenameValue(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleRename()} autoFocus />
) }