gridColumns.tsx296 lines · main
1// @ts-nocheck
2import { CalculatedColumn, RenderCellProps } from 'react-data-grid'
3
4import { DefaultValue } from '../components/common/DefaultValue'
5import { NullValue } from '../components/common/NullValue'
6import { BooleanEditor } from '../components/editor/BooleanEditor'
7import { DateTimeEditor } from '../components/editor/DateTimeEditor'
8import { JsonEditor } from '../components/editor/JsonEditor'
9import { NumberEditor } from '../components/editor/NumberEditor'
10import { SelectEditor } from '../components/editor/SelectEditor'
11import { TextEditor } from '../components/editor/TextEditor'
12import { TimeEditor, TimeWithTimezoneEditor } from '../components/editor/TimeEditor'
13import { BinaryFormatter } from '../components/formatter/BinaryFormatter'
14import { BooleanFormatter } from '../components/formatter/BooleanFormatter'
15import { DefaultFormatter } from '../components/formatter/DefaultFormatter'
16import { ForeignKeyFormatter } from '../components/formatter/ForeignKeyFormatter'
17import { JsonFormatter } from '../components/formatter/JsonFormatter'
18import { AddColumn } from '../components/grid/AddColumn'
19import { ColumnHeader } from '../components/grid/ColumnHeader'
20import { SelectColumn } from '../components/grid/SelectColumn'
21import {
22 isPendingAddRow,
23 type ColumnType,
24 type SupaColumn,
25 type SupaRow,
26 type SupaTable,
27} from '../types'
28import {
29 isArrayColumn,
30 isBinaryColumn,
31 isBoolColumn,
32 isCiTextColumn,
33 isDateColumn,
34 isDateTimeColumn,
35 isEnumColumn,
36 isForeignKeyColumn,
37 isJsonColumn,
38 isNumericalColumn,
39 isTextColumn,
40 isTimeColumn,
41} from './types'
42import { COLUMN_MIN_WIDTH } from '@/components/grid/constants'
43
44export const ESTIMATED_CHARACTER_PIXEL_WIDTH = 9
45
46export function getGridColumns(
47 table: SupaTable,
48 options?: {
49 tableId?: number
50 editable?: boolean
51 defaultWidth?: string | number
52 onAddColumn?: () => void
53 onExpandJSONEditor: (column: string, row: SupaRow) => void
54 onExpandTextEditor: (column: string, row: SupaRow) => void
55 }
56): any[] {
57 const columns = table.columns.map((x, idx) => {
58 const columnType = getColumnType(x)
59 const columnDefaultWidth = getColumnDefaultWidth(x)
60 const columnWidthBasedOnName =
61 (x.name.length + x.format.length) * ESTIMATED_CHARACTER_PIXEL_WIDTH
62 const columnWidth = options?.defaultWidth
63 ? options.defaultWidth
64 : columnDefaultWidth < columnWidthBasedOnName
65 ? columnWidthBasedOnName
66 : columnDefaultWidth
67
68 const columnDefinition: CalculatedColumn<SupaRow> = {
69 key: x.name,
70 name: x.name,
71 idx: idx + 1,
72 resizable: true,
73 sortable: true,
74 width: columnWidth,
75 minWidth: COLUMN_MIN_WIDTH,
76 frozen: false,
77 renderHeaderCell: (props) => (
78 <ColumnHeader
79 {...props}
80 columnType={columnType}
81 isPrimaryKey={x.isPrimaryKey}
82 isEncrypted={x.isEncrypted}
83 format={x.format}
84 foreignKey={x.foreignKey}
85 comment={x.comment}
86 />
87 ),
88 renderEditCell: options
89 ? getCellEditor(
90 x,
91 columnType,
92 options?.editable ?? false,
93 options.onExpandJSONEditor,
94 options.onExpandTextEditor
95 )
96 : undefined,
97 renderCell: getCellRenderer(x, columnType, {
98 tableId: options?.tableId,
99 }),
100
101 // [Next 18 Refactor] Double check if this is correct
102 parent: undefined,
103 level: 0,
104 maxWidth: undefined,
105 draggable: false,
106 }
107
108 return columnDefinition
109 })
110
111 const gridColumns = [SelectColumn, ...columns]
112 if (options?.onAddColumn) {
113 gridColumns.push(AddColumn)
114 }
115
116 return gridColumns
117}
118
119function getCellEditor(
120 columnDefinition: SupaColumn,
121 columnType: ColumnType,
122 isEditable: boolean,
123 onExpandJSONEditor: (column: string, row: any) => void,
124 onExpandTextEditor: (column: string, row: any) => void
125) {
126 if (!isEditable) {
127 if (['array', 'json'].includes(columnType)) {
128 // eslint-disable-next-line react/display-name
129 return (p: any) => (
130 <JsonEditor {...p} isEditable={isEditable} onExpandEditor={onExpandJSONEditor} />
131 )
132 } else if (!['number', 'boolean'].includes(columnType)) {
133 // eslint-disable-next-line react/display-name
134 return (p: any) => (
135 <TextEditor {...p} isEditable={isEditable} onExpandEditor={onExpandTextEditor} />
136 )
137 } else {
138 return
139 }
140 }
141 if (columnDefinition.isPrimaryKey || !columnDefinition.isUpdatable) {
142 return
143 }
144
145 switch (columnType) {
146 case 'boolean': {
147 // eslint-disable-next-line react/display-name
148 return (p: any) => <BooleanEditor {...p} isNullable={columnDefinition.isNullable} />
149 }
150 case 'date': {
151 return DateTimeEditor('date', columnDefinition.isNullable || false)
152 }
153 case 'datetime': {
154 return columnDefinition.format.endsWith('z')
155 ? DateTimeEditor('datetimetz', columnDefinition.isNullable || false)
156 : DateTimeEditor('datetime', columnDefinition.isNullable || false)
157 }
158 case 'time': {
159 return columnDefinition.format.endsWith('z') ? TimeWithTimezoneEditor : TimeEditor
160 }
161 case 'enum': {
162 const options = columnDefinition.enum!.map((x) => {
163 return { label: x, value: x }
164 })
165 // eslint-disable-next-line react/display-name
166 return (p: any) => (
167 <SelectEditor {...p} options={options} isNullable={columnDefinition.isNullable} />
168 )
169 }
170 case 'array':
171 case 'json': {
172 // eslint-disable-next-line react/display-name
173 return (p: any) => <JsonEditor {...p} onExpandEditor={onExpandJSONEditor} />
174 }
175 case 'number': {
176 return NumberEditor
177 }
178 case 'citext':
179 case 'text': {
180 // eslint-disable-next-line react/display-name
181 return (p: any) => (
182 <TextEditor
183 {...p}
184 isEditable={isEditable}
185 isNullable={columnDefinition.isNullable}
186 onExpandEditor={onExpandTextEditor}
187 />
188 )
189 }
190 default: {
191 return undefined
192 }
193 }
194}
195
196function withPendingAddPlaceholders(
197 Formatter: React.ComponentType<RenderCellProps<SupaRow, unknown>>,
198 columnDef: SupaColumn
199) {
200 return function PendingAwareFormatter(props: RenderCellProps<SupaRow, unknown>) {
201 const value = props.row[props.column.key]
202
203 if (isPendingAddRow(props.row) && (value === undefined || value === null || value === '')) {
204 if (value === null) {
205 return <NullValue />
206 }
207 if (columnDef.defaultValue !== undefined || columnDef.isIdentity || columnDef.isGeneratable) {
208 return <DefaultValue />
209 }
210 }
211
212 return <Formatter {...props} />
213 }
214}
215
216function getCellRenderer(
217 columnDef: SupaColumn,
218 columnType: ColumnType,
219 metadata: { tableId?: number }
220) {
221 let formatter: React.ComponentType<RenderCellProps<SupaRow, unknown>>
222
223 switch (columnType) {
224 case 'boolean': {
225 formatter = BooleanFormatter
226 break
227 }
228 case 'foreign_key': {
229 if (!columnDef.isUpdatable) {
230 formatter = DefaultFormatter
231 } else {
232 // eslint-disable-next-line react/display-name
233 formatter = (p: any) => <ForeignKeyFormatter {...p} tableId={metadata.tableId} />
234 }
235 break
236 }
237 case 'binary': {
238 formatter = BinaryFormatter
239 break
240 }
241 case 'json': {
242 formatter = JsonFormatter
243 break
244 }
245 default: {
246 formatter = DefaultFormatter
247 }
248 }
249
250 // Wrap all formatters to handle pending add row placeholders
251 return withPendingAddPlaceholders(formatter, columnDef)
252}
253
254export function getColumnType(columnDef: SupaColumn): ColumnType {
255 if (isForeignKeyColumn(columnDef)) {
256 return 'foreign_key'
257 } else if (isNumericalColumn(columnDef.dataType)) {
258 return 'number'
259 } else if (isArrayColumn(columnDef.dataType)) {
260 return 'array'
261 } else if (isJsonColumn(columnDef.dataType)) {
262 return 'json'
263 } else if (isTextColumn(columnDef.dataType)) {
264 return 'text'
265 } else if (isCiTextColumn(columnDef.format)) {
266 return 'citext'
267 } else if (isDateColumn(columnDef.format)) {
268 return 'date'
269 } else if (isTimeColumn(columnDef.format)) {
270 return 'time'
271 } else if (isDateTimeColumn(columnDef.format)) {
272 return 'datetime'
273 } else if (isBoolColumn(columnDef.dataType)) {
274 return 'boolean'
275 } else if (isEnumColumn(columnDef.dataType)) {
276 return 'enum'
277 } else if (isBinaryColumn(columnDef.dataType)) {
278 return 'binary'
279 } else return 'unknown'
280}
281
282export function getColumnDefaultWidth(columnDef: SupaColumn): number {
283 if (isNumericalColumn(columnDef.dataType)) {
284 return 120
285 } else if (
286 isDateTimeColumn(columnDef.format) ||
287 isDateColumn(columnDef.format) ||
288 isTimeColumn(columnDef.format)
289 ) {
290 return 150
291 } else if (isBoolColumn(columnDef.dataType)) {
292 return 120
293 } else if (isEnumColumn(columnDef.dataType)) {
294 return 150
295 } else return 250
296}