|
| 1 | +'use client'; |
| 2 | +import { TableMapRow } from '@/app/dto/MirrorsDTO'; |
| 3 | +import { DBType } from '@/grpc_generated/peers'; |
| 4 | +import { Checkbox } from '@/lib/Checkbox'; |
| 5 | +import { Icon } from '@/lib/Icon'; |
| 6 | +import { Label } from '@/lib/Label'; |
| 7 | +import { RowWithCheckbox } from '@/lib/Layout'; |
| 8 | +import { SearchField } from '@/lib/SearchField'; |
| 9 | +import { TextField } from '@/lib/TextField'; |
| 10 | +import { Dispatch, SetStateAction, useCallback, useState } from 'react'; |
| 11 | +import { BarLoader } from 'react-spinners/'; |
| 12 | +import { fetchColumns, fetchTables } from '../handlers'; |
| 13 | +import { expandableStyle, schemaBoxStyle, tableBoxStyle } from './styles'; |
| 14 | + |
| 15 | +interface SchemaBoxProps { |
| 16 | + sourcePeer: string; |
| 17 | + schema: string; |
| 18 | + rows: TableMapRow[]; |
| 19 | + setRows: Dispatch<SetStateAction<TableMapRow[]>>; |
| 20 | + tableColumns: { tableName: string; columns: string[] }[]; |
| 21 | + setTableColumns: Dispatch< |
| 22 | + SetStateAction<{ tableName: string; columns: string[] }[]> |
| 23 | + >; |
| 24 | + peerType?: DBType; |
| 25 | +} |
| 26 | +const SchemaBox = ({ |
| 27 | + sourcePeer, |
| 28 | + peerType, |
| 29 | + schema, |
| 30 | + rows, |
| 31 | + setRows, |
| 32 | + tableColumns, |
| 33 | + setTableColumns, |
| 34 | +}: SchemaBoxProps) => { |
| 35 | + const [tablesLoading, setTablesLoading] = useState(false); |
| 36 | + const [columnsLoading, setColumnsLoading] = useState(false); |
| 37 | + const [expandedSchemas, setExpandedSchemas] = useState<string[]>([]); |
| 38 | + const [tableQuery, setTableQuery] = useState<string>(''); |
| 39 | + |
| 40 | + const schemaIsExpanded = useCallback( |
| 41 | + (schema: string) => { |
| 42 | + return !!expandedSchemas.find((schemaName) => schemaName === schema); |
| 43 | + }, |
| 44 | + [expandedSchemas] |
| 45 | + ); |
| 46 | + |
| 47 | + const handleAddRow = (source: string) => { |
| 48 | + const newRows = [...rows]; |
| 49 | + const index = newRows.findIndex((row) => row.source === source); |
| 50 | + if (index >= 0) newRows[index] = { ...newRows[index], selected: true }; |
| 51 | + setRows(newRows); |
| 52 | + addTableColumns(source); |
| 53 | + }; |
| 54 | + |
| 55 | + const handleRemoveRow = (source: string) => { |
| 56 | + const newRows = [...rows]; |
| 57 | + const index = newRows.findIndex((row) => row.source === source); |
| 58 | + if (index >= 0) newRows[index] = { ...newRows[index], selected: false }; |
| 59 | + setRows(newRows); |
| 60 | + removeTableColumns(source); |
| 61 | + }; |
| 62 | + |
| 63 | + const handleTableSelect = (on: boolean, source: string) => { |
| 64 | + on ? handleAddRow(source) : handleRemoveRow(source); |
| 65 | + }; |
| 66 | + |
| 67 | + const updateDestination = (source: string, dest: string) => { |
| 68 | + const newRows = [...rows]; |
| 69 | + const index = newRows.findIndex((row) => row.source === source); |
| 70 | + newRows[index] = { ...newRows[index], destination: dest }; |
| 71 | + setRows(newRows); |
| 72 | + }; |
| 73 | + |
| 74 | + const addTableColumns = (table: string) => { |
| 75 | + const schemaName = table.split('.')[0]; |
| 76 | + const tableName = table.split('.')[1]; |
| 77 | + fetchColumns(sourcePeer, schemaName, tableName, setColumnsLoading).then( |
| 78 | + (res) => |
| 79 | + setTableColumns((prev) => { |
| 80 | + return [...prev, { tableName: table, columns: res }]; |
| 81 | + }) |
| 82 | + ); |
| 83 | + }; |
| 84 | + |
| 85 | + const removeTableColumns = (table: string) => { |
| 86 | + setTableColumns((prev) => { |
| 87 | + return prev.filter((column) => column.tableName !== table); |
| 88 | + }); |
| 89 | + }; |
| 90 | + |
| 91 | + const getTableColumns = (tableName: string) => { |
| 92 | + return tableColumns?.find((column) => column.tableName === tableName) |
| 93 | + ?.columns; |
| 94 | + }; |
| 95 | + |
| 96 | + const handleColumnExclusion = ( |
| 97 | + source: string, |
| 98 | + column: string, |
| 99 | + include: boolean |
| 100 | + ) => { |
| 101 | + const currRows = [...rows]; |
| 102 | + const rowOfSource = currRows.find((row) => row.source === source); |
| 103 | + if (rowOfSource) { |
| 104 | + if (include) { |
| 105 | + const updatedExclude = rowOfSource.exclude.filter( |
| 106 | + (col) => col !== column |
| 107 | + ); |
| 108 | + rowOfSource.exclude = updatedExclude; |
| 109 | + } else { |
| 110 | + rowOfSource.exclude.push(column); |
| 111 | + } |
| 112 | + } |
| 113 | + setRows(currRows); |
| 114 | + }; |
| 115 | + |
| 116 | + const handleSelectAll = ( |
| 117 | + e: React.MouseEvent<HTMLInputElement, MouseEvent> |
| 118 | + ) => { |
| 119 | + const newRows = [...rows]; |
| 120 | + for (const row of newRows) { |
| 121 | + row.selected = e.currentTarget.checked; |
| 122 | + if (e.currentTarget.checked) addTableColumns(row.source); |
| 123 | + else removeTableColumns(row.source); |
| 124 | + } |
| 125 | + setRows(newRows); |
| 126 | + }; |
| 127 | + |
| 128 | + const handleSchemaClick = (schemaName: string) => { |
| 129 | + if (!schemaIsExpanded(schemaName)) { |
| 130 | + setTablesLoading(true); |
| 131 | + setExpandedSchemas((curr) => [...curr, schemaName]); |
| 132 | + fetchTables(sourcePeer, schemaName, peerType).then((tableRows) => { |
| 133 | + const newRows = [...rows, ...tableRows]; |
| 134 | + setRows(newRows); |
| 135 | + setTablesLoading(false); |
| 136 | + }); |
| 137 | + } else { |
| 138 | + setExpandedSchemas((curr) => |
| 139 | + curr.filter((expandedSchema) => expandedSchema != schemaName) |
| 140 | + ); |
| 141 | + setRows((curr) => curr.filter((row) => row.schema !== schemaName)); |
| 142 | + } |
| 143 | + }; |
| 144 | + |
| 145 | + return ( |
| 146 | + <div style={schemaBoxStyle}> |
| 147 | + <div> |
| 148 | + <div style={{ ...expandableStyle, cursor: 'auto' }}> |
| 149 | + <div |
| 150 | + style={{ display: 'flex', cursor: 'pointer' }} |
| 151 | + onClick={() => handleSchemaClick(schema)} |
| 152 | + > |
| 153 | + <Icon |
| 154 | + name={ |
| 155 | + schemaIsExpanded(schema) ? 'arrow_drop_down' : 'arrow_right' |
| 156 | + } |
| 157 | + /> |
| 158 | + <p>{schema}</p> |
| 159 | + </div> |
| 160 | + <div style={{ display: schemaIsExpanded(schema) ? 'flex' : 'none' }}> |
| 161 | + <div style={{ display: 'flex' }}> |
| 162 | + <input type='checkbox' onClick={(e) => handleSelectAll(e)} /> |
| 163 | + <Label as='label' style={{ fontSize: 14 }}> |
| 164 | + Select All |
| 165 | + </Label> |
| 166 | + </div> |
| 167 | + <SearchField |
| 168 | + style={{ fontSize: 13 }} |
| 169 | + placeholder='Search for tables' |
| 170 | + value={tableQuery} |
| 171 | + onChange={(e: React.ChangeEvent<HTMLInputElement>) => |
| 172 | + setTableQuery(e.target.value) |
| 173 | + } |
| 174 | + /> |
| 175 | + </div> |
| 176 | + </div> |
| 177 | + {schemaIsExpanded(schema) && ( |
| 178 | + <div className='ml-5 mt-3' style={{ width: '90%' }}> |
| 179 | + {rows.filter((row) => row.schema === schema).length ? ( |
| 180 | + rows |
| 181 | + .filter( |
| 182 | + (row) => |
| 183 | + row.schema === schema && |
| 184 | + row.source.toLowerCase().includes(tableQuery.toLowerCase()) |
| 185 | + ) |
| 186 | + .map((row, index) => { |
| 187 | + const columns = getTableColumns(row.source); |
| 188 | + return ( |
| 189 | + <div key={index} style={tableBoxStyle}> |
| 190 | + <div |
| 191 | + style={{ |
| 192 | + display: 'flex', |
| 193 | + alignItems: 'center', |
| 194 | + justifyContent: 'space-between', |
| 195 | + }} |
| 196 | + > |
| 197 | + <RowWithCheckbox |
| 198 | + label={ |
| 199 | + <Label as='label' style={{ fontSize: 13 }}> |
| 200 | + {row.source} |
| 201 | + </Label> |
| 202 | + } |
| 203 | + action={ |
| 204 | + <Checkbox |
| 205 | + checked={row.selected} |
| 206 | + onCheckedChange={(state: boolean) => |
| 207 | + handleTableSelect(state, row.source) |
| 208 | + } |
| 209 | + /> |
| 210 | + } |
| 211 | + /> |
| 212 | + |
| 213 | + <div |
| 214 | + style={{ |
| 215 | + width: '40%', |
| 216 | + display: row.selected ? 'block' : 'none', |
| 217 | + }} |
| 218 | + key={row.source} |
| 219 | + > |
| 220 | + <p style={{ fontSize: 12 }}>Target Table:</p> |
| 221 | + <TextField |
| 222 | + key={row.source} |
| 223 | + style={{ |
| 224 | + fontSize: 12, |
| 225 | + marginTop: '0.5rem', |
| 226 | + }} |
| 227 | + variant='simple' |
| 228 | + placeholder={'Enter target table'} |
| 229 | + defaultValue={row.destination} |
| 230 | + onChange={( |
| 231 | + e: React.ChangeEvent<HTMLInputElement> |
| 232 | + ) => updateDestination(row.source, e.target.value)} |
| 233 | + /> |
| 234 | + </div> |
| 235 | + </div> |
| 236 | + {row.selected && ( |
| 237 | + <div className='ml-5' style={{ width: '100%' }}> |
| 238 | + <Label |
| 239 | + as='label' |
| 240 | + colorName='lowContrast' |
| 241 | + style={{ fontSize: 13 }} |
| 242 | + > |
| 243 | + Columns |
| 244 | + </Label> |
| 245 | + {columns ? ( |
| 246 | + columns.map((column, index) => { |
| 247 | + const columnName = column.split(':')[0]; |
| 248 | + const columnType = column.split(':')[1]; |
| 249 | + const isPkey = column.split(':')[2] === 'true'; |
| 250 | + return ( |
| 251 | + <RowWithCheckbox |
| 252 | + key={index} |
| 253 | + label={ |
| 254 | + <Label |
| 255 | + as='label' |
| 256 | + style={{ |
| 257 | + fontSize: 13, |
| 258 | + display: 'flex', |
| 259 | + }} |
| 260 | + > |
| 261 | + {columnName}{' '} |
| 262 | + <p |
| 263 | + style={{ |
| 264 | + marginLeft: '0.5rem', |
| 265 | + color: 'gray', |
| 266 | + }} |
| 267 | + > |
| 268 | + {columnType} |
| 269 | + </p> |
| 270 | + </Label> |
| 271 | + } |
| 272 | + action={ |
| 273 | + <Checkbox |
| 274 | + disabled={isPkey} |
| 275 | + checked={ |
| 276 | + !row.exclude.find( |
| 277 | + (col) => col == columnName |
| 278 | + ) |
| 279 | + } |
| 280 | + onCheckedChange={(state: boolean) => |
| 281 | + handleColumnExclusion( |
| 282 | + row.source, |
| 283 | + columnName, |
| 284 | + state |
| 285 | + ) |
| 286 | + } |
| 287 | + /> |
| 288 | + } |
| 289 | + /> |
| 290 | + ); |
| 291 | + }) |
| 292 | + ) : columnsLoading ? ( |
| 293 | + <BarLoader /> |
| 294 | + ) : ( |
| 295 | + <Label |
| 296 | + as='label' |
| 297 | + colorName='lowContrast' |
| 298 | + style={{ fontSize: 13 }} |
| 299 | + > |
| 300 | + No columns in {row.source} |
| 301 | + </Label> |
| 302 | + )} |
| 303 | + </div> |
| 304 | + )} |
| 305 | + </div> |
| 306 | + ); |
| 307 | + }) |
| 308 | + ) : tablesLoading ? ( |
| 309 | + <BarLoader /> |
| 310 | + ) : ( |
| 311 | + <Label |
| 312 | + as='label' |
| 313 | + colorName='lowContrast' |
| 314 | + style={{ fontSize: 13 }} |
| 315 | + > |
| 316 | + No tables in {schema} |
| 317 | + </Label> |
| 318 | + )} |
| 319 | + </div> |
| 320 | + )} |
| 321 | + </div> |
| 322 | + </div> |
| 323 | + ); |
| 324 | +}; |
| 325 | + |
| 326 | +export default SchemaBox; |
0 commit comments