From 6c0ec02f8522dda75cc90373d8890c502582dcd1 Mon Sep 17 00:00:00 2001 From: Devika Date: Thu, 12 Jun 2025 10:58:29 +0530 Subject: [PATCH] Orientation capturing and sidebar --- src/App.css | 78 ++++ src/App.jsx | 505 ++++++++++++++++---------- src/components/DataflowCanvas.jsx | 89 ++++- src/components/InfiniteCanvas.jsx | 3 +- src/components/TableCreationPopup.jsx | 10 + src/components/mockData.js | 43 ++- 6 files changed, 522 insertions(+), 206 deletions(-) diff --git a/src/App.css b/src/App.css index b796d8b..9c220ca 100644 --- a/src/App.css +++ b/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); diff --git a/src/App.jsx b/src/App.jsx index 4eccd53..b49fca5 100644 --- a/src/App.jsx +++ b/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 - {/* Navigation */} -
- { - e.preventDefault(); - setActiveTab('canvas'); - window.history.pushState(null, '', '/qubit_service'); - }} - > - Overview - - { - e.preventDefault(); - setActiveTab('charts'); - window.history.pushState(null, '', '/charts'); - }} - > - Advanced Charts - - { - e.preventDefault(); - setActiveTab('dataflow'); - window.history.pushState(null, '', '/data_mappings'); - }} - > - Data Mapping - - { - e.preventDefault(); - // Settings functionality would go here - alert('Settings functionality not implemented yet'); - }} - > - Settings - + {/* User profile and controls */} +
+
+ + + + + John Doe +
+ +
+ + + + +
-
- + > + + + Qbit + + {isDropdownOpen ? + : + + } +
+ + {/* Dropdown items container with animation */} +
+ {/* Overview Item */} +
{ + 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' + }} + > + + + Overview + +
+ + {/* ER Diagram Item */} +
{ + 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' + }} + > + + + ER Diagram + +
+ + {/* Data Mapping Item */} +
{ + 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' + }} + > + + + Data Mapping + +
+ + + {/* Settings Item */} +
{ + 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' + }} + > + + + Dashboard + +
+ + {/* Advanced Charts Item */} + {/*
{ + 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' + }} + > + + + Advanced Charts + +
*/} +
+
+ -
+
{activeTab === 'canvas' && ( <> @@ -367,7 +440,7 @@ function App() { {activeTab === 'charts' && (
- + {/* */}
)} @@ -421,6 +494,58 @@ function App() { )} + + {activeTab === 'er_diagram' && ( + <> + +
+

+ + Entity Relationship Diagram +

+

+ View table relationships • Explore schema structure • Analyze foreign keys +

+
+ + )}
diff --git a/src/components/DataflowCanvas.jsx b/src/components/DataflowCanvas.jsx index 551cfea..ee19fe7 100644 --- a/src/components/DataflowCanvas.jsx +++ b/src/components/DataflowCanvas.jsx @@ -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} diff --git a/src/components/InfiniteCanvas.jsx b/src/components/InfiniteCanvas.jsx index f791122..72e47e3 100644 --- a/src/components/InfiniteCanvas.jsx +++ b/src/components/InfiniteCanvas.jsx @@ -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 diff --git a/src/components/TableCreationPopup.jsx b/src/components/TableCreationPopup.jsx index 33149ce..fef8538 100644 --- a/src/components/TableCreationPopup.jsx +++ b/src/components/TableCreationPopup.jsx @@ -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 diff --git a/src/components/mockData.js b/src/components/mockData.js index edcf2be..56b42eb 100644 --- a/src/components/mockData.js +++ b/src/components/mockData.js @@ -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 -- 2.40.1