diff --git a/src/components/InfiniteCanvas.jsx b/src/components/InfiniteCanvas.jsx
index 7244cfc..64280f1 100644
--- a/src/components/InfiniteCanvas.jsx
+++ b/src/components/InfiniteCanvas.jsx
@@ -19,11 +19,1237 @@ import ReactFlow, {
import 'reactflow/dist/style.css';
// Import icons from react-icons
-import { FaDatabase, FaTable, FaFlask, FaArrowRight } from 'react-icons/fa';
+import { FaDatabase, FaTable, FaFlask, FaArrowRight, FaPlus, FaTimes } from 'react-icons/fa';
import { BiSolidData } from 'react-icons/bi';
import { AiFillFolder } from 'react-icons/ai';
import { BsFileEarmarkSpreadsheet } from 'react-icons/bs';
+// Import mock data from DataflowCanvas
+import mockApiData from './mockData';
+
+// Modal component for creating entities
+const Modal = ({ isOpen, title, onClose, children }) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
{title}
+
+
+
+ {children}
+
+
+
+ );
+};
+
+// Database Creation Form with nested schema and table creation
+const DatabaseForm = ({ onSave, onCancel }) => {
+ const [formData, setFormData] = useState({
+ name: '',
+ description: '',
+ url: '',
+ key: '',
+ type: 'PostgreSQL',
+ schemas: [] // Array to hold schemas
+ });
+
+ const [showSchemaForm, setShowSchemaForm] = useState(false);
+ const [currentSchemaIndex, setCurrentSchemaIndex] = useState(null);
+ const [showTableForm, setShowTableForm] = useState(false);
+ const [currentSchemaForTable, setCurrentSchemaForTable] = useState(null);
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({ ...prev, [name]: value }));
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ onSave(formData);
+ };
+
+ const fetchId = () => {
+ // In a real app, this would make an API call
+ alert('Fetching ID from URL: ' + formData.url);
+ };
+
+ // Add a new schema to the database
+ const addSchema = (schemaData) => {
+ if (currentSchemaIndex !== null) {
+ // Edit existing schema
+ const updatedSchemas = [...formData.schemas];
+ updatedSchemas[currentSchemaIndex] = {
+ ...updatedSchemas[currentSchemaIndex],
+ ...schemaData,
+ };
+ setFormData(prev => ({ ...prev, schemas: updatedSchemas }));
+ } else {
+ // Add new schema
+ const newSchema = {
+ id: `schema-${Date.now()}`,
+ name: schemaData.name,
+ description: schemaData.description,
+ tables: [] // Initialize with empty tables array
+ };
+ setFormData(prev => ({
+ ...prev,
+ schemas: [...prev.schemas, newSchema]
+ }));
+ }
+ setShowSchemaForm(false);
+ setCurrentSchemaIndex(null);
+ };
+
+ // Add a new table to a schema
+ const addTable = (tableData) => {
+ const schemaIndex = formData.schemas.findIndex(s => s.id === currentSchemaForTable);
+ if (schemaIndex !== -1) {
+ const newTable = {
+ id: `table-${Date.now()}`,
+ name: tableData.name,
+ description: tableData.description,
+ type: tableData.type
+ };
+
+ const updatedSchemas = [...formData.schemas];
+ updatedSchemas[schemaIndex] = {
+ ...updatedSchemas[schemaIndex],
+ tables: [...updatedSchemas[schemaIndex].tables, newTable]
+ };
+
+ setFormData(prev => ({ ...prev, schemas: updatedSchemas }));
+ }
+ setShowTableForm(false);
+ setCurrentSchemaForTable(null);
+ };
+
+ // Remove a schema
+ const removeSchema = (index) => {
+ const updatedSchemas = [...formData.schemas];
+ updatedSchemas.splice(index, 1);
+ setFormData(prev => ({ ...prev, schemas: updatedSchemas }));
+ };
+
+ // Remove a table
+ const removeTable = (schemaIndex, tableIndex) => {
+ const updatedSchemas = [...formData.schemas];
+ updatedSchemas[schemaIndex].tables.splice(tableIndex, 1);
+ setFormData(prev => ({ ...prev, schemas: updatedSchemas }));
+ };
+
+ return (
+
+ {showSchemaForm ? (
+
+
+
+ {currentSchemaIndex !== null ? 'Edit Schema' : 'Add Schema'}
+
+
+
+
+
{
+ setShowSchemaForm(false);
+ setCurrentSchemaIndex(null);
+ }}
+ initialData={currentSchemaIndex !== null ? formData.schemas[currentSchemaIndex] : null}
+ />
+
+ ) : showTableForm ? (
+
+
+
Add Table
+
+
+
+
{
+ setShowTableForm(false);
+ setCurrentSchemaForTable(null);
+ }}
+ />
+
+ ) : (
+
+ )}
+
+ );
+};
+
+// Schema Creation Form
+const SchemaForm = ({ onSave, onCancel, parentDatabase, initialData }) => {
+ const [formData, setFormData] = useState({
+ name: initialData ? initialData.name : '',
+ description: initialData ? initialData.description : ''
+ });
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({ ...prev, [name]: value }));
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ onSave(parentDatabase ? { ...formData, parentDatabase } : formData);
+ };
+
+ return (
+
+ );
+};
+
+// Entity Selector Component
+const EntitySelector = ({
+ activeEntityType,
+ setActiveEntityType,
+ databases,
+ schemas,
+ selectedDatabaseForSchema,
+ setSelectedDatabaseForSchema,
+ selectedSchemaForTable,
+ setSelectedSchemaForTable,
+ onSaveDatabase,
+ onSaveSchema,
+ onSaveTable,
+ onCancel
+}) => {
+ // Determine which databases are available for schema creation
+ const availableDatabases = databases || [];
+
+ // Determine which schemas are available for table creation
+ const availableSchemas = schemas ? schemas.filter(schema =>
+ selectedDatabaseForSchema ? schema.dbId === selectedDatabaseForSchema : true
+ ) : [];
+
+ return (
+
+ {/* Entity Type Selector */}
+
+
+
+
+
+
+
+
+ {/* Parent Selector (for schema and table) */}
+ {activeEntityType === 'schema' && availableDatabases.length > 0 && (
+
+
+
+
+ )}
+
+ {activeEntityType === 'table' && availableSchemas.length > 0 && (
+
+
+
+
+ )}
+
+ {/* Form based on selected entity type */}
+ {activeEntityType === 'database' && (
+
+ )}
+
+ {activeEntityType === 'schema' && selectedDatabaseForSchema && (
+
+ )}
+
+ {activeEntityType === 'table' && selectedSchemaForTable && (
+
+ )}
+
+ {/* Guidance message when no parent is selected */}
+ {activeEntityType === 'schema' && !selectedDatabaseForSchema && availableDatabases.length > 0 && (
+
+ Please select a database to create a schema.
+
+ )}
+
+ {activeEntityType === 'table' && !selectedSchemaForTable && availableSchemas.length > 0 && (
+
+ Please select a schema to create a table.
+
+ )}
+
+ {/* Guidance message when no parents are available */}
+ {activeEntityType === 'schema' && availableDatabases.length === 0 && (
+
+ You need to create a database first before creating a schema.
+
+ )}
+
+ {activeEntityType === 'table' && availableSchemas.length === 0 && (
+
+ You need to create a schema first before creating a table.
+
+ )}
+
+ );
+};
+
+// Table Creation Form
+const TableForm = ({ onSave, onCancel, parentSchema, initialData }) => {
+ const [formData, setFormData] = useState({
+ name: initialData ? initialData.name : '',
+ description: initialData ? initialData.description : '',
+ type: initialData ? initialData.type : 'dimension',
+ columns: initialData && initialData.columns ? initialData.columns : []
+ });
+
+ const [showColumnInput, setShowColumnInput] = useState(false);
+ const [columnName, setColumnName] = useState('');
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({ ...prev, [name]: value }));
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ onSave(parentSchema ? { ...formData, parentSchema } : formData);
+ };
+
+ const addColumn = () => {
+ if (columnName.trim()) {
+ setFormData(prev => ({
+ ...prev,
+ columns: [...prev.columns, columnName.trim()]
+ }));
+ setColumnName('');
+ setShowColumnInput(false);
+ }
+ };
+
+ const removeColumn = (index) => {
+ const updatedColumns = [...formData.columns];
+ updatedColumns.splice(index, 1);
+ setFormData(prev => ({ ...prev, columns: updatedColumns }));
+ };
+
+ return (
+
+ );
+};
+
// Custom edge with animated arrow
const CustomEdge = ({ id, source, target, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, data, markerEnd }) => {
const [edgePath, labelX, labelY] = getBezierPath({
@@ -73,6 +1299,20 @@ const DatabaseNode = ({ data }) => {
const borderColor = isDbtez ? '#00a99d' : '#1890ff';
const handleColor = isDbtez ? '#00a99d' : '#1890ff';
+ // Database SVG icon
+ const DatabaseIcon = () => (
+
+ );
+
return (
{
display: 'flex',
alignItems: 'center'
}}>
- {isDbtez ? (
-
-
-
- ) : (
-
- )}
+
+
+
{data.label}
@@ -158,6 +1383,20 @@ const SchemaNode = ({ data }) => {
const borderColor = isSalesSchema ? '#fa8c16' : (isDbtezSchema ? '#00a99d' : '#52c41a');
const handleColor = isSalesSchema ? '#fa8c16' : (isDbtezSchema ? '#00a99d' : '#52c41a');
+ // Schema SVG icon
+ const SchemaIcon = () => (
+
+ );
+
return (
{
display: 'flex',
alignItems: 'center'
}}>
- {isSalesSchema ? (
-
-
-
- ) : isDbtezSchema ? (
-
-
-
- ) : (
-
- )}
+
+
+
{data.label}
@@ -249,12 +1458,45 @@ const SchemaNode = ({ data }) => {
};
const TableNode = ({ data }) => {
- const isTestTable = data.label === 'test_table';
- const isTestTable2 = data.label === 'test_table2';
+ const tableType = data.type || (data.isFact ? 'fact' : 'dimension');
- // Use dark theme colors
- const borderColor = '#52c41a';
- const background = '#1a1a1a';
+ // Determine colors based on table type
+ let borderColor, background, labelBg, labelColor;
+
+ switch(tableType) {
+ case 'fact':
+ borderColor = '#fa8c16'; // Orange for fact tables
+ background = '#1a1a1a';
+ labelBg = '#fa8c16';
+ labelColor = 'white';
+ break;
+ case 'stage':
+ borderColor = '#1890ff'; // Blue for stage tables
+ background = '#1a1a1a';
+ labelBg = '#1890ff';
+ labelColor = 'white';
+ break;
+ case 'dimension':
+ default:
+ borderColor = '#52c41a'; // Green for dimension tables
+ background = '#1a1a1a';
+ labelBg = '#52c41a';
+ labelColor = 'white';
+ }
+
+ // Table SVG icon
+ const TableIcon = () => (
+
+ );
return (
{
border: `1px solid ${borderColor}`,
width: '150px',
position: 'relative',
- boxShadow: '0 0 10px rgba(82, 196, 26, 0.3)',
+ boxShadow: `0 0 10px ${borderColor}33`,
color: '#ffffff'
}}>
{/* Connection handles */}
@@ -272,95 +1514,95 @@ const TableNode = ({ data }) => {
type="source"
position={Position.Right}
id="right"
- style={{ background: '#52c41a', width: '10px', height: '10px' }}
+ style={{ background: borderColor, width: '10px', height: '10px' }}
isConnectable={true}
/>
- {isTestTable ? (
-
- ) : isTestTable2 ? (
-
- ) : (
-
- )}
+
+
+
{data.label}
- {isTestTable && (
-
- TEST
-
- )}
- {isTestTable2 && (
-
- STAGE
-
- )}
+
+ {tableType.toUpperCase()}
+
- {isTestTable ? 'Tables' :
- isTestTable2 ? 'Stage Tables' :
- (data.isFact ? 'Fact Table' : 'Dimension Table')}
+ {data.columns && data.columns.length > 0 ?
+ `${data.columns.length} columns` :
+ tableType === 'fact' ? 'Fact Table' :
+ tableType === 'stage' ? 'Stage Table' :
+ 'Dimension Table'}
);
};
-// Mock data for demonstration
+// Generate data for InfiniteCanvas using mockApiData from DataflowCanvas
const generateMockData = () => {
+ // Create database
const databases = [
- { id: 'db4', name: 'Dbtez', schemas: 1, tables: 2 },
+ { id: 'db4', name: 'Dbtez', schemas: mockApiData.schemas.length, tables: mockApiData.tables.length },
];
- const schemas = [
- { id: 'schema10', dbId: 'db4', name: 'Sales', tables: 2 },
- ];
+ // Create schemas from mockApiData
+ const schemas = mockApiData.schemas.map((schema, index) => ({
+ id: `schema${index + 10}`,
+ dbId: 'db4',
+ name: schema.name,
+ slug: schema.slug,
+ tables: mockApiData.tables.filter(table => table.schema === schema.slug).length
+ }));
- const tables = [
- // Dbtez - Sales schema
- { id: 'table16', schemaId: 'schema10', name: 'test_table', isFact: true },
- { id: 'table17', schemaId: 'schema10', name: 'test_table2', isFact: false },
- ];
+ // Create tables from mockApiData
+ const tables = mockApiData.tables.map((table, index) => {
+ // Find the schema this table belongs to
+ const schemaSlug = table.schema;
+ const schemaObj = schemas.find(s => s.slug === schemaSlug);
+
+ return {
+ id: `table${index + 16}`,
+ schemaId: schemaObj.id,
+ name: table.name,
+ slug: table.slug,
+ isFact: table.type === 'fact',
+ type: table.type,
+ columns: table.columns
+ };
+ });
return { databases, schemas, tables };
};
@@ -384,9 +1626,382 @@ const InfiniteCanvas = () => {
const [connectionSource, setConnectionSource] = useState(null);
const [connectionType, setConnectionType] = useState('default'); // default, reference, dependency
- // Mock data
+ // State for entity creation modals
+ const [showAddEntityModal, setShowAddEntityModal] = useState(false);
+ const [activeEntityType, setActiveEntityType] = useState('database'); // 'database', 'schema', or 'table'
+ const [selectedDatabaseForSchema, setSelectedDatabaseForSchema] = useState(null);
+ const [selectedSchemaForTable, setSelectedSchemaForTable] = useState(null);
+
+ // State for storing created entities
+ const [databases, setDatabases] = useState([]);
+ const [schemas, setSchemas] = useState([]);
+ const [tables, setTables] = useState([]);
+
+ // Initialize with mock data
const mockData = generateMockData();
+ // Initialize with default Dbtez database if no databases exist
+ useEffect(() => {
+ if (databases.length === 0) {
+ setDatabases([{
+ id: 'db4',
+ name: 'Dbtez',
+ description: 'Default database for data exploration',
+ type: 'PostgreSQL',
+ schemas: mockData.schemas.length,
+ tables: mockData.tables.length
+ }]);
+ }
+ }, []);
+
+ // Handler for creating a new database with nested schemas and tables
+ const handleCreateDatabase = (formData) => {
+ // Create the database
+ const dbId = `db-${Date.now()}`;
+ const newDatabase = {
+ id: dbId,
+ name: formData.name,
+ description: formData.description,
+ url: formData.url,
+ key: formData.key,
+ type: formData.type,
+ schemas: formData.schemas.length,
+ tables: formData.schemas.reduce((count, schema) => count + schema.tables.length, 0)
+ };
+
+ setDatabases(prev => [...prev, newDatabase]);
+
+ // Add the new database node to the canvas
+ const dbNode = {
+ id: dbId,
+ type: 'database',
+ data: {
+ label: newDatabase.name,
+ schemas: newDatabase.schemas,
+ tables: newDatabase.tables,
+ expanded: false,
+ onToggle: toggleDatabaseExpansion
+ },
+ position: {
+ x: Math.random() * 300,
+ y: Math.random() * 300
+ },
+ };
+
+ const newNodes = [dbNode];
+ const newEdges = [];
+
+ // Process schemas
+ const createdSchemas = formData.schemas.map((schemaData, schemaIndex) => {
+ const schemaId = `schema-${Date.now()}-${schemaIndex}`;
+ const newSchema = {
+ id: schemaId,
+ dbId: dbId,
+ name: schemaData.name,
+ description: schemaData.description,
+ slug: schemaData.name.toLowerCase().replace(/\s+/g, '_'),
+ tables: schemaData.tables.length
+ };
+
+ // Create schema node (positioned relative to database)
+ const schemaNode = {
+ id: schemaId,
+ type: 'schema',
+ data: {
+ label: schemaData.name,
+ tables: schemaData.tables.length,
+ dbId: dbId,
+ expanded: false,
+ onToggle: toggleSchemaExpansion
+ },
+ position: {
+ x: dbNode.position.x + 300,
+ y: dbNode.position.y + (schemaIndex * 150)
+ },
+ };
+
+ newNodes.push(schemaNode);
+
+ // Create edge from database to schema
+ newEdges.push({
+ id: `e-${dbId}-${schemaId}`,
+ source: dbId,
+ target: schemaId,
+ type: 'custom',
+ animated: true,
+ style: { stroke: '#00a99d', strokeWidth: 2 },
+ markerEnd: {
+ type: 'arrowclosed',
+ width: 20,
+ height: 20,
+ color: '#00a99d',
+ },
+ data: {
+ label: 'contains'
+ }
+ });
+
+ // Process tables for this schema
+ const schemaTables = schemaData.tables.map((tableData, tableIndex) => {
+ const tableId = `table-${Date.now()}-${schemaIndex}-${tableIndex}`;
+ const newTable = {
+ id: tableId,
+ schemaId: schemaId,
+ name: tableData.name,
+ description: tableData.description,
+ slug: tableData.name.toLowerCase().replace(/\s+/g, '_'),
+ type: tableData.type,
+ isFact: tableData.type === 'fact',
+ columns: tableData.columns || []
+ };
+
+ // Create table node (positioned relative to schema)
+ const tableNode = {
+ id: tableId,
+ type: 'table',
+ data: {
+ label: tableData.name,
+ type: tableData.type,
+ isFact: tableData.type === 'fact',
+ schemaId: schemaId,
+ columns: tableData.columns || []
+ },
+ position: {
+ x: schemaNode.position.x + 300,
+ y: schemaNode.position.y + (tableIndex * 100)
+ },
+ };
+
+ newNodes.push(tableNode);
+
+ // Create edge from schema to table
+ newEdges.push({
+ id: `e-${schemaId}-${tableId}`,
+ source: schemaId,
+ target: tableId,
+ type: 'custom',
+ animated: true,
+ style: {
+ stroke: tableData.type === 'fact' ? '#fa8c16' :
+ tableData.type === 'stage' ? '#1890ff' : '#52c41a',
+ strokeWidth: 2
+ },
+ markerEnd: {
+ type: 'arrowclosed',
+ width: 20,
+ height: 20,
+ color: tableData.type === 'fact' ? '#fa8c16' :
+ tableData.type === 'stage' ? '#1890ff' : '#52c41a',
+ },
+ data: {
+ label: 'contains'
+ }
+ });
+
+ return newTable;
+ });
+
+ // Add tables to state
+ setTables(prev => [...prev, ...schemaTables]);
+
+ return newSchema;
+ });
+
+ // Add schemas to state
+ setSchemas(prev => [...prev, ...createdSchemas]);
+
+ // Add all nodes and edges to the canvas
+ setNodes(nodes => [...nodes, ...newNodes]);
+ setEdges(edges => [...edges, ...newEdges]);
+
+ // Auto-expand the database if it has schemas
+ if (formData.schemas.length > 0) {
+ setTimeout(() => {
+ toggleDatabaseExpansion(dbId);
+
+ // Auto-expand schemas if they have tables
+ formData.schemas.forEach((schema, index) => {
+ if (schema.tables.length > 0) {
+ const schemaId = `schema-${Date.now()}-${index}`;
+ toggleSchemaExpansion(schemaId);
+ }
+ });
+ }, 500);
+ }
+
+ setShowAddEntityModal(false);
+
+ // Fit view to show all the new nodes
+ setTimeout(() => {
+ fitView();
+ }, 1000);
+ };
+
+ // Handler for creating a new schema
+ const handleCreateSchema = (formData) => {
+ const parentDb = formData.parentDatabase;
+
+ const newSchema = {
+ id: `schema-${Date.now()}`,
+ dbId: parentDb.id,
+ name: formData.name,
+ description: formData.description,
+ slug: formData.name.toLowerCase().replace(/\s+/g, '_'),
+ tables: 0
+ };
+
+ setSchemas(prev => [...prev, newSchema]);
+
+ // Update the parent database's schema count
+ setDatabases(prev => prev.map(db =>
+ db.id === parentDb.id
+ ? { ...db, schemas: db.schemas + 1 }
+ : db
+ ));
+
+ // If the parent database is expanded, add the schema node to the canvas
+ if (expandedDatabases[parentDb.id]) {
+ const dbNode = nodes.find(n => n.id === parentDb.id);
+
+ const newNode = {
+ id: newSchema.id,
+ type: 'schema',
+ data: {
+ label: newSchema.name,
+ tables: newSchema.tables,
+ dbId: parentDb.id,
+ expanded: false,
+ onToggle: toggleSchemaExpansion
+ },
+ position: {
+ x: dbNode.position.x + 150,
+ y: dbNode.position.y + 150 + (schemas.filter(s => s.dbId === parentDb.id).length * 100)
+ },
+ };
+
+ setNodes(nodes => [...nodes, newNode]);
+
+ // Add an edge from the database to the schema
+ const newEdge = {
+ id: `e-${parentDb.id}-${newSchema.id}`,
+ source: parentDb.id,
+ target: newSchema.id,
+ type: 'custom',
+ animated: true,
+ style: { stroke: '#00a99d', strokeWidth: 2 },
+ markerEnd: {
+ type: 'arrowclosed',
+ width: 20,
+ height: 20,
+ color: '#00a99d',
+ },
+ data: {
+ label: 'contains'
+ }
+ };
+
+ setEdges(edges => [...edges, newEdge]);
+ }
+
+ setShowAddEntityModal(false);
+ setSelectedDatabaseForSchema(null);
+
+ // Fit view to show the new node
+ setTimeout(() => {
+ fitView();
+ }, 10);
+ };
+
+ // Handler for creating a new table
+ const handleCreateTable = (formData) => {
+ const parentSchema = formData.parentSchema;
+
+ const newTable = {
+ id: `table-${Date.now()}`,
+ schemaId: parentSchema.id,
+ name: formData.name,
+ description: formData.description,
+ slug: formData.name.toLowerCase().replace(/\s+/g, '_'),
+ type: formData.type,
+ isFact: formData.type === 'fact',
+ columns: []
+ };
+
+ setTables(prev => [...prev, newTable]);
+
+ // Update the parent schema's table count
+ setSchemas(prev => prev.map(schema =>
+ schema.id === parentSchema.id
+ ? { ...schema, tables: schema.tables + 1 }
+ : schema
+ ));
+
+ // Update the parent database's table count
+ const parentDb = databases.find(db => db.id === parentSchema.dbId);
+ if (parentDb) {
+ setDatabases(prev => prev.map(db =>
+ db.id === parentDb.id
+ ? { ...db, tables: db.tables + 1 }
+ : db
+ ));
+ }
+
+ // If the parent schema is expanded, add the table node to the canvas
+ if (expandedSchemas[parentSchema.id]) {
+ const schemaNode = nodes.find(n => n.id === parentSchema.id);
+
+ const newNode = {
+ id: newTable.id,
+ type: 'table',
+ data: {
+ label: newTable.name,
+ type: newTable.type,
+ isFact: newTable.isFact,
+ schemaId: parentSchema.id,
+ columns: newTable.columns
+ },
+ position: {
+ x: schemaNode.position.x + 150,
+ y: schemaNode.position.y + 150 + (tables.filter(t => t.schemaId === parentSchema.id).length * 100)
+ },
+ };
+
+ setNodes(nodes => [...nodes, newNode]);
+
+ // Add an edge from the schema to the table
+ const newEdge = {
+ id: `e-${parentSchema.id}-${newTable.id}`,
+ source: parentSchema.id,
+ target: newTable.id,
+ type: 'custom',
+ animated: true,
+ style: {
+ stroke: newTable.isFact ? '#fa8c16' : '#52c41a',
+ strokeWidth: 2
+ },
+ markerEnd: {
+ type: 'arrowclosed',
+ width: 20,
+ height: 20,
+ color: newTable.isFact ? '#fa8c16' : '#52c41a',
+ },
+ data: {
+ label: 'contains'
+ }
+ };
+
+ setEdges(edges => [...edges, newEdge]);
+ }
+
+ setShowAddEntityModal(false);
+ setSelectedSchemaForTable(null);
+
+ // Fit view to show the new node
+ setTimeout(() => {
+ fitView();
+ }, 10);
+ };
+
// Define node types (memoized to prevent unnecessary re-renders)
const nodeTypes = useMemo(() => ({
database: DatabaseNode,
@@ -470,14 +2085,17 @@ const InfiniteCanvas = () => {
const onInit = (instance) => {
setReactFlowInstance(instance);
- // Auto-expand Dbtez database and its schema on load
+ // Auto-expand Dbtez database and its schemas on load
setTimeout(() => {
// Expand the Dbtez database
toggleDatabaseExpansion('db4');
- // After expanding Dbtez, also expand its Sales schema
+ // After expanding Dbtez, also expand all its schemas
setTimeout(() => {
- toggleSchemaExpansion('schema10');
+ const schemas = mockData.schemas.filter(schema => schema.dbId === 'db4');
+ schemas.forEach(schema => {
+ toggleSchemaExpansion(schema.id);
+ });
// Fit view after all expansions
setTimeout(() => {
@@ -658,10 +2276,12 @@ const InfiniteCanvas = () => {
data: {
label: table.name,
isFact: table.isFact,
- schemaId: schemaId
+ type: table.type || (table.isFact ? 'fact' : 'dimension'),
+ schemaId: schemaId,
+ columns: table.columns || []
},
position: {
- x: schemaNode.position.x - 200 + (index * 100),
+ x: schemaNode.position.x - 200 + (index * 150),
y: schemaNode.position.y + 150
},
}));
@@ -704,11 +2324,22 @@ const InfiniteCanvas = () => {
// Show table details when a table is clicked
const showTableDetails = (tableId) => {
- const table = mockData.tables.find(t => t.id === tableId);
+ // Look for the table in both the state tables and mock data
+ const table = tables.find(t => t.id === tableId) || mockData.tables.find(t => t.id === tableId);
+ if (!table) return;
+
setSelectedTable(table);
- // In a real application, you would fetch the table details from your API
- alert(`Table Details: ${table.name}\nType: ${table.isFact ? 'Fact Table' : 'Dimension Table'}\n\nIn a real application, this would show a modal with columns, relationships, and metrics.`);
+ // Format the table type
+ const tableType = table.type || (table.isFact ? 'fact' : 'dimension');
+
+ // Format the columns list
+ const columnsText = table.columns && table.columns.length > 0
+ ? `Columns:\n${table.columns.join('\n')}`
+ : 'No columns defined';
+
+ // In a real application, you would show a modal with detailed information
+ alert(`Table Details: ${table.name}\nType: ${tableType.toUpperCase()}\n\n${columnsText}\n\nIn a real application, this would show a modal with columns, relationships, and metrics.`);
};
// Delete a custom connection
@@ -798,6 +2429,28 @@ const InfiniteCanvas = () => {
color="#333"
/>
+ {/* Add Button */}
+
+
+
+
{/* Minimal control panel */}
{
)}
+
+ {/* Entity Creation Modal */}
+ {
+ setShowAddEntityModal(false);
+ setActiveEntityType('database');
+ setSelectedDatabaseForSchema(null);
+ setSelectedSchemaForTable(null);
+ }}
+ >
+ {
+ setShowAddEntityModal(false);
+ setActiveEntityType('database');
+ setSelectedDatabaseForSchema(null);
+ setSelectedSchemaForTable(null);
+ }}
+ />
+
);
};