This commit is contained in:
Lakee Sivaraya
2026-01-16 11:01:29 -08:00
parent e69500726b
commit a940dd6351
33 changed files with 29 additions and 321 deletions

View File

@@ -1,9 +1,3 @@
/**
* Table data viewer components.
*
* @module tables/[tableId]/components
*/
export * from './table-action-bar'
export * from './table-query-builder'
export * from './table-row-modal'

View File

@@ -3,33 +3,18 @@
import { Trash2, X } from 'lucide-react'
import { Button } from '@/components/emcn'
/**
* Props for the TableActionBar component.
*/
interface TableActionBarProps {
/** Number of currently selected rows */
selectedCount: number
/** Callback when delete action is triggered */
onDelete: () => void
/** Callback when selection should be cleared */
onClearSelection: () => void
}
/**
* Action bar displayed when rows are selected in the table.
*
* @remarks
* Shows the count of selected rows and provides actions for
* bulk operations like deletion.
*
* @example
* ```tsx
* <TableActionBar
* selectedCount={selectedRows.size}
* onDelete={handleDeleteSelected}
* onClearSelection={() => setSelectedRows(new Set())}
* />
* ```
*/
export function TableActionBar({ selectedCount, onDelete, onClearSelection }: TableActionBarProps) {
return (

View File

@@ -9,13 +9,8 @@ import { useFilterBuilder } from '@/lib/table/filters/use-builder'
import { conditionsToFilter } from '@/lib/table/filters/utils'
import type { JsonValue } from '@/lib/table/types'
/**
* Query options for the table API.
*/
export interface QueryOptions {
/** Filter criteria or null for no filter, keys are column names, values are filter values */
filter: Record<string, JsonValue> | null
/** Sort configuration or null for default sort */
sort: SortCondition | null
}
@@ -46,19 +41,10 @@ interface TableQueryBuilderProps {
/**
* Component for building filter and sort queries for table data.
*
* @remarks
* Provides a visual interface for:
* - Adding multiple filter conditions with AND/OR logic
* - Configuring sort column and direction
* - Applying or clearing the query
*
* @example
* ```tsx
* <TableQueryBuilder
* columns={tableColumns}
* onApply={(options) => setQueryOptions(options)}
* onAddRow={() => setShowAddModal(true)}
* />
* ```
*/
export function TableQueryBuilder({
@@ -89,9 +75,6 @@ export function TableQueryBuilder({
setConditions,
})
/**
* Adds a sort condition.
*/
const handleAddSort = useCallback(() => {
setSortCondition({
id: nanoid(),
@@ -100,16 +83,10 @@ export function TableQueryBuilder({
})
}, [columns])
/**
* Removes the sort condition.
*/
const handleRemoveSort = useCallback(() => {
setSortCondition(null)
}, [])
/**
* Applies the current filter and sort conditions.
*/
const handleApply = useCallback(() => {
const filter = conditionsToFilter(conditions)
onApply({
@@ -118,9 +95,6 @@ export function TableQueryBuilder({
})
}, [conditions, sortCondition, onApply])
/**
* Clears all filters and sort conditions.
*/
const handleClear = useCallback(() => {
setConditions([])
setSortCondition(null)

View File

@@ -20,35 +20,19 @@ import type { ColumnDefinition, TableRow, TableSchema } from '@/lib/table'
const logger = createLogger('TableRowModal')
/**
* Table metadata needed for row operations.
*/
export interface TableInfo {
/** Unique identifier for the table */
id: string
/** Table name for display */
name: string
/** Schema defining columns */
schema: TableSchema
}
/**
* Props for the TableRowModal component.
*/
export interface TableRowModalProps {
/** The operation mode */
mode: 'add' | 'edit' | 'delete'
/** Whether the modal is open */
isOpen: boolean
/** Callback when the modal should close */
onClose: () => void
/** Table to operate on */
table: TableInfo
/** Row being edited/deleted (required for edit/delete modes) */
row?: TableRow
/** Row IDs to delete (for delete mode batch operations) */
rowIds?: string[]
/** Callback when operation is successful */
onSuccess: () => void
}
@@ -67,9 +51,6 @@ function createInitialRowData(columns: ColumnDefinition[]): Record<string, unkno
return initial
}
/**
* Cleans and transforms form data for API submission.
*/
function cleanRowData(
columns: ColumnDefinition[],
rowData: Record<string, unknown>
@@ -104,9 +85,6 @@ function cleanRowData(
return cleanData
}
/**
* Formats a value for display in the input field.
*/
function formatValueForInput(value: unknown, type: string): string {
if (value === null || value === undefined) return ''
if (type === 'json') {
@@ -123,44 +101,6 @@ function formatValueForInput(value: unknown, type: string): string {
return String(value)
}
/**
* Unified modal component for add, edit, and delete row operations.
*
* @example Add mode:
* ```tsx
* <TableRowModal
* mode="add"
* isOpen={isOpen}
* onClose={() => setIsOpen(false)}
* table={tableData}
* onSuccess={() => refetchRows()}
* />
* ```
*
* @example Edit mode:
* ```tsx
* <TableRowModal
* mode="edit"
* isOpen={isOpen}
* onClose={() => setIsOpen(false)}
* table={tableData}
* row={selectedRow}
* onSuccess={() => refetchRows()}
* />
* ```
*
* @example Delete mode:
* ```tsx
* <TableRowModal
* mode="delete"
* isOpen={isOpen}
* onClose={() => setIsOpen(false)}
* table={tableData}
* rowIds={selectedRowIds}
* onSuccess={() => refetchRows()}
* />
* ```
*/
export function TableRowModal({
mode,
isOpen,
@@ -191,9 +131,6 @@ export function TableRowModal({
}
}, [isOpen, mode, columns, row])
/**
* Handles add/edit form submission.
*/
const handleFormSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
@@ -277,9 +214,6 @@ export function TableRowModal({
}
}
/**
* Handles modal close and resets state.
*/
const handleClose = () => {
setRowData({})
setError(null)
@@ -338,7 +272,6 @@ export function TableRowModal({
)
}
// Add/Edit mode UI
const isAddMode = mode === 'add'
return (
@@ -410,21 +343,12 @@ function ErrorMessage({ error }: { error: string | null }) {
)
}
/**
* Props for the ColumnField component.
*/
interface ColumnFieldProps {
/** Column definition */
column: ColumnDefinition
/** Current field value */
value: unknown
/** Callback when value changes */
onChange: (value: unknown) => void
}
/**
* Renders an input field for a column based on its type.
*/
function ColumnField({ column, value, onChange }: ColumnFieldProps) {
return (
<div className='flex flex-col gap-[8px]'>

View File

@@ -1,7 +1,5 @@
/**
* Modal for viewing cell details.
*
* @module tables/[tableId]/table-data-viewer/components/cell-viewer-modal
*/
import { Copy, X } from 'lucide-react'

View File

@@ -1,7 +1,5 @@
/**
* Table data viewer sub-components.
*
* @module tables/[tableId]/table-data-viewer/components
*/
export * from './cell-viewer-modal'

View File

@@ -1,7 +1,5 @@
/**
* Context menu for row actions.
*
* @module tables/[tableId]/table-data-viewer/components/row-context-menu
*/
import { Edit, Trash2 } from 'lucide-react'

View File

@@ -1,7 +1,5 @@
/**
* Modal for viewing table schema.
*
* @module tables/[tableId]/table-data-viewer/components/schema-viewer-modal
*/
import { Info, X } from 'lucide-react'

View File

@@ -1,7 +1,5 @@
/**
* Table body placeholder states (loading and empty).
*
* @module tables/[tableId]/table-data-viewer/components/table-body-states
*/
import { Plus } from 'lucide-react'

View File

@@ -1,7 +1,5 @@
/**
* Cell value renderer for different column types.
*
* @module tables/[tableId]/table-data-viewer/components/table-cell-renderer
*/
import type { ColumnDefinition } from '@/lib/table'

View File

@@ -1,7 +1,5 @@
/**
* Header bar for the table data viewer.
*
* @module tables/[tableId]/table-data-viewer/components/table-header-bar
*/
import { Info, RefreshCw } from 'lucide-react'

View File

@@ -1,7 +1,5 @@
/**
* Pagination controls for the table.
*
* @module tables/[tableId]/table-data-viewer/components/table-pagination
*/
import { Button } from '@/components/emcn'

View File

@@ -1,9 +1,3 @@
/**
* Constants for the table data viewer.
*
* @module tables/[tableId]/table-data-viewer/constants
*/
/** Number of rows to fetch per page */
export const ROWS_PER_PAGE = 100

View File

@@ -1,7 +1,5 @@
/**
* Custom hooks for the table data viewer.
*
* @module tables/[tableId]/table-data-viewer/hooks
*/
export * from './use-context-menu'

View File

@@ -1,9 +1,3 @@
/**
* Hook for managing context menu state.
*
* @module tables/[tableId]/table-data-viewer/hooks/use-context-menu
*/
import { useCallback, useState } from 'react'
import type { TableRow } from '@/lib/table'
import type { ContextMenuState } from '../types'
@@ -26,9 +20,6 @@ export function useContextMenu(): UseContextMenuReturn {
row: null,
})
/**
* Opens the context menu for a row.
*/
const handleRowContextMenu = useCallback((e: React.MouseEvent, row: TableRow) => {
e.preventDefault()
e.stopPropagation()
@@ -39,9 +30,6 @@ export function useContextMenu(): UseContextMenuReturn {
})
}, [])
/**
* Closes the context menu.
*/
const closeContextMenu = useCallback(() => {
setContextMenu((prev) => ({ ...prev, isOpen: false }))
}, [])

View File

@@ -1,7 +1,5 @@
/**
* Hook for managing row selection state.
*
* @module tables/[tableId]/table-data-viewer/hooks/use-row-selection
*/
import { useCallback, useEffect, useState } from 'react'

View File

@@ -1,7 +1,5 @@
/**
* Hook for fetching table data and rows.
*
* @module tables/[tableId]/table-data-viewer/hooks/use-table-data
*/
import { useQuery } from '@tanstack/react-query'

View File

@@ -1,8 +1,2 @@
/**
* Table data viewer module.
*
* @module tables/[tableId]/table-data-viewer
*/
export * from './table-data-viewer'
export * from './types'

View File

@@ -1,11 +1,5 @@
'use client'
/**
* Main table data viewer component.
*
* @module tables/[tableId]/table-data-viewer/table-data-viewer
*/
import { useCallback, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import {
@@ -37,17 +31,11 @@ import type { CellViewerData } from './types'
/**
* Main component for viewing and managing table data.
*
* @remarks
* Provides functionality for:
* - Viewing rows with pagination
* - Filtering and sorting
* - Adding, editing, and deleting rows
* - Viewing cell details for long/complex values
*
* @example
* ```tsx
* <TableDataViewer />
* ```
*/
export function TableDataViewer() {
const params = useParams()

View File

@@ -1,7 +1,5 @@
/**
* Type definitions for the table data viewer.
*
* @module tables/[tableId]/table-data-viewer/types
*/
import type { TableRow } from '@/lib/table'
@@ -15,7 +13,7 @@ export interface CellViewerData {
/** Value being displayed */
value: unknown
/** Display type for formatting */
type: 'json' | 'text' | 'date'
type: 'json' | 'text' | 'date' | 'boolean' | 'number'
}
/**

View File

@@ -1,9 +1,3 @@
/**
* Utility functions for the table data viewer.
*
* @module tables/[tableId]/table-data-viewer/utils
*/
/**
* Gets the badge variant for a column type.
*

View File

@@ -23,19 +23,11 @@ import { useCreateTable } from '@/hooks/queries/use-tables'
const logger = createLogger('CreateTableModal')
/**
* Props for the CreateTableModal component.
*/
interface CreateTableModalProps {
/** Whether the modal is open */
isOpen: boolean
/** Callback when the modal should close */
onClose: () => void
}
/**
* Available column type options for the combobox UI.
*/
const COLUMN_TYPE_OPTIONS: Array<{ value: ColumnDefinition['type']; label: string }> = [
{ value: 'string', label: 'String' },
{ value: 'number', label: 'Number' },
@@ -44,9 +36,6 @@ const COLUMN_TYPE_OPTIONS: Array<{ value: ColumnDefinition['type']; label: strin
{ value: 'json', label: 'JSON' },
]
/**
* Column definition with a stable ID for React key.
*/
interface ColumnWithId extends ColumnDefinition {
/** Stable ID for React key */
id: string
@@ -59,23 +48,6 @@ function createEmptyColumn(): ColumnWithId {
return { id: nanoid(), name: '', type: 'string', required: true, unique: false }
}
/**
* Modal component for creating a new table in a workspace.
*
* @remarks
* This modal allows users to:
* - Set a table name and description
* - Define columns with name, type, and constraints
* - Create the table via the API
*
* @example
* ```tsx
* <CreateTableModal
* isOpen={isModalOpen}
* onClose={() => setIsModalOpen(false)}
* />
* ```
*/
export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
const params = useParams()
const workspaceId = params.workspaceId as string

View File

@@ -1,7 +1,2 @@
/**
* Table management components.
*
* @module tables/components
*/
export * from './create-table-modal'
export * from './table-card'

View File

@@ -26,94 +26,21 @@ import {
} from '@/components/emcn'
import type { TableDefinition } from '@/lib/table'
import { useDeleteTable } from '@/hooks/queries/use-tables'
import { getTypeBadgeVariant } from '../[tableId]/table-data-viewer/utils'
import { formatAbsoluteDate, formatRelativeTime } from './utils'
const logger = createLogger('TableCard')
/**
* Props for the TableCard component.
*/
interface TableCardProps {
/** The table definition to display */
table: TableDefinition
/** ID of the workspace containing this table */
workspaceId: string
}
/**
* Formats a date to relative time (e.g., "2h ago", "3d ago").
*
* @param dateValue - Date string or Date object to format
* @returns Human-readable relative time string
*/
function formatRelativeTime(dateValue: string | Date): string {
const dateString = typeof dateValue === 'string' ? dateValue : dateValue.toISOString()
const date = new Date(dateString)
const now = new Date()
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) return 'just now'
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 604800)}w ago`
if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)}mo ago`
return `${Math.floor(diffInSeconds / 31536000)}y ago`
}
/**
* Formats a date to absolute format for tooltip display.
*
* @param dateValue - Date string or Date object to format
* @returns Formatted date string (e.g., "Jan 15, 2024, 10:30 AM")
*/
function formatAbsoluteDate(dateValue: string | Date): string {
const dateString = typeof dateValue === 'string' ? dateValue : dateValue.toISOString()
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}
/**
* Gets the badge variant for a column type.
*
* @param type - The column type
* @returns Badge variant name
*/
function getTypeBadgeVariant(
type: string
): 'green' | 'blue' | 'purple' | 'orange' | 'teal' | 'gray' {
switch (type) {
case 'string':
return 'green'
case 'number':
return 'blue'
case 'boolean':
return 'purple'
case 'json':
return 'orange'
case 'date':
return 'teal'
default:
return 'gray'
}
}
/**
* Card component for displaying a table summary.
*
* @remarks
* Shows table name, column/row counts, description, and provides
* actions for viewing schema and deleting the table.
*
* @example
* ```tsx
* <TableCard table={tableData} workspaceId="ws_123" />
* ```
*/
export function TableCard({ table, workspaceId }: TableCardProps) {
const router = useRouter()
@@ -123,9 +50,6 @@ export function TableCard({ table, workspaceId }: TableCardProps) {
const deleteTable = useDeleteTable(workspaceId)
/**
* Handles table deletion.
*/
const handleDelete = async () => {
try {
await deleteTable.mutateAsync(table.id)
@@ -135,9 +59,6 @@ export function TableCard({ table, workspaceId }: TableCardProps) {
}
}
/**
* Navigates to the table detail page.
*/
const navigateToTable = () => {
router.push(`/workspace/${workspaceId}/tables/${table.id}`)
}

View File

@@ -0,0 +1,26 @@
export function formatRelativeTime(dateValue: string | Date): string {
const dateString = typeof dateValue === 'string' ? dateValue : dateValue.toISOString()
const date = new Date(dateString)
const now = new Date()
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) return 'just now'
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 604800)}w ago`
if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)}mo ago`
return `${Math.floor(diffInSeconds / 31536000)}y ago`
}
export function formatAbsoluteDate(dateValue: string | Date): string {
const dateString = typeof dateValue === 'string' ? dateValue : dateValue.toISOString()
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}

View File

@@ -1,7 +1,5 @@
/**
* Limits and constants for user-defined tables.
*
* @module lib/table/constants
*/
export const TABLE_LIMITS = {

View File

@@ -1,8 +1,5 @@
/**
* Shared constants and types for table filtering and sorting.
*
* @module lib/table/filters/constants
*
*/
/** Comparison operators for filter conditions (maps to query-builder.ts) */

View File

@@ -1,7 +1,5 @@
/**
* Filter utilities for table queries.
*
* @module lib/table/filters
*/
export * from './constants'

View File

@@ -1,7 +1,5 @@
/**
* Shared utilities for filter builder UI components.
*
* @module lib/table/filters/utils
*/
import { nanoid } from 'nanoid'

View File

@@ -6,8 +6,6 @@
*
* Hooks are not re-exported here to avoid pulling React into server code.
* Import hooks directly from '@/lib/table/hooks' in client components.
*
* @module lib/table
*/
export * from './constants'

View File

@@ -3,8 +3,6 @@
*
* Provides functions to enrich tool descriptions and parameter schemas
* with table-specific information so LLMs can construct proper queries.
*
* @module lib/table/llm-enrichment
*/
/**

View File

@@ -5,8 +5,6 @@
* Use API routes for: HTTP requests, frontend clients.
*
* Note: API routes have their own implementations for HTTP-specific concerns.
*
* @module lib/table/service
*/
import { db } from '@sim/db'

View File

@@ -1,7 +1,5 @@
/**
* Core type definitions for user-defined tables.
*
* @module lib/table/types
*/
import type { COLUMN_TYPES } from './constants'