Compare commits
No commits in common. "5fa97985655e9191c9eb611bdfbe0248056d715c" and "8dd63e1953b4f699aeabeb08fa07449f4dee654a" have entirely different histories.
5fa9798565
...
8dd63e1953
|
|
@ -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 />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
Loading…
Reference in New Issue