585 lines
20 KiB
JavaScript
585 lines
20 KiB
JavaScript
import { useState, useEffect } from 'react'
|
||
import './App.css'
|
||
import InfiniteCanvas from './components/InfiniteCanvas'
|
||
import AdvancedCharts from './components/AdvancedCharts'
|
||
import DataflowCanvas from './components/DataflowCanvas'
|
||
import ERDiagramCanvas from './components/ERDiagramCanvas'
|
||
import { FaDatabase, FaChartBar, FaProjectDiagram, FaSitemap, FaFolder, FaCog, FaChevronDown, FaChevronRight, FaTachometerAlt } from 'react-icons/fa'
|
||
import { Breadcrumbs, Link, Typography } from '@mui/material'
|
||
import { createTheme, ThemeProvider } from '@mui/material/styles'
|
||
|
||
// Create a custom Material UI theme to match your application's dark theme
|
||
const darkTheme = createTheme({
|
||
palette: {
|
||
mode: 'dark',
|
||
primary: {
|
||
main: '#00a99d',
|
||
},
|
||
text: {
|
||
primary: '#ffffff',
|
||
secondary: '#aaaaaa',
|
||
},
|
||
background: {
|
||
default: '#121212',
|
||
paper: '#1a1a1a',
|
||
},
|
||
},
|
||
typography: {
|
||
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
|
||
},
|
||
});
|
||
|
||
function App() {
|
||
// Initialize activeTab based on URL pathname if present
|
||
const getInitialTab = () => {
|
||
const pathname = window.location.pathname;
|
||
if (pathname === '/charts') return 'charts';
|
||
if (pathname === '/data_mappings') return 'data_mappigs';
|
||
if (pathname === '/er_diagram') return 'er_diagram';
|
||
if (pathname === '/settings') return 'settings';
|
||
// Default to canvas/qubit_service
|
||
window.history.pushState(null, '', '/qubit_service');
|
||
return 'canvas';
|
||
};
|
||
|
||
const [activeTab, setActiveTab] = useState(getInitialTab());
|
||
// Add state to track the current database for DataflowCanvas
|
||
const [currentDbSlug, setCurrentDbSlug] = useState(null);
|
||
// Add state to track if the current database has schemas
|
||
const [hasSchemas, setHasSchemas] = useState(true);
|
||
// Add state to track whether the sidebar dropdown is open
|
||
const [isDropdownOpen, setIsDropdownOpen] = useState(true);
|
||
|
||
// Handle browser back/forward navigation
|
||
useEffect(() => {
|
||
const handleLocationChange = () => {
|
||
const pathname = window.location.pathname;
|
||
if (pathname === '/qubit_service') setActiveTab('canvas');
|
||
else if (pathname === '/charts') setActiveTab('charts');
|
||
else if (pathname === '/data_mappings') setActiveTab('data_mappigs');
|
||
else if (pathname === '/er_diagram') setActiveTab('er_diagram');
|
||
// Settings tab would be handled here too
|
||
};
|
||
|
||
window.addEventListener('popstate', handleLocationChange);
|
||
return () => {
|
||
window.removeEventListener('popstate', handleLocationChange);
|
||
};
|
||
}, []);
|
||
|
||
// Listen for custom events to switch tabs
|
||
useEffect(() => {
|
||
// Handler for switching to DataFlow tab
|
||
const handleViewDataFlow = (event) => {
|
||
// Switch to the dataflow tab
|
||
setActiveTab('dataflow');
|
||
|
||
// Update URL path
|
||
window.history.pushState(null, '', '/data_mappings');
|
||
|
||
// Set the current database slug to force DataflowCanvas to re-render
|
||
setCurrentDbSlug(event.detail.databaseSlug);
|
||
|
||
// Set whether this database has schemas
|
||
setHasSchemas(!event.detail.isEmpty);
|
||
|
||
// Log the database info
|
||
console.log('Viewing DataFlow for database:', event.detail);
|
||
console.log('Database has schemas:', !event.detail.isEmpty);
|
||
};
|
||
|
||
// Handler for switching to any tab
|
||
const handleSwitchToTab = (event) => {
|
||
// Switch to the specified tab
|
||
setActiveTab(event.detail);
|
||
|
||
// Update URL path based on the tab
|
||
let path = '/qubit_service';
|
||
if (event.detail === 'charts') path = '/charts';
|
||
if (event.detail === 'data_mappigs') path = '/data_mappings';
|
||
if (event.detail === 'er_diagram') path = '/er_diagram';
|
||
if (event.detail === 'settings') path = '/settings';
|
||
|
||
window.history.pushState(null, '', path);
|
||
console.log('Switching to tab:', event.detail);
|
||
};
|
||
|
||
// Add event listeners
|
||
window.addEventListener('viewDataFlow', handleViewDataFlow);
|
||
window.addEventListener('switchToTab', handleSwitchToTab);
|
||
|
||
// Clean up
|
||
return () => {
|
||
window.removeEventListener('viewDataFlow', handleViewDataFlow);
|
||
window.removeEventListener('switchToTab', handleSwitchToTab);
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<ThemeProvider theme={darkTheme}>
|
||
<div className="app-container" style={{
|
||
width: '100vw',
|
||
height: '100vh',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
overflow: 'hidden',
|
||
background: '#121212', /* Dark background */
|
||
color: '#ffffff'
|
||
}}>
|
||
<header>
|
||
{/* Logo and Title */}
|
||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||
<div style={{
|
||
width: '40px',
|
||
height: '40px',
|
||
background: 'linear-gradient(45deg, #00a99d, #52c41a)',
|
||
borderRadius: '8px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
marginRight: '15px',
|
||
boxShadow: '0 0 10px rgba(82, 196, 26, 0.5)',
|
||
animation: 'pulse 2s infinite'
|
||
}}>
|
||
<FaDatabase style={{ fontSize: '20px', color: 'white' }} />
|
||
</div>
|
||
<h1 style={{
|
||
margin: 0,
|
||
fontSize: '1.5rem',
|
||
background: 'linear-gradient(90deg, #00a99d, #52c41a, #fa8c16)',
|
||
WebkitBackgroundClip: 'text',
|
||
WebkitTextFillColor: 'transparent',
|
||
textShadow: '0 0 20px rgba(82, 196, 26, 0.3)'
|
||
}}>
|
||
{/* The Metricverse
|
||
*/}
|
||
Qubit Service
|
||
</h1>
|
||
</div>
|
||
|
||
{/* User profile and controls */}
|
||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||
<div className="user-profile">
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="2"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
style={{ marginRight: '8px' }}
|
||
>
|
||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
<span>John Doe</span>
|
||
</div>
|
||
|
||
<div className="notification-button">
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="2"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
>
|
||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
|
||
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div style={{ flex: 1, display: 'flex', flexDirection: 'row', overflow: 'hidden' }}>
|
||
{/* Sidebar navigation with dropdown */}
|
||
<div className="sidebar-nav" style={{
|
||
width: '220px',
|
||
background: '#1a1a1a',
|
||
borderRight: '1px solid #333',
|
||
padding: '20px 0',
|
||
display: 'flex',
|
||
flexDirection: 'column'
|
||
}}>
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px', paddingLeft: '15px' }}>
|
||
{/* Main Qbit Item with Dropdown */}
|
||
<div
|
||
className="nav-button main-item"
|
||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '12px 15px',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer',
|
||
background: 'rgba(0, 169, 157, 0.15)',
|
||
transition: 'all 0.2s ease',
|
||
marginBottom: '5px'
|
||
}}
|
||
>
|
||
<FaFolder style={{
|
||
fontSize: '20px',
|
||
color: '#00a99d',
|
||
marginRight: '12px'
|
||
}} />
|
||
<span style={{
|
||
color: '#fff',
|
||
fontWeight: '600',
|
||
flex: 1
|
||
}}>
|
||
Qbit
|
||
</span>
|
||
{isDropdownOpen ?
|
||
<FaChevronDown style={{ color: '#aaa', fontSize: '14px' }} /> :
|
||
<FaChevronRight style={{ color: '#aaa', fontSize: '14px' }} />
|
||
}
|
||
</div>
|
||
|
||
{/* Dropdown items container with animation */}
|
||
<div
|
||
className="dropdown-container"
|
||
style={{
|
||
display: isDropdownOpen ? 'flex' : 'none',
|
||
flexDirection: 'column',
|
||
gap: '4px',
|
||
paddingLeft: '15px',
|
||
marginLeft: '10px',
|
||
borderLeft: '1px solid rgba(170, 170, 170, 0.2)',
|
||
transition: 'all 0.3s ease'
|
||
}}
|
||
>
|
||
{/* Overview Item */}
|
||
<div
|
||
className={`nav-button ${activeTab === 'canvas' ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setActiveTab('canvas');
|
||
window.history.pushState(null, '', '/qubit_service');
|
||
}}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '8px 15px',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer',
|
||
background: activeTab === 'canvas' ? 'rgba(0, 169, 157, 0.1)' : 'transparent',
|
||
transition: 'all 0.2s ease'
|
||
}}
|
||
>
|
||
<FaProjectDiagram style={{
|
||
fontSize: '16px',
|
||
color: activeTab === 'canvas' ? '#00a99d' : '#aaa',
|
||
marginRight: '12px'
|
||
}} />
|
||
<span style={{
|
||
color: activeTab === 'canvas' ? '#fff' : '#aaa',
|
||
fontWeight: activeTab === 'canvas' ? '500' : 'normal'
|
||
}}>
|
||
Overview
|
||
</span>
|
||
</div>
|
||
|
||
{/* ER Diagram Item */}
|
||
<div
|
||
className={`nav-button ${activeTab === 'er_diagram' ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setActiveTab('er_diagram');
|
||
window.history.pushState(null, '', '/er_diagram');
|
||
}}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '8px 15px',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer',
|
||
background: activeTab === 'er_diagram' ? 'rgba(0, 169, 157, 0.1)' : 'transparent',
|
||
transition: 'all 0.2s ease'
|
||
}}
|
||
>
|
||
<FaSitemap style={{
|
||
fontSize: '16px',
|
||
color: activeTab === 'er_diagram' ? '#00a99d' : '#aaa',
|
||
marginRight: '12px'
|
||
}} />
|
||
<span style={{
|
||
color: activeTab === 'er_diagram' ? '#fff' : '#aaa',
|
||
fontWeight: activeTab === 'er_diagram' ? '500' : 'normal'
|
||
}}>
|
||
ER Diagram
|
||
</span>
|
||
</div>
|
||
|
||
{/* Data Mapping Item */}
|
||
<div
|
||
className={`nav-button ${activeTab === 'dataflow' ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setActiveTab('dataflow');
|
||
window.history.pushState(null, '', '/data_mappings');
|
||
}}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '8px 15px',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer',
|
||
background: activeTab === 'dataflow' ? 'rgba(0, 169, 157, 0.1)' : 'transparent',
|
||
transition: 'all 0.2s ease'
|
||
}}
|
||
>
|
||
<FaDatabase style={{
|
||
fontSize: '16px',
|
||
color: activeTab === 'dataflow' ? '#00a99d' : '#aaa',
|
||
marginRight: '12px'
|
||
}} />
|
||
<span style={{
|
||
color: activeTab === 'dataflow' ? '#fff' : '#aaa',
|
||
fontWeight: activeTab === 'dataflow' ? '500' : 'normal'
|
||
}}>
|
||
Data Mapping
|
||
</span>
|
||
</div>
|
||
|
||
|
||
{/* Settings Item */}
|
||
<div
|
||
className={`nav-button ${activeTab === 'settings' ? 'active' : ''}`}
|
||
onClick={() => {
|
||
alert('Settings functionality not implemented yet');
|
||
}}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '8px 15px',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer',
|
||
background: activeTab === 'settings' ? 'rgba(0, 169, 157, 0.1)' : 'transparent',
|
||
transition: 'all 0.2s ease'
|
||
}}
|
||
>
|
||
<FaTachometerAlt
|
||
style={{
|
||
fontSize: '16px',
|
||
color: activeTab === 'settings' ? '#00a99d' : '#aaa',
|
||
marginRight: '12px'
|
||
}}
|
||
/>
|
||
<span style={{
|
||
color: activeTab === 'settings' ? '#fff' : '#aaa',
|
||
fontWeight: activeTab === 'settings' ? '500' : 'normal'
|
||
}}>
|
||
Dashboard
|
||
</span>
|
||
</div>
|
||
|
||
{/* Advanced Charts Item */}
|
||
{/* <div
|
||
className={`nav-button ${activeTab === 'charts' ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setActiveTab('charts');
|
||
window.history.pushState(null, '', '/charts');
|
||
}}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '8px 15px',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer',
|
||
background: activeTab === 'charts' ? 'rgba(0, 169, 157, 0.1)' : 'transparent',
|
||
transition: 'all 0.2s ease'
|
||
}}
|
||
>
|
||
<FaChartBar style={{
|
||
fontSize: '16px',
|
||
color: activeTab === 'charts' ? '#00a99d' : '#aaa',
|
||
marginRight: '12px'
|
||
}} />
|
||
<span style={{
|
||
color: activeTab === 'charts' ? '#fff' : '#aaa',
|
||
fontWeight: activeTab === 'charts' ? '500' : 'normal'
|
||
}}>
|
||
Advanced Charts
|
||
</span>
|
||
</div> */}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<main style={{
|
||
flex: 1,
|
||
overflow: 'hidden',
|
||
position: 'relative',
|
||
padding: '20px'
|
||
}}>
|
||
{activeTab === 'canvas' && (
|
||
<>
|
||
<div style={{
|
||
marginBottom: '20px'
|
||
}}>
|
||
<h1 style={{
|
||
fontSize: '28px',
|
||
fontWeight: '600',
|
||
margin: '0 0 8px 0',
|
||
color: '#ffffff'
|
||
}}>
|
||
Overview
|
||
</h1>
|
||
<Breadcrumbs
|
||
aria-label="breadcrumb"
|
||
separator="›"
|
||
sx={{
|
||
'& .MuiBreadcrumbs-separator': {
|
||
color: '#666',
|
||
margin: '0 8px',
|
||
},
|
||
fontSize: '14px',
|
||
}}
|
||
>
|
||
<Link
|
||
underline="hover"
|
||
color="primary"
|
||
href="#"
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
setIsDropdownOpen(true); // Ensure dropdown is open
|
||
}}
|
||
sx={{ fontWeight: 500 }}
|
||
>
|
||
Qubit
|
||
</Link>
|
||
<Typography color="text.secondary">Overview</Typography>
|
||
</Breadcrumbs>
|
||
</div>
|
||
<InfiniteCanvas />
|
||
<div style={{
|
||
position: 'absolute',
|
||
bottom: '20px',
|
||
left: '20px',
|
||
background: 'rgba(26, 26, 26, 0.8)',
|
||
padding: '12px 16px',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||
fontSize: '12px',
|
||
pointerEvents: 'none',
|
||
backdropFilter: 'blur(5px)',
|
||
border: '1px solid rgba(82, 196, 26, 0.3)',
|
||
color: '#ffffff',
|
||
maxWidth: '300px',
|
||
animation: 'fadeIn 0.5s ease'
|
||
}}>
|
||
<p style={{
|
||
margin: 0,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
fontSize: '13px'
|
||
}}>
|
||
<span style={{
|
||
display: 'inline-block',
|
||
width: '8px',
|
||
height: '8px',
|
||
background: '#52c41a',
|
||
borderRadius: '50%',
|
||
marginRight: '8px',
|
||
boxShadow: '0 0 8px #52c41a'
|
||
}}></span>
|
||
{/* Infinite Canvas - Zoom and pan without limits */}
|
||
The Only Limit is Your Imagination.
|
||
</p>
|
||
<p style={{
|
||
margin: '5px 0 0 16px',
|
||
fontSize: '11px',
|
||
opacity: 0.7
|
||
}}>
|
||
Scroll to zoom • Drag to pan • Connect nodes
|
||
</p>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{activeTab === 'charts' && (
|
||
<div style={{ padding: '20px', height: '100%', overflow: 'auto' }}>
|
||
{/* <AdvancedCharts /> */}
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === 'dataflow' && (
|
||
<>
|
||
<DataflowCanvas
|
||
key={`dataflow-${currentDbSlug || 'default'}-${hasSchemas ? 'has-schemas' : 'no-schemas'}-${Date.now()}`}
|
||
dbSlug={currentDbSlug}
|
||
hasSchemas={hasSchemas}
|
||
/>
|
||
<div style={{
|
||
position: 'absolute',
|
||
bottom: '20px',
|
||
left: '20px',
|
||
background: 'rgba(26, 26, 26, 0.8)',
|
||
padding: '12px 16px',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||
fontSize: '12px',
|
||
pointerEvents: 'none',
|
||
backdropFilter: 'blur(5px)',
|
||
border: '1px solid rgba(250, 140, 22, 0.3)',
|
||
color: '#ffffff',
|
||
maxWidth: '300px',
|
||
animation: 'fadeIn 0.5s ease'
|
||
}}>
|
||
<p style={{
|
||
margin: 0,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
fontSize: '13px'
|
||
}}>
|
||
<span style={{
|
||
display: 'inline-block',
|
||
width: '8px',
|
||
height: '8px',
|
||
background: '#fa8c16',
|
||
borderRadius: '50%',
|
||
marginRight: '8px',
|
||
boxShadow: '0 0 8px #fa8c16'
|
||
}}></span>
|
||
Visualize data flows between tables
|
||
</p>
|
||
<p style={{
|
||
margin: '5px 0 0 16px',
|
||
fontSize: '11px',
|
||
opacity: 0.7
|
||
}}>
|
||
Add tables • Create processes • Connect data flows
|
||
</p>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{activeTab === 'er_diagram' && (
|
||
<>
|
||
<div style={{
|
||
marginBottom: '20px'
|
||
}}>
|
||
<h1 style={{
|
||
fontSize: '28px',
|
||
fontWeight: '600',
|
||
margin: '0 0 8px 0',
|
||
color: '#ffffff'
|
||
}}>
|
||
ER Diagram
|
||
</h1>
|
||
</div>
|
||
<ERDiagramCanvas />
|
||
</>
|
||
)}
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</ThemeProvider>
|
||
)
|
||
}
|
||
|
||
export default App
|