Skip to content

Commit f03cec3

Browse files
waleedlatif1claude
andcommitted
feat(tables): add delete-column undo/redo, rename metadata sync, and comprehensive row ID patching
- Delete column now captures column definition, cell data, order, and width for full undo/redo - Column rename undo/redo now properly syncs columnWidths and columnOrder metadata - patchRedoRowId/patchUndoRowId extended to handle all action types containing row IDs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent acfbf7a commit f03cec3

4 files changed

Lines changed: 177 additions & 26 deletions

File tree

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,26 @@ export function Table({
255255
setColumnOrder(order)
256256
}, [])
257257

258+
const handleColumnRename = useCallback((oldName: string, newName: string) => {
259+
let updatedWidths = columnWidthsRef.current
260+
if (oldName in updatedWidths) {
261+
const { [oldName]: width, ...rest } = updatedWidths
262+
updatedWidths = { ...rest, [newName]: width }
263+
setColumnWidths(updatedWidths)
264+
}
265+
const updatedOrder = columnOrderRef.current?.map((n) => (n === oldName ? newName : n))
266+
if (updatedOrder) setColumnOrder(updatedOrder)
267+
updateMetadataRef.current({
268+
columnWidths: updatedWidths,
269+
columnOrder: updatedOrder,
270+
})
271+
}, [])
272+
258273
const { pushUndo, undo, redo } = useTableUndo({
259274
workspaceId,
260275
tableId,
261276
onColumnOrderChange: handleColumnOrderChange,
277+
onColumnRename: handleColumnRename,
262278
})
263279
const undoRef = useRef(undo)
264280
undoRef.current = undo
@@ -1631,6 +1647,26 @@ export function Table({
16311647
if (!deletingColumn) return
16321648
const columnToDelete = deletingColumn
16331649
const orderAtDelete = columnOrderRef.current
1650+
const cols = schemaColumnsRef.current
1651+
const colDef = cols.find((c) => c.name === columnToDelete)
1652+
const colPosition = cols.indexOf(colDef!)
1653+
const currentRows = rowsRef.current
1654+
const cellData = currentRows
1655+
.filter((r) => r.data[columnToDelete] != null)
1656+
.map((r) => ({ rowId: r.id, value: r.data[columnToDelete] }))
1657+
const previousWidth = columnWidthsRef.current[columnToDelete] ?? null
1658+
1659+
pushUndoRef.current({
1660+
type: 'delete-column',
1661+
columnName: columnToDelete,
1662+
columnType: colDef?.type ?? 'string',
1663+
columnPosition: colPosition >= 0 ? colPosition : cols.length,
1664+
columnUnique: colDef?.unique ?? false,
1665+
cellData,
1666+
previousOrder: orderAtDelete ? [...orderAtDelete] : null,
1667+
previousWidth,
1668+
})
1669+
16341670
setDeletingColumn(null)
16351671
deleteColumnMutation.mutate(columnToDelete, {
16361672
onSuccess: () => {

apps/sim/hooks/use-table-undo.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,15 @@ interface UseTableUndoProps {
3535
workspaceId: string
3636
tableId: string
3737
onColumnOrderChange?: (order: string[]) => void
38+
onColumnRename?: (oldName: string, newName: string) => void
3839
}
3940

40-
export function useTableUndo({ workspaceId, tableId, onColumnOrderChange }: UseTableUndoProps) {
41+
export function useTableUndo({
42+
workspaceId,
43+
tableId,
44+
onColumnOrderChange,
45+
onColumnRename,
46+
}: UseTableUndoProps) {
4147
const push = useTableUndoStore((s) => s.push)
4248
const popUndo = useTableUndoStore((s) => s.popUndo)
4349
const popRedo = useTableUndoStore((s) => s.popRedo)
@@ -61,6 +67,8 @@ export function useTableUndo({ workspaceId, tableId, onColumnOrderChange }: UseT
6167

6268
const onColumnOrderChangeRef = useRef(onColumnOrderChange)
6369
onColumnOrderChangeRef.current = onColumnOrderChange
70+
const onColumnRenameRef = useRef(onColumnRename)
71+
onColumnRenameRef.current = onColumnRename
6472

6573
useEffect(() => {
6674
return () => clear(tableId)
@@ -197,21 +205,66 @@ export function useTableUndo({ workspaceId, tableId, onColumnOrderChange }: UseT
197205
break
198206
}
199207

200-
case 'rename-column': {
208+
case 'delete-column': {
201209
if (direction === 'undo') {
202-
updateColumnMutation.mutate({
203-
columnName: action.newName,
204-
updates: { name: action.oldName },
205-
})
210+
addColumnMutation.mutate(
211+
{
212+
name: action.columnName,
213+
type: action.columnType,
214+
unique: action.columnUnique,
215+
position: action.columnPosition,
216+
},
217+
{
218+
onSuccess: () => {
219+
if (action.cellData.length > 0) {
220+
const updates = action.cellData.map((c) => ({
221+
rowId: c.rowId,
222+
data: { [action.columnName]: c.value },
223+
}))
224+
batchUpdateRowsMutation.mutate({ updates })
225+
}
226+
if (action.previousOrder) {
227+
onColumnOrderChangeRef.current?.(action.previousOrder)
228+
updateMetadataMutation.mutate({
229+
columnOrder: action.previousOrder,
230+
...(action.previousWidth !== null
231+
? {
232+
columnWidths: {
233+
...({} as Record<string, number>),
234+
[action.columnName]: action.previousWidth,
235+
},
236+
}
237+
: {}),
238+
})
239+
}
240+
},
241+
}
242+
)
206243
} else {
207-
updateColumnMutation.mutate({
208-
columnName: action.oldName,
209-
updates: { name: action.newName },
244+
deleteColumnMutation.mutate(action.columnName, {
245+
onSuccess: () => {
246+
if (action.previousOrder) {
247+
const newOrder = action.previousOrder.filter((n) => n !== action.columnName)
248+
onColumnOrderChangeRef.current?.(newOrder)
249+
updateMetadataMutation.mutate({ columnOrder: newOrder })
250+
}
251+
},
210252
})
211253
}
212254
break
213255
}
214256

257+
case 'rename-column': {
258+
const fromName = direction === 'undo' ? action.newName : action.oldName
259+
const toName = direction === 'undo' ? action.oldName : action.newName
260+
updateColumnMutation.mutate({
261+
columnName: fromName,
262+
updates: { name: toName },
263+
})
264+
onColumnRenameRef.current?.(fromName, toName)
265+
break
266+
}
267+
215268
case 'update-column-type': {
216269
const type = direction === 'undo' ? action.previousType : action.newType
217270
updateColumnMutation.mutate({

apps/sim/stores/table/store.ts

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,73 @@ const EMPTY_STACKS: TableUndoStacks = { undo: [], redo: [] }
1313

1414
let undoRedoInProgress = false
1515

16+
function patchRowIdInEntry(entry: UndoEntry, oldRowId: string, newRowId: string): UndoEntry {
17+
const { action } = entry
18+
switch (action.type) {
19+
case 'update-cell':
20+
if (action.rowId === oldRowId) {
21+
return { ...entry, action: { ...action, rowId: newRowId } }
22+
}
23+
break
24+
case 'clear-cells': {
25+
const hasMatch = action.cells.some((c) => c.rowId === oldRowId)
26+
if (hasMatch) {
27+
const patched = action.cells.map((c) =>
28+
c.rowId === oldRowId ? { ...c, rowId: newRowId } : c
29+
)
30+
return { ...entry, action: { ...action, cells: patched } }
31+
}
32+
break
33+
}
34+
case 'update-cells': {
35+
const hasMatch = action.cells.some((c) => c.rowId === oldRowId)
36+
if (hasMatch) {
37+
const patched = action.cells.map((c) =>
38+
c.rowId === oldRowId ? { ...c, rowId: newRowId } : c
39+
)
40+
return { ...entry, action: { ...action, cells: patched } }
41+
}
42+
break
43+
}
44+
case 'create-row':
45+
if (action.rowId === oldRowId) {
46+
return { ...entry, action: { ...action, rowId: newRowId } }
47+
}
48+
break
49+
case 'create-rows': {
50+
const hasMatch = action.rows.some((r) => r.rowId === oldRowId)
51+
if (hasMatch) {
52+
const patched = action.rows.map((r) =>
53+
r.rowId === oldRowId ? { ...r, rowId: newRowId } : r
54+
)
55+
return { ...entry, action: { ...action, rows: patched } }
56+
}
57+
break
58+
}
59+
case 'delete-rows': {
60+
const hasMatch = action.rows.some((r) => r.rowId === oldRowId)
61+
if (hasMatch) {
62+
const patched = action.rows.map((r) =>
63+
r.rowId === oldRowId ? { ...r, rowId: newRowId } : r
64+
)
65+
return { ...entry, action: { ...action, rows: patched } }
66+
}
67+
break
68+
}
69+
case 'delete-column': {
70+
const hasMatch = action.cellData.some((c) => c.rowId === oldRowId)
71+
if (hasMatch) {
72+
const patched = action.cellData.map((c) =>
73+
c.rowId === oldRowId ? { ...c, rowId: newRowId } : c
74+
)
75+
return { ...entry, action: { ...action, cellData: patched } }
76+
}
77+
break
78+
}
79+
}
80+
return entry
81+
}
82+
1683
/**
1784
* Run a function without recording undo entries.
1885
* Used by the hook when executing undo/redo mutations to prevent recursive recording.
@@ -86,16 +153,7 @@ export const useTableUndoStore = create<TableUndoState>()(
86153
const stacks = get().stacks[tableId]
87154
if (!stacks) return
88155

89-
const patchedRedo = stacks.redo.map((entry) => {
90-
const { action } = entry
91-
if (action.type === 'delete-rows') {
92-
const patchedRows = action.rows.map((r) =>
93-
r.rowId === oldRowId ? { ...r, rowId: newRowId } : r
94-
)
95-
return { ...entry, action: { ...action, rows: patchedRows } }
96-
}
97-
return entry
98-
})
156+
const patchedRedo = stacks.redo.map((entry) => patchRowIdInEntry(entry, oldRowId, newRowId))
99157

100158
set((state) => ({
101159
stacks: {
@@ -109,13 +167,7 @@ export const useTableUndoStore = create<TableUndoState>()(
109167
const stacks = get().stacks[tableId]
110168
if (!stacks) return
111169

112-
const patchedUndo = stacks.undo.map((entry) => {
113-
const { action } = entry
114-
if (action.type === 'create-row' && action.rowId === oldRowId) {
115-
return { ...entry, action: { ...action, rowId: newRowId } }
116-
}
117-
return entry
118-
})
170+
const patchedUndo = stacks.undo.map((entry) => patchRowIdInEntry(entry, oldRowId, newRowId))
119171

120172
set((state) => ({
121173
stacks: {

apps/sim/stores/table/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ export type TableUndoAction =
3232
}
3333
| { type: 'delete-rows'; rows: DeletedRowSnapshot[] }
3434
| { type: 'create-column'; columnName: string; position: number }
35+
| {
36+
type: 'delete-column'
37+
columnName: string
38+
columnType: string
39+
columnPosition: number
40+
columnUnique: boolean
41+
cellData: Array<{ rowId: string; value: unknown }>
42+
previousOrder: string[] | null
43+
previousWidth: number | null
44+
}
3545
| { type: 'rename-column'; oldName: string; newName: string }
3646
| { type: 'update-column-type'; columnName: string; previousType: string; newType: string }
3747
| {

0 commit comments

Comments
 (0)