This commit is contained in:
Lakee Sivaraya
2026-01-16 12:18:38 -08:00
parent ea72ab5aa9
commit 42aa794713
27 changed files with 395 additions and 382 deletions

View File

@@ -3,13 +3,13 @@
import { Trash2, X } from 'lucide-react'
import { Button } from '@/components/emcn'
interface TableActionBarProps {
interface ActionBarProps {
selectedCount: number
onDelete: () => void
onClearSelection: () => void
}
export function TableActionBar({ selectedCount, onDelete, onClearSelection }: TableActionBarProps) {
export function ActionBar({ selectedCount, onDelete, onClearSelection }: ActionBarProps) {
return (
<div className='flex h-[36px] shrink-0 items-center justify-between border-[var(--border)] border-b bg-[var(--surface-4)] px-[16px]'>
<div className='flex items-center gap-[12px]'>

View File

@@ -3,11 +3,11 @@ import { Button, TableCell, TableRow } from '@/components/emcn'
import { Skeleton } from '@/components/ui/skeleton'
import type { ColumnDefinition } from '@/lib/table'
interface TableLoadingRowsProps {
interface LoadingRowsProps {
columns: ColumnDefinition[]
}
export function TableLoadingRows({ columns }: TableLoadingRowsProps) {
export function LoadingRows({ columns }: LoadingRowsProps) {
return (
<>
{Array.from({ length: 25 }).map((_, rowIndex) => (
@@ -43,13 +43,13 @@ export function TableLoadingRows({ columns }: TableLoadingRowsProps) {
)
}
interface TableEmptyRowsProps {
interface EmptyRowsProps {
columnCount: number
hasFilter: boolean
onAddRow: () => void
}
export function TableEmptyRows({ columnCount, hasFilter, onAddRow }: TableEmptyRowsProps) {
export function EmptyRows({ columnCount, hasFilter, onAddRow }: EmptyRowsProps) {
return (
<TableRow>
<TableCell colSpan={columnCount + 1} className='h-[160px]'>

View File

@@ -1,14 +1,14 @@
import type { ColumnDefinition } from '@/lib/table'
import { STRING_TRUNCATE_LENGTH } from '../constants'
import type { CellViewerData } from '../types'
import { STRING_TRUNCATE_LENGTH } from '../lib/constants'
import type { CellViewerData } from '../lib/types'
interface TableCellRendererProps {
interface CellRendererProps {
value: unknown
column: ColumnDefinition
onCellClick: (columnName: string, value: unknown, type: CellViewerData['type']) => void
}
export function TableCellRenderer({ value, column, onCellClick }: TableCellRendererProps) {
export function CellRenderer({ value, column, onCellClick }: CellRendererProps) {
const isNull = value === null || value === undefined
if (isNull) {

View File

@@ -1,6 +1,6 @@
import { Copy, X } from 'lucide-react'
import { Badge, Button, Modal, ModalBody, ModalContent } from '@/components/emcn'
import type { CellViewerData } from '../types'
import type { CellViewerData } from '../lib/types'
interface CellViewerModalProps {
cellViewer: CellViewerData | null

View File

@@ -6,16 +6,16 @@ import {
PopoverDivider,
PopoverItem,
} from '@/components/emcn'
import type { ContextMenuState } from '../types'
import type { ContextMenuState } from '../lib/types'
interface RowContextMenuProps {
interface ContextMenuProps {
contextMenu: ContextMenuState
onClose: () => void
onEdit: () => void
onDelete: () => void
}
export function RowContextMenu({ contextMenu, onClose, onEdit, onDelete }: RowContextMenuProps) {
export function ContextMenu({ contextMenu, onClose, onEdit, onDelete }: ContextMenuProps) {
return (
<Popover
open={contextMenu.isOpen}

View File

@@ -2,7 +2,7 @@ import { Info, RefreshCw } from 'lucide-react'
import { Badge, Button, Tooltip } from '@/components/emcn'
import { Skeleton } from '@/components/ui/skeleton'
interface TableHeaderBarProps {
interface HeaderBarProps {
tableName: string
totalCount: number
isLoading: boolean
@@ -11,14 +11,14 @@ interface TableHeaderBarProps {
onRefresh: () => void
}
export function TableHeaderBar({
export function HeaderBar({
tableName,
totalCount,
isLoading,
onNavigateBack,
onShowSchema,
onRefresh,
}: TableHeaderBarProps) {
}: HeaderBarProps) {
return (
<div className='flex h-[48px] shrink-0 items-center justify-between border-[var(--border)] border-b px-[16px]'>
<div className='flex items-center gap-[8px]'>

View File

@@ -1,3 +1,11 @@
export * from './table-action-bar'
export * from './table-query-builder'
export * from './table-row-modal'
export * from './action-bar'
export * from './body-states'
export * from './cell-renderer'
export * from './cell-viewer-modal'
export * from './context-menu'
export * from './header-bar'
export * from './pagination'
export * from './query-builder'
export * from './row-modal'
export * from './schema-modal'
export * from './table-viewer'

View File

@@ -1,6 +1,6 @@
import { Button } from '@/components/emcn'
interface TablePaginationProps {
interface PaginationProps {
currentPage: number
totalPages: number
totalCount: number
@@ -8,13 +8,13 @@ interface TablePaginationProps {
onNextPage: () => void
}
export function TablePagination({
export function Pagination({
currentPage,
totalPages,
totalCount,
onPreviousPage,
onNextPage,
}: TablePaginationProps) {
}: PaginationProps) {
if (totalPages <= 1) return null
return (

View File

@@ -0,0 +1,89 @@
'use client'
import { X } from 'lucide-react'
import { Button, Combobox, Input } from '@/components/emcn'
import type { FilterRule } from '@/lib/table/filters/constants'
interface FilterRowProps {
rule: FilterRule
index: number
columnOptions: Array<{ value: string; label: string }>
comparisonOptions: Array<{ value: string; label: string }>
logicalOptions: Array<{ value: string; label: string }>
onUpdate: (id: string, field: keyof FilterRule, value: string) => void
onRemove: (id: string) => void
onApply: () => void
}
export function FilterRow({
rule,
index,
columnOptions,
comparisonOptions,
logicalOptions,
onUpdate,
onRemove,
onApply,
}: FilterRowProps) {
return (
<div className='flex items-center gap-[8px]'>
<Button
variant='ghost'
size='sm'
onClick={() => onRemove(rule.id)}
className='h-[28px] w-[28px] shrink-0 p-0 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]'
>
<X className='h-[12px] w-[12px]' />
</Button>
<div className='w-[80px] shrink-0'>
{index === 0 ? (
<Combobox
size='sm'
options={[{ value: 'where', label: 'where' }]}
value='where'
disabled
/>
) : (
<Combobox
size='sm'
options={logicalOptions}
value={rule.logicalOperator}
onChange={(value) => onUpdate(rule.id, 'logicalOperator', value as 'and' | 'or')}
/>
)}
</div>
<div className='w-[140px] shrink-0'>
<Combobox
size='sm'
options={columnOptions}
value={rule.column}
onChange={(value) => onUpdate(rule.id, 'column', value)}
placeholder='Column'
/>
</div>
<div className='w-[130px] shrink-0'>
<Combobox
size='sm'
options={comparisonOptions}
value={rule.operator}
onChange={(value) => onUpdate(rule.id, 'operator', value)}
/>
</div>
<Input
className='h-[28px] min-w-[200px] flex-1 text-[12px]'
value={rule.value}
onChange={(e) => onUpdate(rule.id, 'value', e.target.value)}
placeholder='Value'
onKeyDown={(e) => {
if (e.key === 'Enter') {
onApply()
}
}}
/>
</div>
)
}

View File

@@ -0,0 +1,137 @@
'use client'
import { useCallback, useMemo, useState } from 'react'
import { ArrowUpAZ, Loader2, Plus } from 'lucide-react'
import { nanoid } from 'nanoid'
import { Button } from '@/components/emcn'
import type { FilterRule, SortRule } from '@/lib/table/filters/constants'
import { useFilterBuilder } from '@/lib/table/filters/use-builder'
import { filterRulesToFilter, sortRuleToSort } from '@/lib/table/filters/utils'
import type { ColumnDefinition } from '@/lib/table/types'
import type { QueryOptions } from '../../lib/types'
import { FilterRow } from './filter-row'
import { SortRow } from './sort-row'
type Column = Pick<ColumnDefinition, 'name' | 'type'>
interface QueryBuilderProps {
columns: Column[]
onApply: (options: QueryOptions) => void
onAddRow: () => void
isLoading?: boolean
}
export function QueryBuilder({ columns, onApply, onAddRow, isLoading = false }: QueryBuilderProps) {
const [rules, setRules] = useState<FilterRule[]>([])
const [sortRule, setSortRule] = useState<SortRule | null>(null)
const columnOptions = useMemo(
() => columns.map((col) => ({ value: col.name, label: col.name })),
[columns]
)
const {
comparisonOptions,
logicalOptions,
sortDirectionOptions,
addRule: handleAddRule,
removeRule: handleRemoveRule,
updateRule: handleUpdateRule,
} = useFilterBuilder({
columns: columnOptions,
rules,
setRules,
})
const handleAddSort = useCallback(() => {
setSortRule({
id: nanoid(),
column: columns[0]?.name || '',
direction: 'asc',
})
}, [columns])
const handleRemoveSort = useCallback(() => {
setSortRule(null)
}, [])
const handleApply = useCallback(() => {
const filter = filterRulesToFilter(rules)
const sort = sortRuleToSort(sortRule)
onApply({ filter, sort })
}, [rules, sortRule, onApply])
const handleClear = useCallback(() => {
setRules([])
setSortRule(null)
onApply({
filter: null,
sort: null,
})
}, [onApply])
const hasChanges = rules.length > 0 || sortRule !== null
return (
<div className='flex flex-col gap-[8px]'>
{rules.map((rule, index) => (
<FilterRow
key={rule.id}
rule={rule}
index={index}
columnOptions={columnOptions}
comparisonOptions={comparisonOptions}
logicalOptions={logicalOptions}
onUpdate={handleUpdateRule}
onRemove={handleRemoveRule}
onApply={handleApply}
/>
))}
{sortRule && (
<SortRow
sortRule={sortRule}
columnOptions={columnOptions}
sortDirectionOptions={sortDirectionOptions}
onChange={setSortRule}
onRemove={handleRemoveSort}
/>
)}
<div className='flex items-center gap-[8px]'>
<Button variant='default' size='sm' onClick={onAddRow}>
<Plus className='mr-[4px] h-[12px] w-[12px]' />
Add row
</Button>
<Button variant='default' size='sm' onClick={handleAddRule}>
<Plus className='mr-[4px] h-[12px] w-[12px]' />
Add filter
</Button>
{!sortRule && (
<Button variant='default' size='sm' onClick={handleAddSort}>
<ArrowUpAZ className='mr-[4px] h-[12px] w-[12px]' />
Add sort
</Button>
)}
{hasChanges && (
<>
<Button variant='default' size='sm' onClick={handleApply} disabled={isLoading}>
{isLoading && <Loader2 className='mr-[4px] h-[12px] w-[12px] animate-spin' />}
{isLoading ? 'Applying...' : 'Apply'}
</Button>
<button
onClick={handleClear}
className='text-[12px] text-[var(--text-tertiary)] transition-colors hover:text-[var(--text-primary)]'
>
Clear all
</button>
</>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,65 @@
'use client'
import { ArrowDownAZ, ArrowUpAZ, X } from 'lucide-react'
import { Button, Combobox } from '@/components/emcn'
import type { SortRule } from '@/lib/table/filters/constants'
interface SortRowProps {
sortRule: SortRule
columnOptions: Array<{ value: string; label: string }>
sortDirectionOptions: Array<{ value: string; label: string }>
onChange: (rule: SortRule | null) => void
onRemove: () => void
}
export function SortRow({
sortRule,
columnOptions,
sortDirectionOptions,
onChange,
onRemove,
}: SortRowProps) {
return (
<div className='flex items-center gap-[8px]'>
<Button
variant='ghost'
size='sm'
onClick={onRemove}
className='h-[28px] w-[28px] shrink-0 p-0 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]'
>
<X className='h-[12px] w-[12px]' />
</Button>
<div className='w-[80px] shrink-0'>
<Combobox size='sm' options={[{ value: 'order', label: 'order' }]} value='order' disabled />
</div>
<div className='w-[140px] shrink-0'>
<Combobox
size='sm'
options={columnOptions}
value={sortRule.column}
onChange={(value) => onChange({ ...sortRule, column: value })}
placeholder='Column'
/>
</div>
<div className='w-[130px] shrink-0'>
<Combobox
size='sm'
options={sortDirectionOptions}
value={sortRule.direction}
onChange={(value) => onChange({ ...sortRule, direction: value as 'asc' | 'desc' })}
/>
</div>
<div className='flex items-center text-[12px] text-[var(--text-tertiary)]'>
{sortRule.direction === 'asc' ? (
<ArrowUpAZ className='h-[14px] w-[14px]' />
) : (
<ArrowDownAZ className='h-[14px] w-[14px]' />
)}
</div>
</div>
)
}

View File

@@ -18,9 +18,9 @@ import {
import { Input } from '@/components/ui/input'
import type { ColumnDefinition, TableInfo, TableRow } from '@/lib/table'
const logger = createLogger('TableRowModal')
const logger = createLogger('RowModal')
export interface TableRowModalProps {
export interface RowModalProps {
mode: 'add' | 'edit' | 'delete'
isOpen: boolean
onClose: () => void
@@ -92,15 +92,7 @@ function formatValueForInput(value: unknown, type: string): string {
return String(value)
}
export function TableRowModal({
mode,
isOpen,
onClose,
table,
row,
rowIds,
onSuccess,
}: TableRowModalProps) {
export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess }: RowModalProps) {
const params = useParams()
const workspaceId = params.workspaceId as string

View File

@@ -13,15 +13,15 @@ import {
TableRow,
} from '@/components/emcn'
import type { ColumnDefinition } from '@/lib/table'
import { getTypeBadgeVariant } from '../utils'
import { getTypeBadgeVariant } from '../lib/utils'
interface SchemaViewerModalProps {
interface SchemaModalProps {
isOpen: boolean
onClose: () => void
columns: ColumnDefinition[]
}
export function SchemaViewerModal({ isOpen, onClose, columns }: SchemaViewerModalProps) {
export function SchemaModal({ isOpen, onClose, columns }: SchemaModalProps) {
return (
<Modal open={isOpen} onOpenChange={onClose}>
<ModalContent className='w-[500px] duration-100'>

View File

@@ -1,288 +0,0 @@
'use client'
import { useCallback, useMemo, useState } from 'react'
import { ArrowDownAZ, ArrowUpAZ, Loader2, Plus, X } from 'lucide-react'
import { nanoid } from 'nanoid'
import { Button, Combobox, Input } from '@/components/emcn'
import type { FilterRule, SortRule } from '@/lib/table/filters/constants'
import { useFilterBuilder } from '@/lib/table/filters/use-builder'
import { filterRulesToFilter, sortRuleToSort } from '@/lib/table/filters/utils'
import type { ColumnDefinition, Filter, Sort } from '@/lib/table/types'
export interface BuilderQueryResult {
filter: Filter | null
sort: Sort | null
}
type Column = Pick<ColumnDefinition, 'name' | 'type'>
interface TableQueryBuilderProps {
columns: Column[]
onApply: (options: BuilderQueryResult) => void
onAddRow: () => void
isLoading?: boolean
}
export function TableQueryBuilder({
columns,
onApply,
onAddRow,
isLoading = false,
}: TableQueryBuilderProps) {
const [rules, setRules] = useState<FilterRule[]>([])
const [sortRule, setSortRule] = useState<SortRule | null>(null)
const columnOptions = useMemo(
() => columns.map((col) => ({ value: col.name, label: col.name })),
[columns]
)
const {
comparisonOptions,
logicalOptions,
sortDirectionOptions,
addRule: handleAddRule,
removeRule: handleRemoveRule,
updateRule: handleUpdateRule,
} = useFilterBuilder({
columns: columnOptions,
rules,
setRules,
})
const handleAddSort = useCallback(() => {
setSortRule({
id: nanoid(),
column: columns[0]?.name || '',
direction: 'asc',
})
}, [columns])
const handleRemoveSort = useCallback(() => {
setSortRule(null)
}, [])
const handleApply = useCallback(() => {
const filter = filterRulesToFilter(rules)
const sort = sortRuleToSort(sortRule)
onApply({ filter, sort })
}, [rules, sortRule, onApply])
const handleClear = useCallback(() => {
setRules([])
setSortRule(null)
onApply({
filter: null,
sort: null,
})
}, [onApply])
const hasChanges = rules.length > 0 || sortRule !== null
return (
<div className='flex flex-col gap-[8px]'>
{rules.map((rule, index) => (
<FilterRuleRow
key={rule.id}
rule={rule}
index={index}
columnOptions={columnOptions}
comparisonOptions={comparisonOptions}
logicalOptions={logicalOptions}
onUpdate={handleUpdateRule}
onRemove={handleRemoveRule}
onApply={handleApply}
/>
))}
{sortRule && (
<SortRuleRow
sortRule={sortRule}
columnOptions={columnOptions}
sortDirectionOptions={sortDirectionOptions}
onChange={setSortRule}
onRemove={handleRemoveSort}
/>
)}
<div className='flex items-center gap-[8px]'>
<Button variant='default' size='sm' onClick={onAddRow}>
<Plus className='mr-[4px] h-[12px] w-[12px]' />
Add row
</Button>
<Button variant='default' size='sm' onClick={handleAddRule}>
<Plus className='mr-[4px] h-[12px] w-[12px]' />
Add filter
</Button>
{!sortRule && (
<Button variant='default' size='sm' onClick={handleAddSort}>
<ArrowUpAZ className='mr-[4px] h-[12px] w-[12px]' />
Add sort
</Button>
)}
{hasChanges && (
<>
<Button variant='default' size='sm' onClick={handleApply} disabled={isLoading}>
{isLoading && <Loader2 className='mr-[4px] h-[12px] w-[12px] animate-spin' />}
{isLoading ? 'Applying...' : 'Apply'}
</Button>
<button
onClick={handleClear}
className='text-[12px] text-[var(--text-tertiary)] transition-colors hover:text-[var(--text-primary)]'
>
Clear all
</button>
</>
)}
</div>
</div>
)
}
interface FilterRuleRowProps {
rule: FilterRule
index: number
columnOptions: Array<{ value: string; label: string }>
comparisonOptions: Array<{ value: string; label: string }>
logicalOptions: Array<{ value: string; label: string }>
onUpdate: (id: string, field: keyof FilterRule, value: string) => void
onRemove: (id: string) => void
onApply: () => void
}
function FilterRuleRow({
rule,
index,
columnOptions,
comparisonOptions,
logicalOptions,
onUpdate,
onRemove,
onApply,
}: FilterRuleRowProps) {
return (
<div className='flex items-center gap-[8px]'>
<Button
variant='ghost'
size='sm'
onClick={() => onRemove(rule.id)}
className='h-[28px] w-[28px] shrink-0 p-0 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]'
>
<X className='h-[12px] w-[12px]' />
</Button>
<div className='w-[80px] shrink-0'>
{index === 0 ? (
<Combobox
size='sm'
options={[{ value: 'where', label: 'where' }]}
value='where'
disabled
/>
) : (
<Combobox
size='sm'
options={logicalOptions}
value={rule.logicalOperator}
onChange={(value) => onUpdate(rule.id, 'logicalOperator', value as 'and' | 'or')}
/>
)}
</div>
<div className='w-[140px] shrink-0'>
<Combobox
size='sm'
options={columnOptions}
value={rule.column}
onChange={(value) => onUpdate(rule.id, 'column', value)}
placeholder='Column'
/>
</div>
<div className='w-[130px] shrink-0'>
<Combobox
size='sm'
options={comparisonOptions}
value={rule.operator}
onChange={(value) => onUpdate(rule.id, 'operator', value)}
/>
</div>
<Input
className='h-[28px] min-w-[200px] flex-1 text-[12px]'
value={rule.value}
onChange={(e) => onUpdate(rule.id, 'value', e.target.value)}
placeholder='Value'
onKeyDown={(e) => {
if (e.key === 'Enter') {
onApply()
}
}}
/>
</div>
)
}
interface SortRuleRowProps {
sortRule: SortRule
columnOptions: Array<{ value: string; label: string }>
sortDirectionOptions: Array<{ value: string; label: string }>
onChange: (rule: SortRule | null) => void
onRemove: () => void
}
function SortRuleRow({
sortRule,
columnOptions,
sortDirectionOptions,
onChange,
onRemove,
}: SortRuleRowProps) {
return (
<div className='flex items-center gap-[8px]'>
<Button
variant='ghost'
size='sm'
onClick={onRemove}
className='h-[28px] w-[28px] shrink-0 p-0 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]'
>
<X className='h-[12px] w-[12px]' />
</Button>
<div className='w-[80px] shrink-0'>
<Combobox size='sm' options={[{ value: 'order', label: 'order' }]} value='order' disabled />
</div>
<div className='w-[140px] shrink-0'>
<Combobox
size='sm'
options={columnOptions}
value={sortRule.column}
onChange={(value) => onChange({ ...sortRule, column: value })}
placeholder='Column'
/>
</div>
<div className='w-[130px] shrink-0'>
<Combobox
size='sm'
options={sortDirectionOptions}
value={sortRule.direction}
onChange={(value) => onChange({ ...sortRule, direction: value as 'asc' | 'desc' })}
/>
</div>
<div className='flex items-center text-[12px] text-[var(--text-tertiary)]'>
{sortRule.direction === 'asc' ? (
<ArrowUpAZ className='h-[14px] w-[14px]' />
) : (
<ArrowDownAZ className='h-[14px] w-[14px]' />
)}
</div>
</div>
)
}

View File

@@ -14,21 +14,20 @@ import {
} from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import type { TableRow as TableRowType } from '@/lib/table'
import { type QueryOptions, TableActionBar, TableQueryBuilder, TableRowModal } from '../components'
import {
CellViewerModal,
RowContextMenu,
SchemaViewerModal,
TableCellRenderer,
TableEmptyRows,
TableHeaderBar,
TableLoadingRows,
TablePagination,
} from './components'
import { useContextMenu, useRowSelection, useTableData } from './hooks'
import type { CellViewerData } from './types'
import { useContextMenu, useRowSelection, useTableData } from '../hooks'
import type { CellViewerData, QueryOptions } from '../lib/types'
import { ActionBar } from './action-bar'
import { EmptyRows, LoadingRows } from './body-states'
import { CellRenderer } from './cell-renderer'
import { CellViewerModal } from './cell-viewer-modal'
import { ContextMenu } from './context-menu'
import { HeaderBar } from './header-bar'
import { Pagination } from './pagination'
import { QueryBuilder } from './query-builder'
import { RowModal } from './row-modal'
import { SchemaModal } from './schema-modal'
export function TableDataViewer() {
export function TableViewer() {
const params = useParams()
const router = useRouter()
@@ -146,7 +145,7 @@ export function TableDataViewer() {
return (
<div className='flex h-full flex-col'>
<TableHeaderBar
<HeaderBar
tableName={tableData.name}
totalCount={totalCount}
isLoading={isLoadingRows}
@@ -156,7 +155,7 @@ export function TableDataViewer() {
/>
<div className='flex shrink-0 flex-col gap-[8px] border-[var(--border)] border-b px-[16px] py-[10px]'>
<TableQueryBuilder
<QueryBuilder
columns={columns}
onApply={handleApplyQueryOptions}
onAddRow={handleAddRow}
@@ -168,7 +167,7 @@ export function TableDataViewer() {
</div>
{hasSelection && (
<TableActionBar
<ActionBar
selectedCount={selectedCount}
onDelete={handleDeleteSelected}
onClearSelection={clearSelection}
@@ -199,9 +198,9 @@ export function TableDataViewer() {
</TableHeader>
<TableBody>
{isLoadingRows ? (
<TableLoadingRows columns={columns} />
<LoadingRows columns={columns} />
) : rows.length === 0 ? (
<TableEmptyRows
<EmptyRows
columnCount={columns.length}
hasFilter={!!queryOptions.filter}
onAddRow={handleAddRow}
@@ -226,7 +225,7 @@ export function TableDataViewer() {
{columns.map((column) => (
<TableCell key={column.name}>
<div className='max-w-[300px] truncate text-[13px]'>
<TableCellRenderer
<CellRenderer
value={row.data[column.name]}
column={column}
onCellClick={handleCellClick}
@@ -241,7 +240,7 @@ export function TableDataViewer() {
</Table>
</div>
<TablePagination
<Pagination
currentPage={currentPage}
totalPages={totalPages}
totalCount={totalCount}
@@ -249,7 +248,7 @@ export function TableDataViewer() {
onNextPage={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}
/>
<TableRowModal
<RowModal
mode='add'
isOpen={showAddModal}
onClose={() => setShowAddModal(false)}
@@ -261,7 +260,7 @@ export function TableDataViewer() {
/>
{editingRow && (
<TableRowModal
<RowModal
mode='edit'
isOpen={true}
onClose={() => setEditingRow(null)}
@@ -275,7 +274,7 @@ export function TableDataViewer() {
)}
{deletingRows.length > 0 && (
<TableRowModal
<RowModal
mode='delete'
isOpen={true}
onClose={() => setDeletingRows([])}
@@ -289,7 +288,7 @@ export function TableDataViewer() {
/>
)}
<SchemaViewerModal
<SchemaModal
isOpen={showSchemaModal}
onClose={() => setShowSchemaModal(false)}
columns={columns}
@@ -302,7 +301,7 @@ export function TableDataViewer() {
copied={copied}
/>
<RowContextMenu
<ContextMenu
contextMenu={contextMenu}
onClose={closeContextMenu}
onEdit={handleContextMenuEdit}

View File

@@ -1,6 +1,6 @@
import { useCallback, useState } from 'react'
import type { TableRow } from '@/lib/table'
import type { ContextMenuState } from '../types'
import type { ContextMenuState } from '../lib/types'
interface UseContextMenuReturn {
contextMenu: ContextMenuState

View File

@@ -1,12 +1,12 @@
import { useQuery } from '@tanstack/react-query'
import type { TableDefinition, TableRow } from '@/lib/table'
import type { BuilderQueryResult } from '../../components/table-query-builder'
import { ROWS_PER_PAGE } from '../constants'
import { ROWS_PER_PAGE } from '../lib/constants'
import type { QueryOptions } from '../lib/types'
interface UseTableDataParams {
workspaceId: string
tableId: string
queryOptions: BuilderQueryResult
queryOptions: QueryOptions
currentPage: number
}

View File

@@ -0,0 +1,3 @@
export * from './constants'
export * from './types'
export * from './utils'

View File

@@ -0,0 +1,27 @@
import type { Filter, Sort, TableRow } from '@/lib/table'
/**
* Query options for filtering and sorting table data
*/
export interface QueryOptions {
filter: Filter | null
sort: Sort | null
}
/**
* Data for viewing a cell's full content in a modal
*/
export interface CellViewerData {
columnName: string
value: unknown
type: 'json' | 'text' | 'date' | 'boolean' | 'number'
}
/**
* State for the row context menu (right-click)
*/
export interface ContextMenuState {
isOpen: boolean
position: { x: number; y: number }
row: TableRow | null
}

View File

@@ -1,6 +1,9 @@
export function getTypeBadgeVariant(
type: string
): 'green' | 'blue' | 'purple' | 'orange' | 'teal' | 'gray' {
type BadgeVariant = 'green' | 'blue' | 'purple' | 'orange' | 'teal' | 'gray'
/**
* Returns the appropriate badge color variant for a column type
*/
export function getTypeBadgeVariant(type: string): BadgeVariant {
switch (type) {
case 'string':
return 'green'

View File

@@ -1,5 +1,5 @@
import { TableDataViewer } from './table-data-viewer'
import { TableViewer } from './components'
export default function TablePage() {
return <TableDataViewer />
return <TableViewer />
}

View File

@@ -1,7 +0,0 @@
export * from './cell-viewer-modal'
export * from './row-context-menu'
export * from './schema-viewer-modal'
export * from './table-body-states'
export * from './table-cell-renderer'
export * from './table-header-bar'
export * from './table-pagination'

View File

@@ -1,2 +0,0 @@
export * from './table-data-viewer'
export * from './types'

View File

@@ -1,13 +0,0 @@
import type { TableRow } from '@/lib/table'
export interface CellViewerData {
columnName: string
value: unknown
type: 'json' | 'text' | 'date' | 'boolean' | 'number'
}
export interface ContextMenuState {
isOpen: boolean
position: { x: number; y: number }
row: TableRow | null
}