Orientation capturing and sidebar
This commit is contained in:
parent
f242d8661d
commit
6c0ec02f85
78
src/App.css
78
src/App.css
|
|
@ -394,6 +394,84 @@ main {
|
|||
width: 80%;
|
||||
}
|
||||
|
||||
/* Secondary navigation bar styles */
|
||||
.secondary-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background: rgba(18, 18, 18, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Navigation button styles */
|
||||
.nav-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-right: 8px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
background: rgba(0, 169, 157, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.nav-button.active {
|
||||
background: rgba(0, 169, 157, 0.15);
|
||||
border: 1px solid rgba(0, 169, 157, 0.3);
|
||||
}
|
||||
|
||||
.nav-button.active span {
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-button svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* User profile button styles */
|
||||
.user-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.user-profile:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.notification-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.notification-button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Glow effect for active elements */
|
||||
.glow-effect {
|
||||
box-shadow: 0 0 15px rgba(82, 196, 26, 0.6);
|
||||
|
|
|
|||
505
src/App.jsx
505
src/App.jsx
|
|
@ -3,7 +3,7 @@ import './App.css'
|
|||
import InfiniteCanvas from './components/InfiniteCanvas'
|
||||
import AdvancedCharts from './components/AdvancedCharts'
|
||||
import DataflowCanvas from './components/DataflowCanvas'
|
||||
import { FaDatabase, FaChartBar, FaProjectDiagram } from 'react-icons/fa'
|
||||
import { FaDatabase, FaChartBar, FaProjectDiagram, FaSitemap, FaFolder, FaCog, FaChevronDown, FaChevronRight, FaTachometerAlt } from 'react-icons/fa'
|
||||
|
||||
function App() {
|
||||
// Initialize activeTab based on URL pathname if present
|
||||
|
|
@ -11,6 +11,7 @@ function App() {
|
|||
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');
|
||||
|
|
@ -22,6 +23,8 @@ function App() {
|
|||
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(() => {
|
||||
|
|
@ -30,6 +33,7 @@ function App() {
|
|||
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
|
||||
};
|
||||
|
||||
|
|
@ -69,6 +73,7 @@ function App() {
|
|||
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);
|
||||
|
|
@ -121,202 +126,270 @@ function App() {
|
|||
WebkitTextFillColor: 'transparent',
|
||||
textShadow: '0 0 20px rgba(82, 196, 26, 0.3)'
|
||||
}}>
|
||||
The Metricverse
|
||||
{/* The Metricverse
|
||||
*/}
|
||||
Qubit Service
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div>
|
||||
<a
|
||||
href="/qubit_service"
|
||||
style={{
|
||||
marginRight: '25px',
|
||||
cursor: 'pointer',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '4px',
|
||||
transition: 'all 0.3s ease',
|
||||
position: 'relative',
|
||||
background: activeTab === 'canvas' ? 'rgba(0, 169, 157, 0.2)' : 'transparent',
|
||||
color: activeTab === 'canvas' ? '#00a99d' : '#ffffff',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
className="nav-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setActiveTab('canvas');
|
||||
window.history.pushState(null, '', '/qubit_service');
|
||||
}}
|
||||
>
|
||||
Overview
|
||||
</a>
|
||||
<a
|
||||
href="/charts"
|
||||
style={{
|
||||
marginRight: '25px',
|
||||
cursor: 'pointer',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '4px',
|
||||
transition: 'all 0.3s ease',
|
||||
position: 'relative',
|
||||
background: activeTab === 'charts' ? 'rgba(0, 169, 157, 0.2)' : 'transparent',
|
||||
color: activeTab === 'charts' ? '#00a99d' : '#ffffff',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
className="nav-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setActiveTab('charts');
|
||||
window.history.pushState(null, '', '/charts');
|
||||
}}
|
||||
>
|
||||
Advanced Charts
|
||||
</a>
|
||||
<a
|
||||
href="/data_mappings"
|
||||
style={{
|
||||
marginRight: '25px',
|
||||
cursor: 'pointer',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '4px',
|
||||
transition: 'all 0.3s ease',
|
||||
position: 'relative',
|
||||
background: activeTab === 'dataflow' ? 'rgba(0, 169, 157, 0.2)' : 'transparent',
|
||||
color: activeTab === 'dataflow' ? '#00a99d' : '#ffffff',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
className="nav-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setActiveTab('dataflow');
|
||||
window.history.pushState(null, '', '/data_mappings');
|
||||
}}
|
||||
>
|
||||
Data Mapping
|
||||
</a>
|
||||
<a
|
||||
href="/settings"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '4px',
|
||||
transition: 'all 0.3s ease',
|
||||
position: 'relative',
|
||||
textDecoration: 'none',
|
||||
color: '#ffffff'
|
||||
}}
|
||||
className="nav-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
// Settings functionality would go here
|
||||
alert('Settings functionality not implemented yet');
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</a>
|
||||
{/* 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', overflow: 'hidden' }}>
|
||||
<aside>
|
||||
<h3 style={{
|
||||
color: '#52c41a',
|
||||
marginBottom: '15px',
|
||||
borderBottom: '1px solid #333',
|
||||
paddingBottom: '10px',
|
||||
fontSize: '1.1rem'
|
||||
}}>Data Sources</h3>
|
||||
|
||||
<ul style={{ listStyle: 'none', padding: 0 }}>
|
||||
<li style={{
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
color: '#ffffff',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '6px',
|
||||
background: 'rgba(0, 169, 157, 0.1)',
|
||||
marginBottom: '8px',
|
||||
border: '1px solid rgba(0, 169, 157, 0.3)',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 0 10px rgba(0, 169, 157, 0.1)'
|
||||
}} className="glow-effect">
|
||||
<span style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
background: 'linear-gradient(45deg, #00a99d, #52c41a)',
|
||||
borderRadius: '4px',
|
||||
color: 'white',
|
||||
marginRight: '10px',
|
||||
fontSize: '12px'
|
||||
}}>
|
||||
<FaDatabase />
|
||||
</span>
|
||||
Dbtez
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 style={{
|
||||
color: '#fa8c16',
|
||||
marginTop: '25px',
|
||||
marginBottom: '15px',
|
||||
borderBottom: '1px solid #333',
|
||||
paddingBottom: '10px',
|
||||
fontSize: '1.1rem'
|
||||
}}>Saved Views</h3>
|
||||
|
||||
<ul style={{ listStyle: 'none', padding: 0 }}>
|
||||
<li style={{
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '6px',
|
||||
transition: 'all 0.3s ease',
|
||||
marginBottom: '8px',
|
||||
background: 'rgba(255, 255, 255, 0.03)'
|
||||
}}>
|
||||
<span style={{
|
||||
color: '#fa8c16',
|
||||
marginRight: '8px',
|
||||
fontSize: '16px'
|
||||
}}>📊</span>
|
||||
Sales Overview
|
||||
</li>
|
||||
<li style={{
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '6px',
|
||||
transition: 'all 0.3s ease',
|
||||
marginBottom: '8px',
|
||||
background: 'rgba(255, 255, 255, 0.03)'
|
||||
}}>
|
||||
<span style={{
|
||||
color: '#fa8c16',
|
||||
marginRight: '8px',
|
||||
fontSize: '16px'
|
||||
}}>📊</span>
|
||||
Marketing Performance
|
||||
</li>
|
||||
<li style={{
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '6px',
|
||||
transition: 'all 0.3s ease',
|
||||
background: 'rgba(255, 255, 255, 0.03)'
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
color: '#fa8c16',
|
||||
marginRight: '8px',
|
||||
fontSize: '16px'
|
||||
<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'
|
||||
}}
|
||||
>📊</span>
|
||||
Financial Summary
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
>
|
||||
<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' }}>
|
||||
<main style={{
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
padding: '20px'
|
||||
}}>
|
||||
{activeTab === 'canvas' && (
|
||||
<>
|
||||
<InfiniteCanvas />
|
||||
|
|
@ -367,7 +440,7 @@ function App() {
|
|||
|
||||
{activeTab === 'charts' && (
|
||||
<div style={{ padding: '20px', height: '100%', overflow: 'auto' }}>
|
||||
<AdvancedCharts />
|
||||
{/* <AdvancedCharts /> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -421,6 +494,58 @@ function App() {
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'er_diagram' && (
|
||||
<>
|
||||
<DataflowCanvas
|
||||
key={`er-diagram-${currentDbSlug || 'default'}-${hasSchemas ? 'has-schemas' : 'no-schemas'}-${Date.now()}`}
|
||||
dbSlug={currentDbSlug}
|
||||
hasSchemas={hasSchemas}
|
||||
mode="er_diagram"
|
||||
/>
|
||||
<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(138, 43, 226, 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: '#8a2be2',
|
||||
borderRadius: '50%',
|
||||
marginRight: '8px',
|
||||
boxShadow: '0 0 8px #8a2be2'
|
||||
}}></span>
|
||||
Entity Relationship Diagram
|
||||
</p>
|
||||
<p style={{
|
||||
margin: '5px 0 0 16px',
|
||||
fontSize: '11px',
|
||||
opacity: 0.7
|
||||
}}>
|
||||
View table relationships • Explore schema structure • Analyze foreign keys
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1802,9 +1802,29 @@ const DataflowCanvas = ({ dbSlug, hasSchemas = true }) => {
|
|||
type: table.type,
|
||||
columns: table.columns,
|
||||
slug: table.slug,
|
||||
schema: table.schema // Include schema information
|
||||
schema: table.schema, // Include schema information
|
||||
database: dbSlug // Include database information
|
||||
},
|
||||
position: { x: table.orientation.x, y: table.orientation.y },
|
||||
position: (table.config && table.config.orientation) ? table.config.orientation :
|
||||
(table.orientation || {
|
||||
// Try to get stored position from localStorage
|
||||
...((() => {
|
||||
try {
|
||||
const storageKey = `table_position_${dbSlug}_${table.schema}_${table.slug}`;
|
||||
const storedPosition = localStorage.getItem(storageKey);
|
||||
if (storedPosition) {
|
||||
return JSON.parse(storedPosition);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error retrieving stored position:', e);
|
||||
}
|
||||
// Default random position if nothing is stored
|
||||
return {
|
||||
x: Math.floor(Math.random() * 700) + 100,
|
||||
y: Math.floor(Math.random() * 700) + 100
|
||||
};
|
||||
})())
|
||||
}),
|
||||
parentNode: `schema-bg-${table.schema}`, // Connect to parent schema
|
||||
extent: 'parent', // Keep within parent boundaries
|
||||
style: {
|
||||
|
|
@ -2430,14 +2450,27 @@ const DataflowCanvas = ({ dbSlug, hasSchemas = true }) => {
|
|||
try {
|
||||
console.log('Creating new table with data:', tableData);
|
||||
|
||||
// Call the createTable function with the provided data
|
||||
// Extract orientation from config or use orientation directly, or generate random one
|
||||
const orientation = (tableData.config && tableData.config.orientation) ?
|
||||
tableData.config.orientation :
|
||||
(tableData.orientation || {
|
||||
x: Math.floor(Math.random() * 700) + 100,
|
||||
y: Math.floor(Math.random() * 700) + 100
|
||||
});
|
||||
|
||||
console.log('Using orientation for new table:', orientation);
|
||||
|
||||
console.log('Using orientation for new table:', orientation);
|
||||
|
||||
// Call the createTable function with the provided data and orientation
|
||||
const newTable = await createTable(
|
||||
tableData.sch,
|
||||
tableData.name,
|
||||
tableData.table_type,
|
||||
tableData.external_name,
|
||||
tableData.description,
|
||||
tableData.con
|
||||
tableData.con,
|
||||
orientation
|
||||
);
|
||||
|
||||
console.log('Table created successfully:', newTable);
|
||||
|
|
@ -2460,10 +2493,10 @@ const DataflowCanvas = ({ dbSlug, hasSchemas = true }) => {
|
|||
columns: columnNames, // Use the column names from the tableData
|
||||
columnDetails: tableData.columns || [] // Store the full column details
|
||||
},
|
||||
// Position the table inside the schema
|
||||
position: {
|
||||
x: Math.random() * 300 + 100,
|
||||
y: Math.random() * 300 + 100
|
||||
// Position the table inside the schema using the orientation from the API
|
||||
position: newTable.orientation || {
|
||||
x: Math.floor(Math.random() * 400) + 100,
|
||||
y: Math.floor(Math.random() * 400) + 100
|
||||
},
|
||||
// Make it part of the schema
|
||||
parentNode: `schema-${tableData.sch}`,
|
||||
|
|
@ -3267,6 +3300,46 @@ const DataflowCanvas = ({ dbSlug, hasSchemas = true }) => {
|
|||
onInit={onInit}
|
||||
onNodeClick={onNodeClick}
|
||||
onMove={onMove}
|
||||
onNodeDragStop={(event, node) => {
|
||||
// Only handle table node drag events
|
||||
if (node.type === 'table' && node.data) {
|
||||
console.log('Table dragged to new position:', node.position);
|
||||
console.log('Table data:', node.data);
|
||||
|
||||
// Send the new position to the API
|
||||
if (node.data.slug && node.data.schema) {
|
||||
try {
|
||||
console.log('Updating table position through qbt_table_update endpoint');
|
||||
|
||||
// Prepare table data for update with config structure
|
||||
const tableData = {
|
||||
tbl: node.data.slug,
|
||||
sch: node.data.schema,
|
||||
con: node.data.database || dbSlug, // Use node's database or current dbSlug
|
||||
name: node.data.label,
|
||||
table_type: node.data.type || 'stage', // Default to stage if not specified
|
||||
// Use config object with orientation nested inside
|
||||
config: {
|
||||
orientation: node.position
|
||||
}
|
||||
};
|
||||
|
||||
// Call the regular updateTable function
|
||||
updateTable(tableData)
|
||||
.then(result => {
|
||||
console.log('Table position updated successfully through regular update:', result);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to update table position:', error);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error preparing table update:', error);
|
||||
}
|
||||
} else {
|
||||
console.error('Missing data for table position update:', node.data);
|
||||
}
|
||||
}
|
||||
}}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
minZoom={0.05}
|
||||
|
|
|
|||
|
|
@ -1965,8 +1965,7 @@ const InfiniteCanvas = () => {
|
|||
});
|
||||
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}`);
|
||||
// No alert needed for MVP - the view will change automatically
|
||||
};
|
||||
|
||||
// Initialize with database nodes from state instead of mockData
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@ const TableCreationPopup = ({ onClose, onCreateTable, schemaInfo }) => {
|
|||
// Filter out empty columns
|
||||
const validColumns = columns.filter(col => col.name.trim() !== '');
|
||||
|
||||
// Generate random position for the table within the schema
|
||||
const randomOrientation = {
|
||||
x: Math.floor(Math.random() * 400) + 100,
|
||||
y: Math.floor(Math.random() * 400) + 100
|
||||
};
|
||||
|
||||
onCreateTable({
|
||||
name: tableName,
|
||||
external_name: externalName || tableName,
|
||||
|
|
@ -69,6 +75,10 @@ const TableCreationPopup = ({ onClose, onCreateTable, schemaInfo }) => {
|
|||
description: description,
|
||||
sch: schemaInfo.sch,
|
||||
con: schemaInfo.con,
|
||||
// Use config object with orientation nested inside (new API structure)
|
||||
config: {
|
||||
orientation: randomOrientation
|
||||
},
|
||||
columns: validColumns.map(col => ({
|
||||
name: col.name,
|
||||
data_type: col.type
|
||||
|
|
|
|||
|
|
@ -1489,7 +1489,7 @@ export const useApiData = (forceRefresh = false, dbSlug = null) => {
|
|||
fetchAndCacheAllData(true);
|
||||
|
||||
// Function to create a new table in a schema
|
||||
const createTable = async (schemaSlug, tableName, tableType, externalName = "", description = "", dbSlug = null) => {
|
||||
const createTable = async (schemaSlug, tableName, tableType, externalName = "", description = "", dbSlug = null, orientation = null) => {
|
||||
try {
|
||||
// Use the provided dbSlug or fall back to the global currentDbSlug
|
||||
const databaseSlug = dbSlug || currentDbSlug;
|
||||
|
|
@ -1513,6 +1513,12 @@ const createTable = async (schemaSlug, tableName, tableType, externalName = "",
|
|||
|
||||
console.log(`Creating new table "${tableName}" in schema: ${schemaSlug}, database: ${databaseSlug}`);
|
||||
|
||||
// Generate random position if not provided
|
||||
const tableOrientation = orientation || {
|
||||
x: Math.floor(Math.random() * 400) + 100,
|
||||
y: Math.floor(Math.random() * 400) + 100
|
||||
};
|
||||
|
||||
// Prepare the payload
|
||||
const payload = {
|
||||
token: token,
|
||||
|
|
@ -1522,7 +1528,8 @@ const createTable = async (schemaSlug, tableName, tableType, externalName = "",
|
|||
name: tableName,
|
||||
external_name: externalName || tableName,
|
||||
table_type: tableType,
|
||||
description: description
|
||||
description: description,
|
||||
config: { orientation: tableOrientation }
|
||||
};
|
||||
|
||||
console.log('Table create request payload:', payload);
|
||||
|
|
@ -1557,7 +1564,9 @@ const createTable = async (schemaSlug, tableName, tableType, externalName = "",
|
|||
created_at: new Date().toISOString(),
|
||||
schema: schemaSlug,
|
||||
database: databaseSlug,
|
||||
columns: []
|
||||
columns: [],
|
||||
orientation: tableOrientation, // Add orientation from the config
|
||||
config: { orientation: tableOrientation } // Also include full config
|
||||
};
|
||||
|
||||
return newTable;
|
||||
|
|
@ -1589,6 +1598,10 @@ const createTable = async (schemaSlug, tableName, tableType, externalName = "",
|
|||
}
|
||||
};
|
||||
|
||||
// Note: We've removed the separate updateTablePosition function
|
||||
// as we're now using the regular updateTable function to update positions
|
||||
// This ensures the standard API endpoint (qbt_table_update) is used for all table updates
|
||||
|
||||
// Function to update a table
|
||||
const updateTable = async (tableData) => {
|
||||
try {
|
||||
|
|
@ -1601,7 +1614,7 @@ const updateTable = async (tableData) => {
|
|||
console.log(`Using current database slug: ${currentDbSlug}`);
|
||||
}
|
||||
|
||||
const { tbl, sch, con, name, external_name, table_type, description } = tableData;
|
||||
const { tbl, sch, con, name, external_name, table_type, description, orientation } = tableData;
|
||||
|
||||
if (!con) {
|
||||
console.error('Missing database identifier in tableData:', tableData);
|
||||
|
|
@ -1631,6 +1644,9 @@ const updateTable = async (tableData) => {
|
|||
console.log(`Updating table "${name}" (${tbl}) in schema: ${sch}, database: ${con}`);
|
||||
|
||||
// Prepare the payload
|
||||
// Log orientation data
|
||||
console.log('Table update orientation data:', orientation);
|
||||
|
||||
const payload = {
|
||||
token: token,
|
||||
org: orgSlug,
|
||||
|
|
@ -1640,7 +1656,13 @@ const updateTable = async (tableData) => {
|
|||
name: name,
|
||||
external_name: external_name || name,
|
||||
table_type: table_type,
|
||||
description: description || ''
|
||||
description: description || '',
|
||||
config: {
|
||||
orientation: orientation || tableData.orientation || {
|
||||
x: Math.floor(Math.random() * 700) + 100,
|
||||
y: Math.floor(Math.random() * 700) + 100
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Table update request payload:', payload);
|
||||
|
|
@ -1662,6 +1684,11 @@ const updateTable = async (tableData) => {
|
|||
if (response.data && response.data.success) {
|
||||
console.log(`Successfully updated table "${name}" in schema ${sch}`);
|
||||
|
||||
// Extract orientation from config if available
|
||||
const tableOrientation = orientation ||
|
||||
(tableData.orientation ? tableData.orientation : null) ||
|
||||
(payload.config && payload.config.orientation ? payload.config.orientation : null);
|
||||
|
||||
// Return the updated table object
|
||||
const updatedTable = {
|
||||
name: name,
|
||||
|
|
@ -1670,9 +1697,13 @@ const updateTable = async (tableData) => {
|
|||
description: description || '',
|
||||
slug: tbl,
|
||||
schema: sch,
|
||||
database: con
|
||||
database: con,
|
||||
orientation: tableOrientation, // Include orientation from config in the response
|
||||
config: payload.config // Also include the full config
|
||||
};
|
||||
|
||||
console.log('Returning updated table with orientation:', updatedTable.orientation);
|
||||
|
||||
return updatedTable;
|
||||
} else {
|
||||
// If the API indicates failure
|
||||
|
|
|
|||
Loading…
Reference in New Issue