Renamed ER Diagram to Data Entity #18
|
|
@ -284,7 +284,7 @@ function App() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ER Diagram Item */}
|
{/* Data Entity Item */}
|
||||||
<div
|
<div
|
||||||
className={`nav-button ${activeTab === 'er_diagram' ? 'active' : ''}`}
|
className={`nav-button ${activeTab === 'er_diagram' ? 'active' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -310,7 +310,7 @@ function App() {
|
||||||
color: activeTab === 'er_diagram' ? '#fff' : '#aaa',
|
color: activeTab === 'er_diagram' ? '#fff' : '#aaa',
|
||||||
fontWeight: activeTab === 'er_diagram' ? '500' : 'normal'
|
fontWeight: activeTab === 'er_diagram' ? '500' : 'normal'
|
||||||
}}>
|
}}>
|
||||||
ER Diagram
|
Data Entity
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -568,7 +568,7 @@ function App() {
|
||||||
margin: '0 0 8px 0',
|
margin: '0 0 8px 0',
|
||||||
color: '#ffffff'
|
color: '#ffffff'
|
||||||
}}>
|
}}>
|
||||||
ER Diagram
|
Data Entity
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<ERDiagramCanvas />
|
<ERDiagramCanvas />
|
||||||
|
|
|
||||||
|
|
@ -1122,23 +1122,7 @@ const AddTableModal = ({
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={2}>
|
|
||||||
<FormControl fullWidth size="small">
|
|
||||||
<InputLabel>Target Key</InputLabel>
|
|
||||||
<Select
|
|
||||||
value={relation.targetKey}
|
|
||||||
onChange={(e) => updateRelation(relation.id, 'targetKey', e.target.value)}
|
|
||||||
label="Target Key"
|
|
||||||
disabled={!relation.targetTable}
|
|
||||||
>
|
|
||||||
{getTargetTableKeys(relation.targetTable).map(key => (
|
|
||||||
<MenuItem key={key.id} value={key.id}>
|
|
||||||
{key.name} ({key.type})
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} md={2}>
|
<Grid item xs={12} md={2}>
|
||||||
<FormControl fullWidth size="small">
|
<FormControl fullWidth size="small">
|
||||||
<InputLabel>Table Key</InputLabel>
|
<InputLabel>Table Key</InputLabel>
|
||||||
|
|
|
||||||
|
|
@ -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 ERTableNode = ({ data, id }) => {
|
||||||
const getTableIcon = () => {
|
const getTableIcon = () => {
|
||||||
switch(data.table_type) {
|
switch(data.table_type) {
|
||||||
|
|
@ -357,14 +357,14 @@ const ERSchemaGroupNode = ({ data, id }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Node types for ER Diagram
|
// Node types for Data Entity
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
erTable: ERTableNode,
|
erTable: ERTableNode,
|
||||||
erSchemaGroup: ERSchemaGroupNode,
|
erSchemaGroup: ERSchemaGroupNode,
|
||||||
erDatabaseWrapper: ERDatabaseWrapperNode,
|
erDatabaseWrapper: ERDatabaseWrapperNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Edge types for ER Diagram
|
// Edge types for Data Entity
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
erRelationship: ERRelationshipEdge,
|
erRelationship: ERRelationshipEdge,
|
||||||
};
|
};
|
||||||
|
|
@ -461,7 +461,7 @@ const HierarchicalBreadcrumb = ({
|
||||||
Qubit
|
Qubit
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Typography color="text.secondary">ER Diagram</Typography>
|
<Typography color="text.secondary">Data Entity</Typography>
|
||||||
|
|
||||||
{/* DBTEZ Services Dropdown */}
|
{/* DBTEZ Services Dropdown */}
|
||||||
<div
|
<div
|
||||||
|
|
@ -546,7 +546,7 @@ const HierarchicalBreadcrumb = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main ER Diagram Canvas Component
|
// Main Data Entity Canvas Component
|
||||||
const ERDiagramCanvasContent = () => {
|
const ERDiagramCanvasContent = () => {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
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(() => {
|
useEffect(() => {
|
||||||
const fetchERData = async () => {
|
const fetchERData = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -955,7 +955,7 @@ const ERDiagramCanvasContent = () => {
|
||||||
selectedDataSource?.slug === db.con
|
selectedDataSource?.slug === db.con
|
||||||
) || databases[0]; // Fallback to first database if no match
|
) || 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 })));
|
console.log('Available databases:', databases.map(db => ({ name: db.name, con: db.con })));
|
||||||
|
|
||||||
// Fetch complete structure (schemas, tables, columns) for the selected database
|
// Fetch complete structure (schemas, tables, columns) for the selected database
|
||||||
|
|
@ -989,7 +989,7 @@ const ERDiagramCanvasContent = () => {
|
||||||
generateERDiagram(completeDatabase);
|
generateERDiagram(completeDatabase);
|
||||||
setIsUsingMockData(false);
|
setIsUsingMockData(false);
|
||||||
setApiError(null);
|
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 {
|
} else {
|
||||||
throw new Error('No databases found from API');
|
throw new Error('No databases found from API');
|
||||||
|
|
@ -1024,7 +1024,7 @@ const ERDiagramCanvasContent = () => {
|
||||||
setDatabases([singleDbData]); // Wrap in array for compatibility
|
setDatabases([singleDbData]); // Wrap in array for compatibility
|
||||||
setSelectedDatabase(singleDbData); // Set selected database for modal
|
setSelectedDatabase(singleDbData); // Set selected database for modal
|
||||||
generateERDiagram(singleDbData);
|
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) {
|
} catch (error) {
|
||||||
console.error('Unexpected error in fetchERData:', 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...');
|
console.log('Scheduling diagram regeneration...');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Regenerating diagram with updated database...');
|
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) => {
|
const generateERDiagram = (database) => {
|
||||||
console.log('🔄 Starting ER diagram generation...');
|
console.log('🔄 Starting Data Entity generation...');
|
||||||
console.log('Database received:', database?.name);
|
console.log('Database received:', database?.name);
|
||||||
console.log('Total schemas:', database?.schemas?.length);
|
console.log('Total schemas:', database?.schemas?.length);
|
||||||
console.log('Relationships received:', database?.relationships?.length);
|
console.log('Relationships received:', database?.relationships?.length);
|
||||||
|
|
@ -1281,7 +1281,7 @@ const ERDiagramCanvasContent = () => {
|
||||||
const newEdges = []; // Will be populated with relationship edges
|
const newEdges = []; // Will be populated with relationship edges
|
||||||
|
|
||||||
// Process the selected database
|
// 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('Total schemas in database:', database?.schemas?.length);
|
||||||
console.log('Schema details:', database?.schemas?.map(s => ({
|
console.log('Schema details:', database?.schemas?.map(s => ({
|
||||||
name: s.name,
|
name: s.name,
|
||||||
|
|
@ -1613,7 +1613,7 @@ const ERDiagramCanvasContent = () => {
|
||||||
setNodes(newNodes);
|
setNodes(newNodes);
|
||||||
setEdges(newEdges);
|
setEdges(newEdges);
|
||||||
|
|
||||||
console.log('✅ ER diagram generation completed');
|
console.log('✅ Data Entity generation completed');
|
||||||
|
|
||||||
// Update available schemas and existing tables for the modal
|
// Update available schemas and existing tables for the modal
|
||||||
if (database && database.schemas) {
|
if (database && database.schemas) {
|
||||||
|
|
@ -1695,7 +1695,7 @@ const ERDiagramCanvasContent = () => {
|
||||||
}}>
|
}}>
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<FaProjectDiagram style={{ fontSize: '48px', color: '#8a2be2', marginBottom: '16px' }} />
|
<FaProjectDiagram style={{ fontSize: '48px', color: '#8a2be2', marginBottom: '16px' }} />
|
||||||
<h3>Loading ER Diagram...</h3>
|
<h3>Loading Data Entity...</h3>
|
||||||
<p>Fetching schema for {selectedService?.name} - {selectedDataSource?.name}</p>
|
<p>Fetching schema for {selectedService?.name} - {selectedDataSource?.name}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
maxWidth="lg"
|
||||||
|
fullWidth
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
height: '90vh',
|
||||||
|
maxHeight: '90vh',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider'
|
||||||
|
}}>
|
||||||
|
<TableIcon />
|
||||||
|
Update Table: {tableData?.name}
|
||||||
|
<IconButton
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={{ ml: 'auto' }}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent sx={{ p: 0, overflow: 'hidden' }}>
|
||||||
|
<Box sx={{ p: 3, height: '100%', overflow: 'auto' }}>
|
||||||
|
{/* Basic Information */}
|
||||||
|
<Paper variant="outlined" sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<TableIcon />
|
||||||
|
Basic Information
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Table Name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => handleFormChange('name', e.target.value)}
|
||||||
|
error={!!errors.name}
|
||||||
|
helperText={errors.name}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<FormControl fullWidth size="small" error={!!errors.tableType}>
|
||||||
|
<InputLabel>Table Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData.tableType}
|
||||||
|
onChange={(e) => handleFormChange('tableType', e.target.value)}
|
||||||
|
label="Table Type"
|
||||||
|
>
|
||||||
|
{tableTypes.map(type => (
|
||||||
|
<MenuItem key={type.value} value={type.value}>
|
||||||
|
{type.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => handleFormChange('description', e.target.value)}
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Columns Section */}
|
||||||
|
<Paper variant="outlined" sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<FaColumns />
|
||||||
|
Columns ({columns.length})
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={addColumn}
|
||||||
|
>
|
||||||
|
Add Column
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errors.columns && typeof errors.columns === 'string' && (
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
{errors.columns}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{columns.length === 0 ? (
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
|
||||||
|
No columns defined yet. Click "Add Column" to create table columns.
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<Paper key={column.id} variant="outlined" sx={{ p: 2 }}>
|
||||||
|
<Grid container spacing={2} alignItems="center">
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Column Name"
|
||||||
|
value={column.name}
|
||||||
|
onChange={(e) => updateColumn(column.id, 'name', e.target.value)}
|
||||||
|
error={!!errors.columns?.[column.id]}
|
||||||
|
helperText={errors.columns?.[column.id]}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<InputLabel>Data Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={column.type}
|
||||||
|
onChange={(e) => updateColumn(column.id, 'type', e.target.value)}
|
||||||
|
label="Data Type"
|
||||||
|
>
|
||||||
|
{(columnTypesFromAPI.length > 0 ? columnTypesFromAPI : columnTypes.map(type => ({ name: type }))).map(type => (
|
||||||
|
<MenuItem key={type.name} value={type.name}>
|
||||||
|
{type.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={4}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||||
|
<FormControl size="small">
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Checkbox
|
||||||
|
checked={column.isPrimaryKey}
|
||||||
|
onChange={(e) => updateColumn(column.id, 'isPrimaryKey', e.target.checked)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Typography variant="caption">Primary</Typography>
|
||||||
|
</Box>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl size="small">
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Checkbox
|
||||||
|
checked={column.isForeignKey}
|
||||||
|
onChange={(e) => updateColumn(column.id, 'isForeignKey', e.target.checked)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Typography variant="caption">Foreign</Typography>
|
||||||
|
</Box>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl size="small">
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Checkbox
|
||||||
|
checked={column.isNullable}
|
||||||
|
onChange={(e) => updateColumn(column.id, 'isNullable', e.target.checked)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Typography variant="caption">Nullable</Typography>
|
||||||
|
</Box>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={2}>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => removeColumn(column.id)}
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Keys Section */}
|
||||||
|
<Paper variant="outlined" sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<KeyIcon />
|
||||||
|
Keys ({keys.length})
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={addKey}
|
||||||
|
disabled={columns.length === 0}
|
||||||
|
>
|
||||||
|
Add Key
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{keys.length === 0 ? (
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
|
||||||
|
{columns.length === 0
|
||||||
|
? "Define columns first before creating keys."
|
||||||
|
: "No keys defined yet. Click \"Add Key\" to create table keys."
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
{keys.map((key) => (
|
||||||
|
<Paper key={key.id} variant="outlined" sx={{ p: 2 }}>
|
||||||
|
<Grid container spacing={2} alignItems="center">
|
||||||
|
<Grid item xs={12} md={4}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Key Name"
|
||||||
|
value={key.name}
|
||||||
|
onChange={(e) => updateKey(key.id, 'name', e.target.value)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<InputLabel>Key Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={key.keyType}
|
||||||
|
onChange={(e) => updateKey(key.id, 'keyType', e.target.value)}
|
||||||
|
label="Key Type"
|
||||||
|
disabled={loadingKeyTypes}
|
||||||
|
>
|
||||||
|
{keyTypes.map(type => (
|
||||||
|
<MenuItem key={type.kytp} value={type.kytp}>
|
||||||
|
{type.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={4}>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
Columns: {key.keyColumns?.length || 0} selected
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={1}>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => removeKey(key.id)}
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Relations Section */}
|
||||||
|
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<LinkIcon />
|
||||||
|
Relations ({relations.length})
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={addRelation}
|
||||||
|
disabled={keys.length === 0 || existingTables.length === 0}
|
||||||
|
>
|
||||||
|
Add Relation
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{relations.length === 0 ? (
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
|
||||||
|
{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."
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
{relations.map((relation) => (
|
||||||
|
<Paper key={relation.id} variant="outlined" sx={{ p: 2 }}>
|
||||||
|
<Grid container spacing={2} alignItems="center">
|
||||||
|
<Grid item xs={12} md={2.5}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<InputLabel>Target Table</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={relation.targetTable}
|
||||||
|
onChange={(e) => handleTargetTableChange(relation.id, e.target.value)}
|
||||||
|
label="Target Table"
|
||||||
|
>
|
||||||
|
{existingTables.map(table => (
|
||||||
|
<MenuItem key={table.id} value={table.id}>
|
||||||
|
{table.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={2}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<InputLabel>Source Key</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={relation.sourceKey}
|
||||||
|
onChange={(e) => updateRelation(relation.id, 'sourceKey', e.target.value)}
|
||||||
|
label="Source Key"
|
||||||
|
>
|
||||||
|
{getAvailableKeys().map(key => (
|
||||||
|
<MenuItem key={key.id} value={key.id}>
|
||||||
|
{key.name} ({key.columnName})
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={2}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<InputLabel>Target Key</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={relation.targetKey}
|
||||||
|
onChange={(e) => updateRelation(relation.id, 'targetKey', e.target.value)}
|
||||||
|
label="Target Key"
|
||||||
|
disabled={!relation.targetTable}
|
||||||
|
>
|
||||||
|
{/* This would need to be populated based on target table keys */}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={2}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<InputLabel>Table Key</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={relation.tableKey}
|
||||||
|
onChange={(e) => updateRelation(relation.id, 'tableKey', e.target.value)}
|
||||||
|
label="Table Key"
|
||||||
|
disabled={loadingTableKeys || !relation.targetTable}
|
||||||
|
>
|
||||||
|
{!relation.targetTable ? (
|
||||||
|
<MenuItem disabled>
|
||||||
|
Select a target table first
|
||||||
|
</MenuItem>
|
||||||
|
) : tableKeysFromAPI.length === 0 ? (
|
||||||
|
<MenuItem disabled>
|
||||||
|
{loadingTableKeys ? 'Loading...' : 'No keys available'}
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
tableKeysFromAPI.map(key => (
|
||||||
|
<MenuItem key={key.key} value={key.key}>
|
||||||
|
{key.name}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={1.5}>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => removeRelation(relation.id)}
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions sx={{ p: 3, gap: 1 }}>
|
||||||
|
<Button onClick={handleClose} variant="outlined">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
variant="contained"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
startIcon={isSubmitting ? null : <SaveIcon />}
|
||||||
|
>
|
||||||
|
{isSubmitting ? 'Updating Table...' : 'Update Table'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateTableModal;
|
||||||
Loading…
Reference in New Issue