gridColumns.tsx296 lines · main
| 1 | // @ts-nocheck |
| 2 | import { CalculatedColumn, RenderCellProps } from 'react-data-grid' |
| 3 | |
| 4 | import { DefaultValue } from '../components/common/DefaultValue' |
| 5 | import { NullValue } from '../components/common/NullValue' |
| 6 | import { BooleanEditor } from '../components/editor/BooleanEditor' |
| 7 | import { DateTimeEditor } from '../components/editor/DateTimeEditor' |
| 8 | import { JsonEditor } from '../components/editor/JsonEditor' |
| 9 | import { NumberEditor } from '../components/editor/NumberEditor' |
| 10 | import { SelectEditor } from '../components/editor/SelectEditor' |
| 11 | import { TextEditor } from '../components/editor/TextEditor' |
| 12 | import { TimeEditor, TimeWithTimezoneEditor } from '../components/editor/TimeEditor' |
| 13 | import { BinaryFormatter } from '../components/formatter/BinaryFormatter' |
| 14 | import { BooleanFormatter } from '../components/formatter/BooleanFormatter' |
| 15 | import { DefaultFormatter } from '../components/formatter/DefaultFormatter' |
| 16 | import { ForeignKeyFormatter } from '../components/formatter/ForeignKeyFormatter' |
| 17 | import { JsonFormatter } from '../components/formatter/JsonFormatter' |
| 18 | import { AddColumn } from '../components/grid/AddColumn' |
| 19 | import { ColumnHeader } from '../components/grid/ColumnHeader' |
| 20 | import { SelectColumn } from '../components/grid/SelectColumn' |
| 21 | import { |
| 22 | isPendingAddRow, |
| 23 | type ColumnType, |
| 24 | type SupaColumn, |
| 25 | type SupaRow, |
| 26 | type SupaTable, |
| 27 | } from '../types' |
| 28 | import { |
| 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' |
| 42 | import { COLUMN_MIN_WIDTH } from '@/components/grid/constants' |
| 43 | |
| 44 | export const ESTIMATED_CHARACTER_PIXEL_WIDTH = 9 |
| 45 | |
| 46 | export 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 | |
| 119 | function 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 | |
| 196 | function 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 | |
| 216 | function 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 | |
| 254 | export 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 | |
| 282 | export 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 | } |