From 48250f5ed833aee501b145d5e01e911387b3a966 Mon Sep 17 00:00:00 2001 From: Lakee Sivaraya Date: Wed, 14 Jan 2026 11:44:31 -0800 Subject: [PATCH] chages --- .../tables/[tableId]/table-data-viewer.tsx | 6 +- .../filter-format/filter-format.tsx | 3 +- .../components/sub-block/components/index.ts | 1 + .../components/sort-format/sort-format.tsx | 3 +- .../table-selector/table-selector.tsx | 148 ++++++++++++++++++ .../editor/components/sub-block/sub-block.tsx | 139 ++++------------ apps/sim/blocks/blocks/table.ts | 59 +------ apps/sim/blocks/types.ts | 1 + 8 files changed, 192 insertions(+), 168 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table-selector/table-selector.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table-data-viewer.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table-data-viewer.tsx index 772fe1665..b51af6ddd 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table-data-viewer.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table-data-viewer.tsx @@ -105,7 +105,8 @@ export function TableDataViewer() { const res = await fetch(`/api/table/${tableId}?workspaceId=${workspaceId}`) if (!res.ok) throw new Error('Failed to fetch table') const json = await res.json() - return json.table as TableData + const data = json.data || json + return data.table as TableData }, }) @@ -135,7 +136,8 @@ export function TableDataViewer() { const res = await fetch(`/api/table/${tableId}/rows?${params}`) if (!res.ok) throw new Error('Failed to fetch rows') - return res.json() + const json = await res.json() + return json.data || json }, enabled: !!tableData, }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx index be7698d8a..773a90fd1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx @@ -119,7 +119,8 @@ export function FilterFormat({ const response = await fetch(`/api/table/${tableIdValue}?workspaceId=${workspaceId}`) if (!response.ok) return - const data = await response.json() + const result = await response.json() + const data = result.data || result const cols = data.table?.schema?.columns || [] setDynamicColumns( cols.map((col: { name: string }) => ({ value: col.name, label: col.name })) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts index 43d9915c5..50fa2fafd 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts @@ -31,6 +31,7 @@ export { InputFormat } from './starter/input-format' export { SubBlockInputController } from './sub-block-input-controller' export { Switch } from './switch/switch' export { Table } from './table/table' +export { TableSelector } from './table-selector/table-selector' export { Text } from './text/text' export { TimeInput } from './time-input/time-input' export { ToolInput } from './tool-input/tool-input' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx index b4cda420b..005b45e49 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx @@ -110,7 +110,8 @@ export function SortFormat({ const response = await fetch(`/api/table/${tableIdValue}?workspaceId=${workspaceId}`) if (!response.ok) return - const data = await response.json() + const result = await response.json() + const data = result.data || result const cols = data.table?.schema?.columns || [] // Add built-in columns for sorting const builtInCols = [ diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table-selector/table-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table-selector/table-selector.tsx new file mode 100644 index 000000000..97605b604 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table-selector/table-selector.tsx @@ -0,0 +1,148 @@ +'use client' + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { ExternalLink } from 'lucide-react' +import { useParams } from 'next/navigation' +import { Combobox, type ComboboxOption, Tooltip } from '@/components/emcn' +import { Button } from '@/components/ui/button' +import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' +import type { SubBlockConfig } from '@/blocks/types' + +interface TableSelectorProps { + blockId: string + subBlock: SubBlockConfig + disabled?: boolean + isPreview?: boolean + previewValue?: string | null +} + +interface TableOption { + label: string + id: string +} + +/** + * Table selector component with dropdown and link to view table + * + * @remarks + * Provides a dropdown to select workspace tables and an external link + * to navigate directly to the table page view when a table is selected. + */ +export function TableSelector({ + blockId, + subBlock, + disabled = false, + isPreview = false, + previewValue, +}: TableSelectorProps) { + const params = useParams() + const workspaceId = params.workspaceId as string + + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) + const [tables, setTables] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const value = isPreview ? previewValue : storeValue + const tableId = typeof value === 'string' ? value : null + + /** + * Fetches available tables from the API + */ + const fetchTables = useCallback(async () => { + if (!workspaceId || isPreview || disabled) return + + setIsLoading(true) + setError(null) + + try { + const response = await fetch(`/api/table?workspaceId=${workspaceId}`) + if (!response.ok) { + throw new Error('Failed to fetch tables') + } + + const data = await response.json() + const tableOptions = (data.data?.tables || []).map((table: { id: string; name: string }) => ({ + label: table.name, + id: table.id, + })) + setTables(tableOptions) + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to fetch tables' + setError(errorMessage) + setTables([]) + } finally { + setIsLoading(false) + } + }, [workspaceId, isPreview, disabled]) + + useEffect(() => { + if (!isPreview && !disabled && tables.length === 0 && !isLoading && !error) { + void fetchTables() + } + }, [fetchTables, isPreview, disabled, tables.length, isLoading, error]) + + const options = useMemo(() => { + return tables.map((table) => ({ + label: table.label.toLowerCase(), + value: table.id, + })) + }, [tables]) + + const handleChange = useCallback( + (selectedValue: string) => { + if (isPreview || disabled) return + setStoreValue(selectedValue) + }, + [isPreview, disabled, setStoreValue] + ) + + const handleNavigateToTable = useCallback(() => { + if (tableId && workspaceId) { + window.open(`/workspace/${workspaceId}/tables/${tableId}`, '_blank') + } + }, [workspaceId, tableId]) + + const hasSelectedTable = tableId && !tableId.startsWith('<') + + return ( +
+
+ { + if (open) { + void fetchTables() + } + }} + isLoading={isLoading} + error={error} + searchable={options.length > 5} + searchPlaceholder='Search...' + /> +
+ {hasSelectedTable && !isPreview && ( + + + + + +

View table

+
+
+ )} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index 68fbe6953..b8e6d840f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -1,6 +1,5 @@ -import { type JSX, type MouseEvent, memo, useCallback, useRef, useState } from 'react' -import { AlertTriangle, ExternalLink, Wand2 } from 'lucide-react' -import { useParams } from 'next/navigation' +import { type JSX, type MouseEvent, memo, useRef, useState } from 'react' +import { AlertTriangle, Wand2 } from 'lucide-react' import { Label, Tooltip } from '@/components/emcn/components' import { Button } from '@/components/ui/button' import { cn } from '@/lib/core/utils/cn' @@ -39,6 +38,7 @@ import { SortFormat, Switch, Table, + TableSelector, Text, TimeInput, ToolInput, @@ -47,8 +47,6 @@ import { } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components' import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' import type { SubBlockConfig } from '@/blocks/types' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' /** * Interface for wand control handlers exposed by sub-block inputs @@ -296,99 +294,6 @@ const arePropsEqual = (prevProps: SubBlockProps, nextProps: SubBlockProps): bool ) } -/** - * Props for the DropdownWithTableLink component - */ -interface DropdownWithTableLinkProps { - blockId: string - config: SubBlockConfig - isPreview: boolean - previewValue: string | string[] | null | undefined - isDisabled: boolean - handleMouseDown: (e: MouseEvent) => void -} - -/** - * Renders a dropdown with an optional navigation link for table selectors. - * When the dropdown is for selecting a table (tableId), shows an icon button - * to navigate directly to the table page view. - */ -function DropdownWithTableLink({ - blockId, - config, - isPreview, - previewValue, - isDisabled, - handleMouseDown, -}: DropdownWithTableLinkProps): JSX.Element { - const params = useParams() - const workspaceId = params.workspaceId as string - - const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId) - const tableId = useSubBlockStore( - useCallback( - (state) => { - if (!activeWorkflowId) return null - const value = state.workflowValues[activeWorkflowId]?.[blockId]?.[config.id] - return typeof value === 'string' ? value : null - }, - [activeWorkflowId, blockId, config.id] - ) - ) - - const isTableSelector = config.id === 'tableId' - const hasSelectedTable = isTableSelector && tableId && !tableId.startsWith('<') - - const handleNavigateToTable = useCallback( - (e: MouseEvent) => { - e.stopPropagation() - if (tableId && workspaceId) { - window.open(`/workspace/${workspaceId}/tables/${tableId}`, '_blank') - } - }, - [workspaceId, tableId] - ) - - return ( -
-
- -
- {hasSelectedTable && !isPreview && ( - - - - - -

View table

-
-
- )} -
- ) -} - /** * Renders a single workflow sub-block input based on config.type. * @@ -547,14 +452,36 @@ function SubBlockComponent({ case 'dropdown': return ( - +
+ +
+ ) + + case 'table-selector': + return ( +
+ +
) case 'combobox': diff --git a/apps/sim/blocks/blocks/table.ts b/apps/sim/blocks/blocks/table.ts index adfa769fb..bc1f39227 100644 --- a/apps/sim/blocks/blocks/table.ts +++ b/apps/sim/blocks/blocks/table.ts @@ -3,60 +3,6 @@ import { conditionsToFilter, sortConditionsToSort } from '@/lib/table/filter-bui import type { BlockConfig } from '@/blocks/types' import type { TableQueryResponse } from '@/tools/table/types' -/** - * Fetches available tables for the dropdown selector. - * Defined outside BlockConfig to maintain stable reference and prevent infinite re-renders. - */ -const fetchTableOptions = async () => { - const { useWorkflowRegistry } = await import('@/stores/workflows/registry/store') - - const workspaceId = useWorkflowRegistry.getState().hydration.workspaceId - if (!workspaceId) { - return [] - } - - try { - const response = await fetch(`/api/table?workspaceId=${workspaceId}`) - if (!response.ok) { - return [] - } - - const data = await response.json() - return (data.data?.tables || []).map((table: any) => ({ - label: table.name, - id: table.id, - })) - } catch (error) { - return [] - } -} - -/** - * Fetches a specific table option by ID. - * Defined outside BlockConfig to maintain stable reference and prevent infinite re-renders. - */ -const fetchTableOptionById = async (_blockId: string, _subBlockId: string, tableId: string) => { - const { useWorkflowRegistry } = await import('@/stores/workflows/registry/store') - - const workspaceId = useWorkflowRegistry.getState().hydration.workspaceId - if (!workspaceId) { - return null - } - - try { - const response = await fetch(`/api/table?workspaceId=${workspaceId}`) - if (!response.ok) { - return null - } - - const data = await response.json() - const table = (data.data?.tables || []).find((t: any) => t.id === tableId) - return table ? { label: table.name, id: table.id } : null - } catch (error) { - return null - } -} - export const TableBlock: BlockConfig = { type: 'table', name: 'Table', @@ -91,12 +37,9 @@ export const TableBlock: BlockConfig = { { id: 'tableId', title: 'Table', - type: 'dropdown', + type: 'table-selector', placeholder: 'Select a table', required: true, - options: [], - fetchOptions: fetchTableOptions, - fetchOptionById: fetchTableOptionById, }, // Row ID for get/update/delete diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 98ff5df9e..ea39e1d9b 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -82,6 +82,7 @@ export type SubBlockType = | 'workflow-input-mapper' // Dynamic workflow input mapper based on selected workflow | 'text' // Read-only text display | 'router-input' // Router route definitions with descriptions + | 'table-selector' // Table selector with link to view table /** * Selector types that require display name hydration