From 361c1a5b47c33dce8000cc669824f8f75233640b Mon Sep 17 00:00:00 2001 From: Devika Date: Mon, 30 Jun 2025 09:39:44 +0530 Subject: [PATCH] Renamed ER Diagram to Data Entity --- src/App.jsx | 6 +- src/components/AddTableModal.jsx | 18 +- src/components/ERDiagramCanvas.jsx | 30 +- src/components/UpdateTableModal.jsx | 924 ++++++++++++++++++++++++++++ 4 files changed, 943 insertions(+), 35 deletions(-) create mode 100644 src/components/UpdateTableModal.jsx diff --git a/src/App.jsx b/src/App.jsx index ad000b2..a4329de 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -284,7 +284,7 @@ function App() { - {/* ER Diagram Item */} + {/* Data Entity Item */}
{ @@ -310,7 +310,7 @@ function App() { color: activeTab === 'er_diagram' ? '#fff' : '#aaa', fontWeight: activeTab === 'er_diagram' ? '500' : 'normal' }}> - ER Diagram + Data Entity
@@ -568,7 +568,7 @@ function App() { margin: '0 0 8px 0', color: '#ffffff' }}> - ER Diagram + Data Entity diff --git a/src/components/AddTableModal.jsx b/src/components/AddTableModal.jsx index 7e055d5..d29d083 100644 --- a/src/components/AddTableModal.jsx +++ b/src/components/AddTableModal.jsx @@ -1122,23 +1122,7 @@ const AddTableModal = ({ - - - Target Key - - - + Table Key diff --git a/src/components/ERDiagramCanvas.jsx b/src/components/ERDiagramCanvas.jsx index 46ad2a2..3c0918a 100644 --- a/src/components/ERDiagramCanvas.jsx +++ b/src/components/ERDiagramCanvas.jsx @@ -42,7 +42,7 @@ import AddTableModal from './AddTableModal'; -// Custom Table Node Component for ER Diagram +// Custom Table Node Component for Data Entity const ERTableNode = ({ data, id }) => { const getTableIcon = () => { switch(data.table_type) { @@ -357,14 +357,14 @@ const ERSchemaGroupNode = ({ data, id }) => { ); }; -// Node types for ER Diagram +// Node types for Data Entity const nodeTypes = { erTable: ERTableNode, erSchemaGroup: ERSchemaGroupNode, erDatabaseWrapper: ERDatabaseWrapperNode, }; -// Edge types for ER Diagram +// Edge types for Data Entity const edgeTypes = { erRelationship: ERRelationshipEdge, }; @@ -461,7 +461,7 @@ const HierarchicalBreadcrumb = ({ Qubit - ER Diagram + Data Entity {/* DBTEZ Services Dropdown */}
{ const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -934,7 +934,7 @@ const ERDiagramCanvasContent = () => { - // Fetch databases and generate ER diagram using real API + // Fetch databases and generate Data Entity using real API useEffect(() => { const fetchERData = async () => { try { @@ -955,7 +955,7 @@ const ERDiagramCanvasContent = () => { selectedDataSource?.slug === db.con ) || databases[0]; // Fallback to first database if no match - console.log('Selected database for ER diagram:', targetDatabase); + console.log('Selected database for Data Entity:', targetDatabase); console.log('Available databases:', databases.map(db => ({ name: db.name, con: db.con }))); // Fetch complete structure (schemas, tables, columns) for the selected database @@ -989,7 +989,7 @@ const ERDiagramCanvasContent = () => { generateERDiagram(completeDatabase); setIsUsingMockData(false); setApiError(null); - console.log('Successfully fetched and generated ER diagram from API data'); + console.log('Successfully fetched and generated Data Entity from API data'); } else { throw new Error('No databases found from API'); @@ -1024,7 +1024,7 @@ const ERDiagramCanvasContent = () => { setDatabases([singleDbData]); // Wrap in array for compatibility setSelectedDatabase(singleDbData); // Set selected database for modal generateERDiagram(singleDbData); - console.log('Using mock data for ER diagram due to API error'); + console.log('Using mock data for Data Entity due to API error'); } } catch (error) { console.error('Unexpected error in fetchERData:', error); @@ -1208,7 +1208,7 @@ const ERDiagramCanvasContent = () => { }); }); - // Regenerate the ER diagram with the new table immediately + // Regenerate the Data Entity with the new table immediately console.log('Scheduling diagram regeneration...'); setTimeout(() => { console.log('Regenerating diagram with updated database...'); @@ -1259,9 +1259,9 @@ const ERDiagramCanvasContent = () => { } }; - // Generate ER diagram with Database Wrapper structure + // Generate Data Entity with Database Wrapper structure const generateERDiagram = (database) => { - console.log('🔄 Starting ER diagram generation...'); + console.log('🔄 Starting Data Entity generation...'); console.log('Database received:', database?.name); console.log('Total schemas:', database?.schemas?.length); console.log('Relationships received:', database?.relationships?.length); @@ -1281,7 +1281,7 @@ const ERDiagramCanvasContent = () => { const newEdges = []; // Will be populated with relationship edges // Process the selected database - console.log('Generating ER diagram for database:', database?.name); + console.log('Generating Data Entity for database:', database?.name); console.log('Total schemas in database:', database?.schemas?.length); console.log('Schema details:', database?.schemas?.map(s => ({ name: s.name, @@ -1613,7 +1613,7 @@ const ERDiagramCanvasContent = () => { setNodes(newNodes); setEdges(newEdges); - console.log('✅ ER diagram generation completed'); + console.log('✅ Data Entity generation completed'); // Update available schemas and existing tables for the modal if (database && database.schemas) { @@ -1695,7 +1695,7 @@ const ERDiagramCanvasContent = () => { }}>
-

Loading ER Diagram...

+

Loading Data Entity...

Fetching schema for {selectedService?.name} - {selectedDataSource?.name}

diff --git a/src/components/UpdateTableModal.jsx b/src/components/UpdateTableModal.jsx new file mode 100644 index 0000000..64e90dd --- /dev/null +++ b/src/components/UpdateTableModal.jsx @@ -0,0 +1,924 @@ +import React, { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Select, + MenuItem, + FormControl, + InputLabel, + IconButton, + Box, + Typography, + Divider, + Grid, + Paper, + Chip, + Alert, + Autocomplete, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Checkbox +} from '@mui/material'; +import { + FaPlus as AddIcon, + FaTrash as DeleteIcon, + FaTimes as CloseIcon, + FaTable as TableIcon, + FaKey as KeyIcon, + FaLink as LinkIcon, + FaArrowLeft as BackIcon, + FaSave as SaveIcon +} from 'react-icons/fa'; + +const UpdateTableModal = ({ + open, + onClose, + onUpdateTable, + tableData, + schemas = [], + existingTables = [], + position = 'center', + tableTypes = [ + { value: 'fact', label: 'Fact Table' }, + { value: 'dimension', label: 'Dimension Table' }, + { value: 'stage', label: 'Stage Table' } + ], + columnTypes = [ + 'INTEGER', + 'VARCHAR(255)', + 'VARCHAR(100)', + 'VARCHAR(50)', + 'TEXT', + 'DECIMAL(10,2)', + 'DECIMAL(15,2)', + 'DATE', + 'TIMESTAMP', + 'BOOLEAN', + 'BIGINT', + 'SMALLINT', + 'FLOAT', + 'DOUBLE' + ] +}) => { + // Main form state + const [formData, setFormData] = useState({ + name: '', + description: '', + tableType: '', + schema: '' + }); + + // Dynamic sections state + const [columns, setColumns] = useState([]); + const [keys, setKeys] = useState([]); + const [relations, setRelations] = useState([]); + + // Key types from API + const [keyTypes, setKeyTypes] = useState([]); + const [loadingKeyTypes, setLoadingKeyTypes] = useState(false); + + // Column types from API + const [columnTypesFromAPI, setColumnTypesFromAPI] = useState([]); + const [loadingColumnTypes, setLoadingColumnTypes] = useState(false); + + // Table keys from API + const [tableKeysFromAPI, setTableKeysFromAPI] = useState([]); + const [loadingTableKeys, setLoadingTableKeys] = useState(false); + + // Validation and UI state + const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Keys section navigation state + const [keysViewMode, setKeysViewMode] = useState('keys'); + const [selectedKeyForColumns, setSelectedKeyForColumns] = useState(null); + + // Populate form when modal opens with existing table data + useEffect(() => { + if (open && tableData) { + populateFormWithTableData(); + fetchKeyTypes(); + fetchColumnTypes(); + } + }, [open, tableData]); + + // Populate form with existing table data + const populateFormWithTableData = () => { + if (!tableData) return; + + setFormData({ + name: tableData.name || '', + description: tableData.description || '', + tableType: tableData.table_type || '', + schema: tableData.schema || '' + }); + + // Populate columns + if (tableData.columns) { + const formattedColumns = tableData.columns.map((col, index) => ({ + id: col.id || Date.now() + index, + name: col.name || '', + type: col.data_type || 'VARCHAR', + isPrimaryKey: col.is_primary_key || false, + isForeignKey: col.is_foreign_key || false, + isNullable: col.is_nullable !== false + })); + setColumns(formattedColumns); + } + + // Populate keys (if available in tableData) + if (tableData.keys) { + const formattedKeys = tableData.keys.map((key, index) => ({ + id: key.id || Date.now() + index, + name: key.name || '', + keyType: key.key_type || 'PRIMARY', + columnIds: key.column_ids || [], + keyColumns: key.key_columns || [] + })); + setKeys(formattedKeys); + } + + // Populate relations (if available in tableData) + if (tableData.relations) { + const formattedRelations = tableData.relations.map((rel, index) => ({ + id: rel.id || Date.now() + index, + targetTable: rel.target_table || '', + sourceKey: rel.source_key || '', + targetKey: rel.target_key || '', + tableKey: rel.table_key || '', + relationType: rel.relation_type || '1:N' + })); + setRelations(formattedRelations); + } + }; + + // Fetch key types from API + const fetchKeyTypes = async () => { + setLoadingKeyTypes(true); + try { + const response = await fetch('https://sandbox.kezel.io/api/qbt_table_key_type_list_get', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: "abdhsg", + org: "sN05Pjv11qvH" + }) + }); + const data = await response.json(); + + if (data.status === 200 && data.items) { + setKeyTypes(data.items); + } else { + console.error('Failed to fetch key types:', data.message); + setKeyTypes([ + { kytp: 'PRIMARY', name: 'Primary' }, + { kytp: 'FOREIGN', name: 'Foreign' }, + { kytp: 'UNIQUE', name: 'Unique' } + ]); + } + } catch (error) { + console.error('Error fetching key types:', error); + setKeyTypes([ + { kytp: 'PRIMARY', name: 'Primary' }, + { kytp: 'FOREIGN', name: 'Foreign' }, + { kytp: 'UNIQUE', name: 'Unique' } + ]); + } finally { + setLoadingKeyTypes(false); + } + }; + + // Fetch column types from API + const fetchColumnTypes = async () => { + setLoadingColumnTypes(true); + try { + const response = await fetch('https://sandbox.kezel.io/api/qbt_column_type_list_get', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: "abdhsg", + org: "sN05Pjv11qvH" + }) + }); + const data = await response.json(); + + if (data.status === 200 && data.items) { + setColumnTypesFromAPI(data.items); + } else { + console.error('Failed to fetch column types:', data.message); + setColumnTypesFromAPI([ + { cltp: 'VARCHAR', name: 'VARCHAR', description: 'Variable-length character string' }, + { cltp: 'INT', name: 'INT', description: 'Standard integer value' }, + { cltp: 'TEXT', name: 'TEXT', description: 'Variable-length character string for long text' }, + { cltp: 'DATE', name: 'DATE', description: 'Date value' }, + { cltp: 'TIMESTAMP', name: 'TIMESTAMP', description: 'Timestamp value' }, + { cltp: 'BOOLEAN', name: 'BOOLEAN', description: 'Logical boolean value' } + ]); + } + } catch (error) { + console.error('Error fetching column types:', error); + setColumnTypesFromAPI([ + { cltp: 'VARCHAR', name: 'VARCHAR', description: 'Variable-length character string' }, + { cltp: 'INT', name: 'INT', description: 'Standard integer value' }, + { cltp: 'TEXT', name: 'TEXT', description: 'Variable-length character string for long text' }, + { cltp: 'DATE', name: 'DATE', description: 'Date value' }, + { cltp: 'TIMESTAMP', name: 'TIMESTAMP', description: 'Timestamp value' }, + { cltp: 'BOOLEAN', name: 'BOOLEAN', description: 'Logical boolean value' } + ]); + } finally { + setLoadingColumnTypes(false); + } + }; + + // Fetch table keys from API for a specific table + const fetchTableKeys = async (tableSlug) => { + if (!tableSlug) { + setTableKeysFromAPI([]); + return; + } + + setLoadingTableKeys(true); + try { + const response = await fetch('https://sandbox.kezel.io/api/qbt_table_key_list_get', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: "abdhsg", + org: "sN05Pjv11qvH", + tbl: tableSlug + }) + }); + const data = await response.json(); + + if (data.status === 200 && data.items) { + setTableKeysFromAPI(data.items); + } else { + console.error('Failed to fetch table keys:', data.message); + setTableKeysFromAPI([]); + } + } catch (error) { + console.error('Error fetching table keys:', error); + setTableKeysFromAPI([]); + } finally { + setLoadingTableKeys(false); + } + }; + + const resetForm = () => { + setFormData({ + name: '', + description: '', + tableType: '', + schema: '' + }); + setColumns([]); + setKeys([]); + setRelations([]); + setKeyTypes([]); + setLoadingKeyTypes(false); + setColumnTypesFromAPI([]); + setLoadingColumnTypes(false); + setTableKeysFromAPI([]); + setLoadingTableKeys(false); + setErrors({}); + setIsSubmitting(false); + setKeysViewMode('keys'); + setSelectedKeyForColumns(null); + }; + + // Form field handlers + const handleFormChange = (field, value) => { + setFormData(prev => ({ + ...prev, + [field]: value + })); + if (errors[field]) { + setErrors(prev => ({ + ...prev, + [field]: null + })); + } + }; + + // Column management + const addColumn = () => { + const defaultColumnType = columnTypesFromAPI.length > 0 ? columnTypesFromAPI[0].name : 'VARCHAR'; + const newColumn = { + id: Date.now(), + name: '', + type: defaultColumnType, + isPrimaryKey: false, + isForeignKey: false, + isNullable: true + }; + setColumns(prev => [...prev, newColumn]); + }; + + const updateColumn = (id, field, value) => { + setColumns(prev => prev.map(col => + col.id === id ? { ...col, [field]: value } : col + )); + + if (field === 'name' && errors.columns?.[id]) { + setErrors(prev => ({ + ...prev, + columns: { + ...prev.columns, + [id]: null + } + })); + } + }; + + const removeColumn = (id) => { + setColumns(prev => prev.filter(col => col.id !== id)); + setKeys(prev => prev.map(key => ({ + ...key, + columnIds: key.columnIds?.filter(colId => colId !== id) || [], + keyColumns: key.keyColumns?.filter(keyCol => keyCol.columnId !== id) || [] + })).filter(key => key.keyColumns?.length > 0 || key.columnIds?.length > 0)); + }; + + // Key management + const addKey = () => { + const defaultKeyType = keyTypes.length > 0 ? keyTypes[0].kytp : 'PRIMARY'; + const newKey = { + id: Date.now(), + name: '', + columnIds: [], + keyType: defaultKeyType, + keyColumns: [] + }; + setKeys(prev => [...prev, newKey]); + }; + + const updateKey = (id, field, value) => { + setKeys(prev => prev.map(key => + key.id === id ? { ...key, [field]: value } : key + )); + }; + + const removeKey = (id) => { + setKeys(prev => prev.filter(key => key.id !== id)); + }; + + // Relation management + const addRelation = () => { + const newRelation = { + id: Date.now(), + targetTable: '', + sourceKey: '', + targetKey: '', + tableKey: '', + relationType: '1:N' + }; + setRelations(prev => [...prev, newRelation]); + }; + + const updateRelation = (id, field, value) => { + setRelations(prev => prev.map(rel => + rel.id === id ? { ...rel, [field]: value } : rel + )); + }; + + // Handle target table selection change + const handleTargetTableChange = (relationId, tableId) => { + updateRelation(relationId, 'targetTable', tableId); + updateRelation(relationId, 'tableKey', ''); + + const selectedTable = existingTables.find(table => table.id === tableId); + if (selectedTable && selectedTable.tbl) { + fetchTableKeys(selectedTable.tbl); + } else { + setTableKeysFromAPI([]); + } + }; + + const removeRelation = (id) => { + setRelations(prev => prev.filter(rel => rel.id !== id)); + }; + + // Get available keys for relations + const getAvailableKeys = () => { + return keys.map(key => { + const keyColumnNames = (key.keyColumns || []) + .sort((a, b) => a.sequence - b.sequence) + .map(kc => { + const column = columns.find(col => col.id === kc.columnId); + return column?.name || 'Unknown Column'; + }) + .join(', '); + + return { + id: key.id, + name: key.name, + columnName: keyColumnNames || 'No columns selected' + }; + }); + }; + + // Form validation + const validateForm = () => { + const newErrors = {}; + + if (!formData.name.trim()) { + newErrors.name = 'Table name is required'; + } + + if (!formData.tableType) { + newErrors.tableType = 'Table type is required'; + } + + if (columns.length === 0) { + newErrors.columns = 'At least one column is required'; + } else { + const columnErrors = {}; + columns.forEach(col => { + if (!col.name.trim()) { + columnErrors[col.id] = 'Column name is required'; + } + }); + if (Object.keys(columnErrors).length > 0) { + newErrors.columns = columnErrors; + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + // Handle form submission + const handleSubmit = async () => { + if (!validateForm()) { + return; + } + + setIsSubmitting(true); + try { + const tableUpdateData = { + id: tableData.id, + name: formData.name.trim(), + description: formData.description.trim(), + table_type: formData.tableType, + schema: formData.schema, + columns: columns.map(col => ({ + id: col.id, + name: col.name.trim(), + data_type: col.type, + is_primary_key: col.isPrimaryKey, + is_foreign_key: col.isForeignKey, + is_nullable: col.isNullable + })), + keys: keys.flatMap(key => + (key.keyColumns || []).map(kc => ({ + name: key.name.trim(), + column_name: columns.find(col => col.id === kc.columnId)?.name || '', + key_type: key.keyType, + sequence: kc.sequence + })) + ), + relations: relations.map(rel => ({ + target_table: rel.targetTable, + source_key: rel.sourceKey, + target_key: rel.targetKey, + table_key: rel.tableKey, + relation_type: rel.relationType + })) + }; + + await onUpdateTable(tableUpdateData); + onClose(); + + } catch (error) { + console.error('Error updating table:', error); + } finally { + setIsSubmitting(false); + } + }; + + const handleClose = () => { + resetForm(); + onClose(); + }; + + return ( + + + + Update Table: {tableData?.name} + + + + + + + + {/* Basic Information */} + + + + Basic Information + + + + handleFormChange('name', e.target.value)} + error={!!errors.name} + helperText={errors.name} + size="small" + /> + + + + Table Type + + + + + handleFormChange('description', e.target.value)} + multiline + rows={2} + size="small" + /> + + + + + {/* Columns Section */} + + + + + Columns ({columns.length}) + + + + + {errors.columns && typeof errors.columns === 'string' && ( + + {errors.columns} + + )} + + {columns.length === 0 ? ( + + No columns defined yet. Click "Add Column" to create table columns. + + ) : ( + + {columns.map((column) => ( + + + + updateColumn(column.id, 'name', e.target.value)} + error={!!errors.columns?.[column.id]} + helperText={errors.columns?.[column.id]} + size="small" + /> + + + + Data Type + + + + + + + + updateColumn(column.id, 'isPrimaryKey', e.target.checked)} + size="small" + /> + Primary + + + + + updateColumn(column.id, 'isForeignKey', e.target.checked)} + size="small" + /> + Foreign + + + + + updateColumn(column.id, 'isNullable', e.target.checked)} + size="small" + /> + Nullable + + + + + + removeColumn(column.id)} + color="error" + size="small" + > + + + + + + ))} + + )} + + + {/* Keys Section */} + + + + + Keys ({keys.length}) + + + + + {keys.length === 0 ? ( + + {columns.length === 0 + ? "Define columns first before creating keys." + : "No keys defined yet. Click \"Add Key\" to create table keys." + } + + ) : ( + + {keys.map((key) => ( + + + + updateKey(key.id, 'name', e.target.value)} + size="small" + /> + + + + Key Type + + + + + + Columns: {key.keyColumns?.length || 0} selected + + + + removeKey(key.id)} + color="error" + size="small" + > + + + + + + ))} + + )} + + + {/* Relations Section */} + + + + + Relations ({relations.length}) + + + + + {relations.length === 0 ? ( + + {keys.length === 0 + ? "Define keys first before creating relations." + : existingTables.length === 0 + ? "No existing tables available for relations." + : "No relations defined yet. Click \"Add Relation\" to create table relationships." + } + + ) : ( + + {relations.map((relation) => ( + + + + + Target Table + + + + + + Source Key + + + + + + Target Key + + + + + + Table Key + + + + + removeRelation(relation.id)} + color="error" + size="small" + > + + + + + + ))} + + )} + + + + + + + + + + ); +}; + +export default UpdateTableModal; \ No newline at end of file -- 2.40.1