Added the Provision for the extra db creation and added logic for the persistance of the newly created db #3
21
src/App.jsx
21
src/App.jsx
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import InfiniteCanvas from './components/InfiniteCanvas'
|
import InfiniteCanvas from './components/InfiniteCanvas'
|
||||||
import AdvancedCharts from './components/AdvancedCharts'
|
import AdvancedCharts from './components/AdvancedCharts'
|
||||||
|
|
@ -8,6 +8,25 @@ import { FaDatabase, FaChartBar, FaProjectDiagram } from 'react-icons/fa'
|
||||||
function App() {
|
function App() {
|
||||||
const [activeTab, setActiveTab] = useState('canvas'); // 'canvas', 'charts', or 'dataflow'
|
const [activeTab, setActiveTab] = useState('canvas'); // 'canvas', 'charts', or 'dataflow'
|
||||||
|
|
||||||
|
// Listen for the custom event to switch to DataFlow tab
|
||||||
|
useEffect(() => {
|
||||||
|
const handleViewDataFlow = (event) => {
|
||||||
|
// Switch to the dataflow tab
|
||||||
|
setActiveTab('dataflow');
|
||||||
|
|
||||||
|
// Log the database info (in a real app, you would use this to initialize the DataFlow view)
|
||||||
|
console.log('Viewing DataFlow for database:', event.detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
window.addEventListener('viewDataFlow', handleViewDataFlow);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('viewDataFlow', handleViewDataFlow);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-container" style={{
|
<div className="app-container" style={{
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
|
|
|
||||||
|
|
@ -643,6 +643,23 @@ const DataflowCanvas = () => {
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
// State for selected database
|
||||||
|
const [selectedDatabase, setSelectedDatabase] = useState(null);
|
||||||
|
|
||||||
|
// Read selected database from localStorage on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const dbData = localStorage.getItem('selectedDatabase');
|
||||||
|
if (dbData) {
|
||||||
|
const parsedData = JSON.parse(dbData);
|
||||||
|
setSelectedDatabase(parsedData);
|
||||||
|
console.log('DataFlow view initialized with database:', parsedData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading database from localStorage:', error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// State for connection mode
|
// State for connection mode
|
||||||
const [isConnectionMode, setIsConnectionMode] = useState(false);
|
const [isConnectionMode, setIsConnectionMode] = useState(false);
|
||||||
const [connectionSource, setConnectionSource] = useState(null);
|
const [connectionSource, setConnectionSource] = useState(null);
|
||||||
|
|
@ -1553,6 +1570,47 @@ const DataflowCanvas = () => {
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: '100%', background: '#ffffff' }} ref={reactFlowWrapper}>
|
<div style={{ width: '100%', height: '100%', background: '#ffffff' }} ref={reactFlowWrapper}>
|
||||||
<style>{customStyles}</style>
|
<style>{customStyles}</style>
|
||||||
|
|
||||||
|
{/* Database Header */}
|
||||||
|
{selectedDatabase && (
|
||||||
|
<div style={{
|
||||||
|
padding: '10px 15px',
|
||||||
|
background: 'linear-gradient(90deg, #00a99d, #52c41a)',
|
||||||
|
color: 'white',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)'
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 41 39" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="40.5" height="38.7" rx="3.6" fill="white" fillOpacity="0.2"/>
|
||||||
|
<path d="M19.8845 24.789C17.0714 24.789 14.2465 24.0672 12.912 22.5097C12.8725 22.683 12.8462 22.8607 12.8462 23.0493V25.8844C12.8462 28.3962 16.4937 29.5392 19.8845 29.5392C23.2753 29.5392 26.9228 28.3962 26.9228 25.8844V23.0493C26.9228 22.8607 26.8964 22.6837 26.8569 22.5104C25.5217 24.068 22.6976 24.7904 19.8837 24.7904L19.8845 24.789ZM19.8845 19.3083C17.0714 19.3083 14.2465 18.5865 12.9127 17.0289C12.8706 17.2053 12.8486 17.3858 12.8469 17.5671V20.4022C12.8469 22.9133 16.4937 24.0563 19.8845 24.0563C23.2753 24.0563 26.9228 22.9133 26.9228 20.4015V17.5657C26.9228 17.3778 26.8964 17.2001 26.8569 17.0268C25.5217 18.5843 22.6976 19.3068 19.8837 19.3068L19.8845 19.3083ZM19.8845 8.42944C16.4937 8.42944 12.8462 9.57385 12.8462 12.0857V14.9208C12.8462 17.4326 16.4937 18.5755 19.8845 18.5755C23.2753 18.5755 26.9228 17.4333 26.9228 14.9215V12.0864C26.9228 9.57458 23.2753 8.43017 19.8845 8.43017V8.42944ZM19.8845 14.2794C16.8059 14.2794 14.3087 13.2974 14.3087 12.0857C14.3087 10.8747 16.8052 9.89194 19.8845 9.89194C22.9638 9.89194 25.4603 10.8747 25.4603 12.0857C25.4603 13.2981 22.9638 14.2794 19.8845 14.2794Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<h2 style={{ margin: 0, fontSize: '18px' }}>{selectedDatabase.name} Database</h2>
|
||||||
|
<div style={{ fontSize: '12px', opacity: 0.9 }}>Data Flow View</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => window.history.back()}
|
||||||
|
style={{
|
||||||
|
background: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
border: 'none',
|
||||||
|
color: 'white',
|
||||||
|
padding: '5px 10px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Back to Explorer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
|
|
@ -1585,6 +1643,58 @@ const DataflowCanvas = () => {
|
||||||
connectionLineType="bezier"
|
connectionLineType="bezier"
|
||||||
defaultMarkerColor="#00a99d"
|
defaultMarkerColor="#00a99d"
|
||||||
>
|
>
|
||||||
|
{/* Empty Database Message */}
|
||||||
|
{nodes.length === 0 && selectedDatabase && (
|
||||||
|
<Panel position="center">
|
||||||
|
<div style={{
|
||||||
|
background: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
padding: '30px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||||
|
textAlign: 'center',
|
||||||
|
maxWidth: '500px'
|
||||||
|
}}>
|
||||||
|
<svg width="80" height="80" viewBox="0 0 41 39" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ margin: '0 auto 20px' }}>
|
||||||
|
<rect width="40.5" height="38.7" rx="3.6" fill="url(#db_paint0_linear)"/>
|
||||||
|
<path d="M19.8845 24.789C17.0714 24.789 14.2465 24.0672 12.912 22.5097C12.8725 22.683 12.8462 22.8607 12.8462 23.0493V25.8844C12.8462 28.3962 16.4937 29.5392 19.8845 29.5392C23.2753 29.5392 26.9228 28.3962 26.9228 25.8844V23.0493C26.9228 22.8607 26.8964 22.6837 26.8569 22.5104C25.5217 24.068 22.6976 24.7904 19.8837 24.7904L19.8845 24.789ZM19.8845 19.3083C17.0714 19.3083 14.2465 18.5865 12.9127 17.0289C12.8706 17.2053 12.8486 17.3858 12.8469 17.5671V20.4022C12.8469 22.9133 16.4937 24.0563 19.8845 24.0563C23.2753 24.0563 26.9228 22.9133 26.9228 20.4015V17.5657C26.9228 17.3778 26.8964 17.2001 26.8569 17.0268C25.5217 18.5843 22.6976 19.3068 19.8837 19.3068L19.8845 19.3083ZM19.8845 8.42944C16.4937 8.42944 12.8462 9.57385 12.8462 12.0857V14.9208C12.8462 17.4326 16.4937 18.5755 19.8845 18.5755C23.2753 18.5755 26.9228 17.4333 26.9228 14.9215V12.0864C26.9228 9.57458 23.2753 8.43017 19.8845 8.43017V8.42944ZM19.8845 14.2794C16.8059 14.2794 14.3087 13.2974 14.3087 12.0857C14.3087 10.8747 16.8052 9.89194 19.8845 9.89194C22.9638 9.89194 25.4603 10.8747 25.4603 12.0857C25.4603 13.2981 22.9638 14.2794 19.8845 14.2794Z" fill="white"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="db_paint0_linear" x1="40.5" y1="19.35" x2="0" y2="19.35" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stopColor="#006064"/>
|
||||||
|
<stop offset="0.711538" stopColor="#00A1A8"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
<h3 style={{ margin: '0 0 10px', color: '#333', fontSize: '20px' }}>
|
||||||
|
{selectedDatabase.name} Database is Empty
|
||||||
|
</h3>
|
||||||
|
<p style={{ color: '#666', marginBottom: '20px' }}>
|
||||||
|
This database doesn't have any tables or data flows yet. Start by adding tables and processes to visualize your data flow.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', gap: '10px' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowTablePopup(true)}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '5px',
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#52c41a',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaTable size={14} />
|
||||||
|
Add First Table
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Process Details Popup */}
|
{/* Process Details Popup */}
|
||||||
{showProcessPopup && selectedProcess && (
|
{showProcessPopup && selectedProcess && (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -383,186 +383,7 @@ const DatabaseForm = ({ onSave, onCancel }) => {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Schemas Section */}
|
{/* Schemas Section removed as per requirements */}
|
||||||
<div style={{ marginBottom: '16px' }}>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '10px'
|
|
||||||
}}>
|
|
||||||
<label style={{ fontWeight: '500', color: '#333' }}>
|
|
||||||
Schemas
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowSchemaForm(true)}
|
|
||||||
style={{
|
|
||||||
padding: '5px 10px',
|
|
||||||
backgroundColor: '#00a99d',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '12px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaPlus size={10} /> Add Schema
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{formData.schemas.length > 0 ? (
|
|
||||||
<div style={{
|
|
||||||
border: '1px solid #eee',
|
|
||||||
borderRadius: '4px',
|
|
||||||
maxHeight: '200px',
|
|
||||||
overflowY: 'auto'
|
|
||||||
}}>
|
|
||||||
{formData.schemas.map((schema, index) => (
|
|
||||||
<div
|
|
||||||
key={schema.id}
|
|
||||||
style={{
|
|
||||||
padding: '10px',
|
|
||||||
borderBottom: index < formData.schemas.length - 1 ? '1px solid #eee' : 'none',
|
|
||||||
backgroundColor: index % 2 === 0 ? '#f9f9f9' : 'white'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}>
|
|
||||||
<div>
|
|
||||||
<div style={{ fontWeight: '500' }}>{schema.name}</div>
|
|
||||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
|
||||||
{schema.tables.length} tables
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', gap: '5px' }}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentSchemaForTable(schema.id);
|
|
||||||
setShowTableForm(true);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
padding: '3px 8px',
|
|
||||||
backgroundColor: '#1890ff',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '12px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add Table
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentSchemaIndex(index);
|
|
||||||
setShowSchemaForm(true);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
padding: '3px 8px',
|
|
||||||
backgroundColor: '#52c41a',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '12px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => removeSchema(index)}
|
|
||||||
style={{
|
|
||||||
padding: '3px 8px',
|
|
||||||
backgroundColor: '#ff4d4f',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '12px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tables within this schema */}
|
|
||||||
{schema.tables.length > 0 && (
|
|
||||||
<div style={{
|
|
||||||
marginTop: '8px',
|
|
||||||
paddingTop: '8px',
|
|
||||||
borderTop: '1px dashed #eee',
|
|
||||||
paddingLeft: '15px'
|
|
||||||
}}>
|
|
||||||
{schema.tables.map((table, tableIndex) => (
|
|
||||||
<div
|
|
||||||
key={table.id}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '5px 0',
|
|
||||||
borderBottom: tableIndex < schema.tables.length - 1 ? '1px dotted #f0f0f0' : 'none'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span style={{ fontSize: '13px' }}>{table.name}</span>
|
|
||||||
<span style={{
|
|
||||||
fontSize: '11px',
|
|
||||||
backgroundColor: table.type === 'fact' ? '#fff2e8' :
|
|
||||||
table.type === 'stage' ? '#e6f7ff' : '#f6ffed',
|
|
||||||
color: table.type === 'fact' ? '#fa8c16' :
|
|
||||||
table.type === 'stage' ? '#1890ff' : '#52c41a',
|
|
||||||
padding: '1px 6px',
|
|
||||||
borderRadius: '10px',
|
|
||||||
marginLeft: '8px'
|
|
||||||
}}>
|
|
||||||
{table.type}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => removeTable(index, tableIndex)}
|
|
||||||
style={{
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
color: '#ff4d4f',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '12px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{
|
|
||||||
padding: '20px',
|
|
||||||
textAlign: 'center',
|
|
||||||
border: '1px dashed #ddd',
|
|
||||||
borderRadius: '4px',
|
|
||||||
color: '#999',
|
|
||||||
backgroundColor: '#fafafa'
|
|
||||||
}}>
|
|
||||||
No schemas added yet. Click "Add Schema" to create one.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '24px' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '24px' }}>
|
||||||
<button
|
<button
|
||||||
|
|
@ -722,7 +543,7 @@ const EntitySelector = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Entity Type Selector */}
|
{/* Entity Type Selector - Only Database */}
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
@ -741,8 +562,8 @@ const EntitySelector = ({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
background: activeEntityType === 'database' ? '#00a99d' : '#f5f5f5',
|
background: '#00a99d',
|
||||||
color: activeEntityType === 'database' ? 'white' : '#333',
|
color: 'white',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
|
@ -763,184 +584,15 @@ const EntitySelector = ({
|
||||||
</svg>
|
</svg>
|
||||||
<span>Database</span>
|
<span>Database</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setActiveEntityType('schema');
|
|
||||||
setSelectedSchemaForTable(null);
|
|
||||||
}}
|
|
||||||
disabled={availableDatabases.length === 0}
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
padding: '12px',
|
|
||||||
border: 'none',
|
|
||||||
background: activeEntityType === 'schema' ? '#00a99d' : '#f5f5f5',
|
|
||||||
color: activeEntityType === 'schema' ? 'white' : availableDatabases.length === 0 ? '#aaa' : '#333',
|
|
||||||
cursor: availableDatabases.length === 0 ? 'not-allowed' : 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px',
|
|
||||||
transition: 'all 0.2s'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg width="20" height="20" viewBox="0 0 42 39" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="0.6875" y="0.136963" width="40.95" height="38.4259" rx="3.6" fill="url(#schema_paint0_linear)"/>
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M35.28 25.3874H32.8947C32.7256 23.869 32.0612 22.4376 30.9499 21.2794C29.4071 19.6721 27.226 18.7868 24.8092 18.7868H21.7639V11.5874H27.2722C27.572 11.5874 27.8149 11.3441 27.8149 11.0447V6.95504C27.8149 6.65523 27.572 6.41235 27.2722 6.41235H15.2479C14.9478 6.41235 14.7049 6.65523 14.7049 6.95504V11.0447C14.7049 11.3441 14.9478 11.5874 15.2479 11.5874H20.7469V18.7868H17.6905C13.3466 18.7868 10.1185 21.5175 9.63615 25.4032C7.87769 25.5619 6.49976 27.0378 6.49976 28.8374C6.49976 30.7424 8.04467 32.2874 9.94976 32.2874C11.8552 32.2874 13.3998 30.7424 13.3998 28.8374C13.3998 27.1713 12.2188 25.7817 10.6487 25.4584C11.0914 22.0881 13.8538 19.8039 17.6905 19.8039H20.7469V25.3874H17.2251C17.0347 25.3874 16.9226 25.6088 17.0309 25.771L21.0657 32.1814C21.1595 32.3225 21.3603 32.3225 21.4545 32.1814L25.4892 25.771C25.5976 25.6088 25.4851 25.3874 25.295 25.3874H21.7639V19.8039H24.8092C26.9469 19.8039 28.8671 20.5781 30.2157 21.9836C31.1428 22.9492 31.7079 24.1322 31.8728 25.3874H29.4695C29.1687 25.3874 28.9248 25.6313 28.9248 25.9321V31.7426C28.9248 32.0434 29.1687 32.2874 29.4695 32.2874H35.28C35.5808 32.2874 35.8248 32.0434 35.8248 31.7426V25.9321C35.8248 25.6313 35.5808 25.3874 35.28 25.3874Z" fill="white"/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="schema_paint0_linear" x1="0.6875" y1="19.3499" x2="41.6375" y2="19.3499" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stopColor="#FF9D2C"/>
|
|
||||||
<stop offset="1" stopColor="#CD750F"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<span>Schema</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveEntityType('table')}
|
|
||||||
disabled={availableSchemas.length === 0}
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
padding: '12px',
|
|
||||||
border: 'none',
|
|
||||||
background: activeEntityType === 'table' ? '#00a99d' : '#f5f5f5',
|
|
||||||
color: activeEntityType === 'table' ? 'white' : availableSchemas.length === 0 ? '#aaa' : '#333',
|
|
||||||
cursor: availableSchemas.length === 0 ? 'not-allowed' : 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px',
|
|
||||||
transition: 'all 0.2s'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg width="20" height="20" viewBox="0 0 42 39" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="0.825195" width="40.5" height="38.7" rx="3.6" fill="url(#table_paint0_linear)"/>
|
|
||||||
<path d="M17.1504 20.7083H24.9896V25.2531H17.1504V20.7083ZM17.1504 31.0499H24.9896V26.5051H17.1504V31.0499ZM17.1504 19.4563H24.9896V14.9053H17.1504V19.4563ZM26.0349 19.4563H33.8741V14.9053H26.0349V19.4563ZM8.26592 19.4563H16.1052V14.9053H8.26592V19.4563ZM8.26592 25.2531H16.1052V20.7083H8.26592V25.2531ZM26.061 25.2531H33.9002V20.7083H26.061V25.2531ZM32.3324 7.6499H9.81809C9.40227 7.6499 9.00348 7.84776 8.70946 8.19996C8.41543 8.55215 8.25024 9.02983 8.25024 9.52791V13.6533H16.5494C16.5754 13.6497 16.6017 13.6497 16.6278 13.6533H16.7062H25.4443H25.5227C25.5488 13.6497 25.5751 13.6497 25.6011 13.6533H33.9002V9.52791C33.9002 9.02983 33.7351 8.55215 33.441 8.19996C33.147 7.84776 32.7482 7.6499 32.3324 7.6499ZM8.25024 29.1719C8.25024 29.67 8.41543 30.1477 8.70946 30.4998C8.85504 30.6742 9.02788 30.8126 9.2181 30.9069C9.40832 31.0013 9.6122 31.0499 9.81809 31.0499H16.0895V26.5051H8.25024V29.1719ZM26.0453 31.0499H32.3167C32.7325 31.0499 33.1313 30.852 33.4254 30.4998C33.7194 30.1477 33.8846 29.67 33.8846 29.1719V26.5051H26.0453V31.0499Z" fill="white"/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="table_paint0_linear" x1="41.3252" y1="19.35" x2="0.825195" y2="19.35" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stopColor="#659667"/>
|
|
||||||
<stop offset="1" stopColor="#81C784"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<span>Table</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Parent Selector (for schema and table) */}
|
{/* No parent selectors needed since we only have database */}
|
||||||
{activeEntityType === 'schema' && availableDatabases.length > 0 && (
|
|
||||||
<div style={{ marginBottom: '20px' }}>
|
|
||||||
<label style={{
|
|
||||||
display: 'block',
|
|
||||||
marginBottom: '6px',
|
|
||||||
fontWeight: '500',
|
|
||||||
color: '#333'
|
|
||||||
}}>
|
|
||||||
Select Database
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={selectedDatabaseForSchema || ''}
|
|
||||||
onChange={(e) => setSelectedDatabaseForSchema(e.target.value)}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
fontSize: '14px',
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="" disabled>Select a database</option>
|
|
||||||
{availableDatabases.map(db => (
|
|
||||||
<option key={db.id} value={db.id}>{db.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEntityType === 'table' && availableSchemas.length > 0 && (
|
{/* Only Database Form */}
|
||||||
<div style={{ marginBottom: '20px' }}>
|
|
||||||
<label style={{
|
|
||||||
display: 'block',
|
|
||||||
marginBottom: '6px',
|
|
||||||
fontWeight: '500',
|
|
||||||
color: '#333'
|
|
||||||
}}>
|
|
||||||
Select Schema
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={selectedSchemaForTable || ''}
|
|
||||||
onChange={(e) => setSelectedSchemaForTable(e.target.value)}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
fontSize: '14px',
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="" disabled>Select a schema</option>
|
|
||||||
{availableSchemas.map(schema => (
|
|
||||||
<option key={schema.id} value={schema.id}>{schema.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Form based on selected entity type */}
|
|
||||||
{activeEntityType === 'database' && (
|
|
||||||
<DatabaseForm
|
<DatabaseForm
|
||||||
onSave={onSaveDatabase}
|
onSave={onSaveDatabase}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEntityType === 'schema' && selectedDatabaseForSchema && (
|
|
||||||
<SchemaForm
|
|
||||||
onSave={onSaveSchema}
|
|
||||||
onCancel={onCancel}
|
|
||||||
parentDatabase={selectedDatabaseForSchema}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEntityType === 'table' && selectedSchemaForTable && (
|
|
||||||
<TableForm
|
|
||||||
onSave={onSaveTable}
|
|
||||||
onCancel={onCancel}
|
|
||||||
parentSchema={selectedSchemaForTable}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Guidance message when no parent is selected */}
|
|
||||||
{activeEntityType === 'schema' && !selectedDatabaseForSchema && availableDatabases.length > 0 && (
|
|
||||||
<div style={{
|
|
||||||
padding: '30px',
|
|
||||||
textAlign: 'center',
|
|
||||||
border: '1px dashed #ddd',
|
|
||||||
borderRadius: '4px',
|
|
||||||
color: '#666',
|
|
||||||
backgroundColor: '#fafafa'
|
|
||||||
}}>
|
|
||||||
Please select a database to create a schema.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEntityType === 'table' && !selectedSchemaForTable && availableSchemas.length > 0 && (
|
|
||||||
<div style={{
|
|
||||||
padding: '30px',
|
|
||||||
textAlign: 'center',
|
|
||||||
border: '1px dashed #ddd',
|
|
||||||
borderRadius: '4px',
|
|
||||||
color: '#666',
|
|
||||||
backgroundColor: '#fafafa'
|
|
||||||
}}>
|
|
||||||
Please select a schema to create a table.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Guidance message when no parents are available */}
|
{/* Guidance message when no parents are available */}
|
||||||
{activeEntityType === 'schema' && availableDatabases.length === 0 && (
|
{activeEntityType === 'schema' && availableDatabases.length === 0 && (
|
||||||
|
|
@ -1366,9 +1018,43 @@ const DatabaseNode = ({ data }) => {
|
||||||
{data.label}
|
{data.label}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ fontSize: '0.8em', color: '#aaa' }}>
|
<div style={{ fontSize: '0.8em', color: '#aaa', marginBottom: '10px' }}>
|
||||||
{isDbtez ? 'Database' : `${data.schemas} schemas • ${data.tables} tables`}
|
{isDbtez ? 'Database' : `${data.schemas} schemas • ${data.tables} tables`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* View Details Button */}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<button
|
||||||
|
onClick={(event) => {
|
||||||
|
// Stop propagation to prevent the node click handler from being triggered
|
||||||
|
event.stopPropagation();
|
||||||
|
// Call the viewDetails function passed in data
|
||||||
|
if (data.onViewDetails) {
|
||||||
|
data.onViewDetails(data.id, data.label);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: '4px 8px',
|
||||||
|
backgroundColor: isDbtez ? '#00a99d' : '#1890ff',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '3px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '11px',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '5px',
|
||||||
|
transition: 'all 0.2s'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 4.5C7 4.5 2.73 7.61 1 12C2.73 16.39 7 19.5 12 19.5C17 19.5 21.27 16.39 23 12C21.27 7.61 17 4.5 12 4.5ZM12 17C9.24 17 7 14.76 7 12C7 9.24 9.24 7 12 7C14.76 7 17 9.24 17 12C17 14.76 14.76 17 12 17ZM12 9C10.34 9 9 10.34 9 12C9 13.66 10.34 15 12 15C13.66 15 15 13.66 15 12C15 10.34 13.66 9 12 9Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
View Data Flow
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -1632,27 +1318,70 @@ const InfiniteCanvas = () => {
|
||||||
const [selectedDatabaseForSchema, setSelectedDatabaseForSchema] = useState(null);
|
const [selectedDatabaseForSchema, setSelectedDatabaseForSchema] = useState(null);
|
||||||
const [selectedSchemaForTable, setSelectedSchemaForTable] = 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
|
// Initialize with mock data
|
||||||
const mockData = generateMockData();
|
const mockData = generateMockData();
|
||||||
|
|
||||||
// Initialize with default Dbtez database if no databases exist
|
// State for storing created entities
|
||||||
useEffect(() => {
|
const [databases, setDatabases] = useState(() => {
|
||||||
if (databases.length === 0) {
|
// Try to load databases from localStorage
|
||||||
setDatabases([{
|
try {
|
||||||
|
const savedDatabases = localStorage.getItem('databases');
|
||||||
|
if (savedDatabases) {
|
||||||
|
return JSON.parse(savedDatabases);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading databases from localStorage:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no saved databases, return default Dbtez database
|
||||||
|
return [{
|
||||||
id: 'db4',
|
id: 'db4',
|
||||||
name: 'Dbtez',
|
name: 'Dbtez',
|
||||||
description: 'Default database for data exploration',
|
description: 'Default database for data exploration',
|
||||||
type: 'PostgreSQL',
|
type: 'PostgreSQL',
|
||||||
schemas: mockData.schemas.length,
|
schemas: mockData.schemas.length,
|
||||||
tables: mockData.tables.length
|
tables: mockData.tables.length
|
||||||
}]);
|
}];
|
||||||
|
});
|
||||||
|
|
||||||
|
const [schemas, setSchemas] = useState(() => {
|
||||||
|
// Try to load schemas from localStorage
|
||||||
|
try {
|
||||||
|
const savedSchemas = localStorage.getItem('schemas');
|
||||||
|
if (savedSchemas) {
|
||||||
|
return JSON.parse(savedSchemas);
|
||||||
}
|
}
|
||||||
}, []);
|
} catch (error) {
|
||||||
|
console.error('Error loading schemas from localStorage:', error);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const [tables, setTables] = useState(() => {
|
||||||
|
// Try to load tables from localStorage
|
||||||
|
try {
|
||||||
|
const savedTables = localStorage.getItem('tables');
|
||||||
|
if (savedTables) {
|
||||||
|
return JSON.parse(savedTables);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading tables from localStorage:', error);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save databases, schemas, and tables to localStorage whenever they change
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('databases', JSON.stringify(databases));
|
||||||
|
}, [databases]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('schemas', JSON.stringify(schemas));
|
||||||
|
}, [schemas]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('tables', JSON.stringify(tables));
|
||||||
|
}, [tables]);
|
||||||
|
|
||||||
// Handler for creating a new database with nested schemas and tables
|
// Handler for creating a new database with nested schemas and tables
|
||||||
const handleCreateDatabase = (formData) => {
|
const handleCreateDatabase = (formData) => {
|
||||||
|
|
@ -1680,7 +1409,8 @@ const InfiniteCanvas = () => {
|
||||||
schemas: newDatabase.schemas,
|
schemas: newDatabase.schemas,
|
||||||
tables: newDatabase.tables,
|
tables: newDatabase.tables,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
onToggle: toggleDatabaseExpansion
|
onToggle: toggleDatabaseExpansion,
|
||||||
|
onViewDetails: handleViewDataFlow // Add the function to handle redirection
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: Math.random() * 300,
|
x: Math.random() * 300,
|
||||||
|
|
@ -2014,16 +1744,38 @@ const InfiniteCanvas = () => {
|
||||||
custom: CustomEdge,
|
custom: CustomEdge,
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
// Initialize with database nodes only
|
// Function to handle redirection to DataFlow view
|
||||||
const initialNodes = mockData.databases.map((db, index) => ({
|
const handleViewDataFlow = (dbId, dbName) => {
|
||||||
|
// In a real application with proper routing, you would use a router to navigate
|
||||||
|
// Since we're using a tab-based navigation in App.jsx, we need to communicate with the parent
|
||||||
|
|
||||||
|
// Store the selected database info in localStorage for the DataFlow component to use
|
||||||
|
localStorage.setItem('selectedDatabase', JSON.stringify({
|
||||||
|
id: dbId,
|
||||||
|
name: dbName
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Trigger an event that App.jsx can listen to
|
||||||
|
const event = new CustomEvent('viewDataFlow', {
|
||||||
|
detail: { databaseId: dbId, databaseName: dbName }
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Alert the user about the redirection (in a real app, this would be a smooth transition)
|
||||||
|
alert(`Redirecting to Data Flow view for database: ${dbName}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize with database nodes from state instead of mockData
|
||||||
|
const initialNodes = databases.map((db, index) => ({
|
||||||
id: db.id,
|
id: db.id,
|
||||||
type: 'database',
|
type: 'database',
|
||||||
data: {
|
data: {
|
||||||
label: db.name,
|
label: db.name,
|
||||||
schemas: db.schemas,
|
schemas: db.schemas || 0,
|
||||||
tables: db.tables,
|
tables: db.tables || 0,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
onToggle: (id) => toggleDatabaseExpansion(id)
|
onToggle: (id) => toggleDatabaseExpansion(id),
|
||||||
|
onViewDetails: handleViewDataFlow // Add the function to handle redirection
|
||||||
},
|
},
|
||||||
position: { x: 250 * index, y: 50 },
|
position: { x: 250 * index, y: 50 },
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue