Compare commits

..

No commits in common. "5fa97985655e9191c9eb611bdfbe0248056d715c" and "8dd63e1953b4f699aeabeb08fa07449f4dee654a" have entirely different histories.

4 changed files with 35 additions and 943 deletions

View File

@ -284,7 +284,7 @@ function App() {
</span> </span>
</div> </div>
{/* Data Entity Item */} {/* ER Diagram 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'
}}> }}>
Data Entity ER Diagram
</span> </span>
</div> </div>
@ -568,7 +568,7 @@ function App() {
margin: '0 0 8px 0', margin: '0 0 8px 0',
color: '#ffffff' color: '#ffffff'
}}> }}>
Data Entity ER Diagram
</h1> </h1>
</div> </div>
<ERDiagramCanvas /> <ERDiagramCanvas />

View File

@ -1122,7 +1122,23 @@ 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>

View File

@ -42,7 +42,7 @@ import AddTableModal from './AddTableModal';
// Custom Table Node Component for Data Entity // Custom Table Node Component for ER Diagram
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 Data Entity // Node types for ER Diagram
const nodeTypes = { const nodeTypes = {
erTable: ERTableNode, erTable: ERTableNode,
erSchemaGroup: ERSchemaGroupNode, erSchemaGroup: ERSchemaGroupNode,
erDatabaseWrapper: ERDatabaseWrapperNode, erDatabaseWrapper: ERDatabaseWrapperNode,
}; };
// Edge types for Data Entity // Edge types for ER Diagram
const edgeTypes = { const edgeTypes = {
erRelationship: ERRelationshipEdge, erRelationship: ERRelationshipEdge,
}; };
@ -461,7 +461,7 @@ const HierarchicalBreadcrumb = ({
Qubit Qubit
</Link> </Link>
<Typography color="text.secondary">Data Entity</Typography> <Typography color="text.secondary">ER Diagram</Typography>
{/* DBTEZ Services Dropdown */} {/* DBTEZ Services Dropdown */}
<div <div
@ -546,7 +546,7 @@ const HierarchicalBreadcrumb = ({
); );
}; };
// Main Data Entity Canvas Component // Main ER Diagram 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 Data Entity using real API // Fetch databases and generate ER diagram 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 Data Entity:', targetDatabase); console.log('Selected database for ER diagram:', 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 Data Entity from API data'); console.log('Successfully fetched and generated ER diagram 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 Data Entity due to API error'); console.log('Using mock data for ER diagram 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 Data Entity with the new table immediately // Regenerate the ER diagram 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 Data Entity with Database Wrapper structure // Generate ER diagram with Database Wrapper structure
const generateERDiagram = (database) => { const generateERDiagram = (database) => {
console.log('🔄 Starting Data Entity generation...'); console.log('🔄 Starting ER diagram 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 Data Entity for database:', database?.name); console.log('Generating ER diagram 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('✅ Data Entity generation completed'); console.log('✅ ER diagram 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 Data Entity...</h3> <h3>Loading ER Diagram...</h3>
<p>Fetching schema for {selectedService?.name} - {selectedDataSource?.name}</p> <p>Fetching schema for {selectedService?.name} - {selectedDataSource?.name}</p>
</div> </div>
</div> </div>

View File

@ -1,924 +0,0 @@
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;