Compare commits

..

2 Commits

3 changed files with 247 additions and 25 deletions

View File

@ -43,6 +43,7 @@ const AddTableModal = ({
onAddTable, onAddTable,
schemas = [], schemas = [],
existingTables = [], existingTables = [],
position = 'center', // 'center' | 'bottom-right'
tableTypes = [ tableTypes = [
{ value: 'fact', label: 'Fact Table' }, { value: 'fact', label: 'Fact Table' },
{ value: 'dimension', label: 'Dimension Table' }, { value: 'dimension', label: 'Dimension Table' },
@ -78,6 +79,14 @@ const AddTableModal = ({
const [keys, setKeys] = useState([]); const [keys, setKeys] = useState([]);
const [relations, setRelations] = 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);
// Validation and UI state // Validation and UI state
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@ -90,9 +99,91 @@ const AddTableModal = ({
useEffect(() => { useEffect(() => {
if (open) { if (open) {
resetForm(); resetForm();
fetchKeyTypes();
fetchColumnTypes();
} }
}, [open]); }, [open]);
// 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);
}
} catch (error) {
console.error('Error fetching key types:', error);
// Fallback to default key types
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);
// Fallback to default column types
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);
// Fallback to default column types
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);
}
};
const resetForm = () => { const resetForm = () => {
setFormData({ setFormData({
name: '', name: '',
@ -103,6 +194,10 @@ const AddTableModal = ({
setColumns([]); setColumns([]);
setKeys([]); setKeys([]);
setRelations([]); setRelations([]);
setKeyTypes([]);
setLoadingKeyTypes(false);
setColumnTypesFromAPI([]);
setLoadingColumnTypes(false);
setErrors({}); setErrors({});
setIsSubmitting(false); setIsSubmitting(false);
setKeysViewMode('keys'); setKeysViewMode('keys');
@ -126,10 +221,11 @@ const AddTableModal = ({
// Column management // Column management
const addColumn = () => { const addColumn = () => {
const defaultColumnType = columnTypesFromAPI.length > 0 ? columnTypesFromAPI[0].name : 'VARCHAR';
const newColumn = { const newColumn = {
id: Date.now(), id: Date.now(),
name: '', name: '',
type: 'VARCHAR(255)', type: defaultColumnType,
isPrimaryKey: false, isPrimaryKey: false,
isForeignKey: false, isForeignKey: false,
isNullable: true isNullable: true
@ -177,11 +273,12 @@ const AddTableModal = ({
// Key management // Key management
const addKey = () => { const addKey = () => {
const defaultKeyType = keyTypes.length > 0 ? keyTypes[0].kytp : 'PRIMARY';
const newKey = { const newKey = {
id: Date.now(), id: Date.now(),
name: '', name: '',
columnIds: [], // Changed to array for multi-select columnIds: [], // Changed to array for multi-select
keyType: 'PRIMARY', // PRIMARY, FOREIGN, UNIQUE keyType: defaultKeyType, // Use first available key type from API
keyColumns: [] // Array of {columnId, sequence} objects keyColumns: [] // Array of {columnId, sequence} objects
}; };
setKeys(prev => [...prev, newKey]); setKeys(prev => [...prev, newKey]);
@ -406,12 +503,16 @@ const AddTableModal = ({
columns: columns.map(col => ({ columns: columns.map(col => ({
name: col.name.trim(), name: col.name.trim(),
data_type: col.type, data_type: col.type,
is_primary_key: keys.some(key => is_primary_key: keys.some(key => {
key.keyColumns?.some(kc => kc.columnId === col.id) && key.keyType === 'PRIMARY' const keyTypeObj = keyTypes.find(kt => kt.kytp === key.keyType);
), return key.keyColumns?.some(kc => kc.columnId === col.id) &&
is_foreign_key: keys.some(key => keyTypeObj?.name?.toLowerCase() === 'primary';
key.keyColumns?.some(kc => kc.columnId === col.id) && key.keyType === 'FOREIGN' }),
), is_foreign_key: keys.some(key => {
const keyTypeObj = keyTypes.find(kt => kt.kytp === key.keyType);
return key.keyColumns?.some(kc => kc.columnId === col.id) &&
keyTypeObj?.name?.toLowerCase() === 'foreign';
}),
is_nullable: col.isNullable is_nullable: col.isNullable
})), })),
keys: keys.flatMap(key => keys: keys.flatMap(key =>
@ -444,14 +545,44 @@ const AddTableModal = ({
} }
}; };
// Define positioning styles based on position prop
const getDialogStyles = () => {
if (position === 'bottom-right') {
return {
'& .MuiDialog-container': {
justifyContent: 'flex-end',
alignItems: 'flex-end',
padding: '20px',
},
'& .MuiDialog-paper': {
margin: 0,
maxWidth: '780px', // Increased by 30% from 600px
width: '780px', // Increased by 30% from 600px
maxHeight: '80vh',
minHeight: '60vh',
borderRadius: '12px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
}
};
}
return {
'& .MuiDialog-paper': {
minHeight: '80vh',
maxHeight: '90vh',
maxWidth: '1170px', // Increased by 30% from 900px (lg breakpoint)
}
};
};
return ( return (
<Dialog <Dialog
open={open} open={open}
onClose={onClose} onClose={onClose}
maxWidth="lg" maxWidth={position === 'bottom-right' ? false : 'lg'}
fullWidth fullWidth={position !== 'bottom-right'}
sx={getDialogStyles()}
PaperProps={{ PaperProps={{
sx: { sx: position === 'bottom-right' ? {} : {
minHeight: '80vh', minHeight: '80vh',
maxHeight: '90vh' maxHeight: '90vh'
} }
@ -467,7 +598,7 @@ const AddTableModal = ({
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent dividers sx={{ p: 3 }}> <DialogContent dividers sx={{ p: position === 'bottom-right' ? 2 : 3 }}>
{errors.submit && ( {errors.submit && (
<Alert severity="error" sx={{ mb: 3 }}> <Alert severity="error" sx={{ mb: 3 }}>
{errors.submit} {errors.submit}
@ -475,7 +606,7 @@ const AddTableModal = ({
)} )}
{/* Basic Table Information */} {/* Basic Table Information */}
<Paper elevation={1} sx={{ p: 3, mb: 3 }}> <Paper elevation={1} sx={{ p: position === 'bottom-right' ? 2 : 3, mb: position === 'bottom-right' ? 2 : 3 }}>
<Typography variant="h6" gutterBottom color="primary"> <Typography variant="h6" gutterBottom color="primary">
Table Information Table Information
</Typography> </Typography>
@ -542,7 +673,7 @@ const AddTableModal = ({
</Paper> </Paper>
{/* Columns Section */} {/* Columns Section */}
<Paper elevation={1} sx={{ p: 3, mb: 3 }}> <Paper elevation={1} sx={{ p: position === 'bottom-right' ? 2 : 3, mb: position === 'bottom-right' ? 2 : 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
Columns Columns
@ -587,12 +718,30 @@ const AddTableModal = ({
value={column.type} value={column.type}
onChange={(e) => updateColumn(column.id, 'type', e.target.value)} onChange={(e) => updateColumn(column.id, 'type', e.target.value)}
label="Data Type" label="Data Type"
disabled={loadingColumnTypes}
> >
{columnTypes.map(type => ( {loadingColumnTypes ? (
<MenuItem key={type} value={type}> <MenuItem value="">Loading...</MenuItem>
{type} ) : columnTypesFromAPI.length > 0 ? (
</MenuItem> columnTypesFromAPI.map(type => (
))} <MenuItem key={type.cltp} value={type.name} title={type.description}>
<Box>
<Typography variant="body2" component="div">
{type.name}
</Typography>
<Typography variant="caption" color="text.secondary" component="div">
{type.description}
</Typography>
</Box>
</MenuItem>
))
) : (
columnTypes.map(type => (
<MenuItem key={type} value={type}>
{type}
</MenuItem>
))
)}
</Select> </Select>
</FormControl> </FormControl>
</Grid> </Grid>
@ -624,7 +773,7 @@ const AddTableModal = ({
</Paper> </Paper>
{/* Keys Section */} {/* Keys Section */}
<Paper elevation={1} sx={{ p: 3, mb: 3 }}> <Paper elevation={1} sx={{ p: position === 'bottom-right' ? 2 : 3, mb: position === 'bottom-right' ? 2 : 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
<KeyIcon style={{ marginRight: '8px', verticalAlign: 'middle' }} /> <KeyIcon style={{ marginRight: '8px', verticalAlign: 'middle' }} />
@ -711,10 +860,17 @@ const AddTableModal = ({
updateKey(key.id, 'keyType', e.target.value); updateKey(key.id, 'keyType', e.target.value);
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
disabled={loadingKeyTypes}
> >
<MenuItem value="PRIMARY">Primary Key</MenuItem> {loadingKeyTypes ? (
<MenuItem value="FOREIGN">Foreign Key</MenuItem> <MenuItem value="">Loading...</MenuItem>
<MenuItem value="UNIQUE">Unique Key</MenuItem> ) : (
keyTypes.map((keyType) => (
<MenuItem key={keyType.kytp} value={keyType.kytp}>
{keyType.name} Key
</MenuItem>
))
)}
</Select> </Select>
</FormControl> </FormControl>
</TableCell> </TableCell>
@ -854,7 +1010,7 @@ const AddTableModal = ({
</Paper> </Paper>
{/* Relations Section */} {/* Relations Section */}
<Paper elevation={1} sx={{ p: 3 }}> <Paper elevation={1} sx={{ p: position === 'bottom-right' ? 2 : 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h6" color="primary"> <Typography variant="h6" color="primary">
<LinkIcon style={{ marginRight: '8px', verticalAlign: 'middle' }} /> <LinkIcon style={{ marginRight: '8px', verticalAlign: 'middle' }} />
@ -965,7 +1121,7 @@ const AddTableModal = ({
</Paper> </Paper>
</DialogContent> </DialogContent>
<DialogActions sx={{ p: 3, gap: 1 }}> <DialogActions sx={{ p: position === 'bottom-right' ? 2 : 3, gap: 1 }}>
<Button onClick={onClose} variant="outlined"> <Button onClick={onClose} variant="outlined">
Cancel Cancel
</Button> </Button>

View File

@ -1755,6 +1755,7 @@ const ERDiagramCanvasContent = () => {
onAddTable={handleAddTable} onAddTable={handleAddTable}
schemas={availableSchemas} schemas={availableSchemas}
existingTables={existingTables} existingTables={existingTables}
position="bottom-right"
/> />
</div> </div>

65
test_api.js Normal file
View File

@ -0,0 +1,65 @@
// Test script to verify both API calls
async function testKeyTypesAPI() {
console.log('🔍 Testing Key Types API...');
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) {
console.log('✅ Key Types API call successful');
console.log('Key types found:', data.items.length);
data.items.forEach(item => {
console.log(`- ${item.name} (ID: ${item.kytp})`);
});
} else {
console.log('❌ Key Types API call failed:', data.message);
}
} catch (error) {
console.error('❌ Error calling Key Types API:', error);
}
}
async function testColumnTypesAPI() {
console.log('\n🔍 Testing Column Types API...');
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) {
console.log('✅ Column Types API call successful');
console.log('Column types found:', data.items.length);
data.items.forEach(item => {
console.log(`- ${item.name} (ID: ${item.cltp}) - ${item.description}`);
});
} else {
console.log('❌ Column Types API call failed:', data.message);
}
} catch (error) {
console.error('❌ Error calling Column Types API:', error);
}
}
async function runTests() {
await testKeyTypesAPI();
await testColumnTypesAPI();
}
runTests();