OperationItem.tsx93 lines · main
1import { useQueryClient } from '@tanstack/react-query'
2import { Undo2 } from 'lucide-react'
3import { Card, CardContent, CardHeader } from 'ui'
4
5import { formatOperationItemValue } from './OperationQueueSidePanel.utils'
6import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
7import { tableRowKeys } from '@/data/table-rows/keys'
8import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
9import { useTableEditorStateSnapshot } from '@/state/table-editor'
10import { EditCellContentPayload } from '@/state/table-editor-operation-queue.types'
11
12interface OperationItemProps {
13 operationId: string
14 tableId: number
15 content: EditCellContentPayload
16}
17
18export const OperationItem = ({ operationId, tableId, content }: OperationItemProps) => {
19 const { table, columnName, oldValue, newValue, rowIdentifiers } = content
20 const tableSchema = table.schema
21 const tableName = table.name
22
23 const queryClient = useQueryClient()
24 const { data: project } = useSelectedProjectQuery()
25 const snap = useTableEditorStateSnapshot()
26
27 const fullTableName = `${tableSchema}.${tableName}`
28 const whereClause = Object.entries(rowIdentifiers)
29 .map(([key, value]) => `${key} = ${formatOperationItemValue(value)}`)
30 .join(', ')
31
32 const formattedOldValue = formatOperationItemValue(oldValue)
33 const formattedNewValue = formatOperationItemValue(newValue)
34
35 const handleDelete = () => {
36 // Remove the operation from the queue
37 snap.removeOperation(operationId)
38
39 // Invalidate the query to revert the optimistic update
40 if (project) {
41 queryClient.invalidateQueries({
42 queryKey: tableRowKeys.tableRowsAndCount(project.ref, tableId),
43 })
44 }
45 }
46
47 return (
48 <Card className="overflow-hidden">
49 <CardHeader className="py-2 px-3 flex flex-row gap-2 space-y-0 items-center">
50 <div className="min-w-0 flex-1">
51 <code className="text-code-inline dark:bg-surface-300 dark:border-foreground-muted/50">
52 {fullTableName}
53 </code>
54 <div className="text-xs text-foreground mt-1 ml-0.5">
55 <span>{columnName}</span>
56 <span className="text-foreground-muted mx-1.5">·</span>
57 <span>where {whereClause}</span>
58 </div>
59 </div>
60 <ButtonTooltip
61 type="text"
62 aria-label="Discard change"
63 className="px-1.5"
64 icon={<Undo2 />}
65 onClick={handleDelete}
66 tooltip={{
67 content: {
68 side: 'left',
69 align: 'end',
70 text: 'Discard change',
71 },
72 }}
73 />
74 </CardHeader>
75
76 <CardContent className="py-2 px-3 font-mono text-xs">
77 <div className="flex gap-2 py-0.5">
78 <span className="text-destructive select-none font-medium">-</span>
79 <span className="text-destructive truncate max-w-full" title={formattedOldValue}>
80 {formattedOldValue}
81 </span>
82 </div>
83
84 <div className="flex gap-2 py-0.5">
85 <span className="text-brand-link select-none font-medium">+</span>
86 <span className="text-brand-link truncate max-w-full" title={formattedNewValue}>
87 {formattedNewValue}
88 </span>
89 </div>
90 </CardContent>
91 </Card>
92 )
93}