diff --git a/frontend/index.html b/frontend/index.html
index 040fe1b..ba2e436 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -5,9 +5,6 @@
Mariavel - Premium Database Manager
-
-
-
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 56c0e78..cc0daac 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -13,6 +13,7 @@
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^9.0.0",
"@mui/material": "^9.0.0",
+ "@mui/x-data-grid": "^9.0.2",
"axios": "^1.15.2",
"devextreme": "^25.2.6",
"devextreme-react": "^25.2.6",
@@ -990,6 +991,88 @@
}
}
},
+ "node_modules/@mui/x-data-grid": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-9.0.2.tgz",
+ "integrity": "sha512-9hkBS73x3G5MniOpkCh54iH5iwBr55obchF5IS1eybURZEgPxSXFizNMrDbyM2EGaYG9DQ87MvC5IoV7g0F2Vw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/utils": "9.0.0",
+ "@mui/x-internals": "^9.0.0",
+ "@mui/x-virtualizer": "9.0.0-alpha.0",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@mui/material": "^7.3.0 || ^9.0.0",
+ "@mui/system": "^7.3.0 || ^9.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/x-internals": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.0.0.tgz",
+ "integrity": "sha512-E/4rdg69JjhyybpPGypCjAKSKLLnSdCFM+O6P/nkUg47+qt3uftxQEhjQO53rcn6ahHl6du/uNZ9BLgeY6kYxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/utils": "9.0.0",
+ "reselect": "^5.1.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@mui/x-virtualizer": {
+ "version": "9.0.0-alpha.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-9.0.0-alpha.0.tgz",
+ "integrity": "sha512-K52TKCuWlkMEWOeB2nPfhIAHaWsYEb9h1ME9Wb+gmw4FloMA03VvKsrqvn8o6l8hYUi4/5F8NfYOIfPwqW3EhA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/utils": "9.0.0",
+ "@mui/x-internals": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
@@ -3712,6 +3795,12 @@
"util-deprecate": "~1.0.1"
}
},
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -4050,6 +4139,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index f954aad..5132e2e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,6 +15,7 @@
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^9.0.0",
"@mui/material": "^9.0.0",
+ "@mui/x-data-grid": "^9.0.2",
"axios": "^1.15.2",
"devextreme": "^25.2.6",
"devextreme-react": "^25.2.6",
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx
index 21f519d..5a8db64 100644
--- a/frontend/src/components/Header.tsx
+++ b/frontend/src/components/Header.tsx
@@ -14,6 +14,7 @@ const Header: React.FC = () => {
borderBottom: 1,
borderColor: 'divider',
bgcolor: darkMode ? 'rgba(16, 24, 48, 0.95)' : 'rgba(255, 255, 255, 0.95)',
+ color: darkMode ? '#f1f5f9' : '#1e293b',
backdropFilter: 'blur(10px)',
zIndex: (theme) => theme.zIndex.drawer + 1
}}
@@ -23,10 +24,10 @@ const Header: React.FC = () => {
MARIAVEL
-
+
{darkMode ? : }
-
+
diff --git a/frontend/src/components/MainContent.tsx b/frontend/src/components/MainContent.tsx
index 8d83001..f4bb3a1 100644
--- a/frontend/src/components/MainContent.tsx
+++ b/frontend/src/components/MainContent.tsx
@@ -1,53 +1,33 @@
-import React, { useEffect, useState, useMemo } from 'react';
+import React, { useEffect, useState, useCallback } from 'react';
import { Box, Paper, Typography, CircularProgress } from '@mui/material';
-import DataGrid, {
- Column,
- Scrolling,
- FilterRow,
- HeaderFilter,
- SearchPanel,
- GroupPanel,
- Export
-} from 'devextreme-react/data-grid';
+import { DataGrid } from '@mui/x-data-grid';
+import type { GridColDef, GridPaginationModel } from '@mui/x-data-grid';
import { useAppStore } from '../store/useAppStore';
-import api, { SchemaService } from '../services/api';
-
-import CustomStore from 'devextreme/data/custom_store';
+import { SchemaService } from '../services/api';
const MainContent: React.FC = () => {
const { activeTable, activeDatabase } = useAppStore();
- const [columns, setColumns] = useState([]);
+ const [columns, setColumns] = useState([]);
+ const [rows, setRows] = useState([]);
+ const [rowCount, setRowCount] = useState(0);
const [loadingSchema, setLoadingSchema] = useState(false);
+ const [loadingData, setLoadingData] = useState(false);
+ const [paginationModel, setPaginationModel] = useState({
+ page: 0,
+ pageSize: 100,
+ });
- // Define data source with CustomStore for Remote Operations
- const dataSource = useMemo(() => {
- if (!activeTable || !activeDatabase) return null;
-
- return new CustomStore({
- key: 'id', // Ideally this should be the primary key from schema
- load: async (loadOptions: any) => {
- try {
- const params = {
- skip: loadOptions.skip || 0,
- take: loadOptions.take || 100,
- requireTotalCount: loadOptions.requireTotalCount,
- database: activeDatabase,
- };
-
- const response = await SchemaService.getTableData(activeTable, params);
-
- return {
- data: response.data.data,
- totalCount: response.data.totalCount,
- };
- } catch (error) {
- console.error('Data loading error', error);
- throw 'Data Loading Error';
- }
- }
- });
- }, [activeTable, activeDatabase]);
+ // Helper to map SQL types to MUI X Data Grid types
+ const mapSqlTypeToMuiType = (sqlType: string): 'string' | 'number' | 'date' | 'dateTime' | 'boolean' => {
+ sqlType = sqlType.toLowerCase();
+ if (sqlType.includes('int') || sqlType.includes('decimal') || sqlType.includes('float') || sqlType.includes('double')) return 'number';
+ if (sqlType.includes('datetime') || sqlType.includes('timestamp')) return 'dateTime';
+ if (sqlType.includes('date')) return 'date';
+ if (sqlType.includes('bool') || sqlType.includes('tinyint(1)')) return 'boolean';
+ return 'string';
+ };
+ // Fetch Schema
useEffect(() => {
const fetchSchema = async () => {
if (!activeTable || !activeDatabase) return;
@@ -55,12 +35,26 @@ const MainContent: React.FC = () => {
setLoadingSchema(true);
try {
const schemaRes = await SchemaService.getTableSchema(activeTable, activeDatabase);
- const cols = schemaRes.data.map((col: any) => ({
- dataField: col.Field,
- caption: col.Field,
- dataType: mapSqlTypeToDxType(col.Type)
- }));
+ const cols: GridColDef[] = schemaRes.data.map((col: any) => {
+ const type = mapSqlTypeToMuiType(col.Type);
+ return {
+ field: col.Field,
+ headerName: col.Field,
+ type: type,
+ flex: 1,
+ minWidth: 150,
+ valueGetter: (value: any) => {
+ if ((type === 'date' || type === 'dateTime') && value && typeof value === 'string') {
+ return new Date(value);
+ }
+ return value;
+ },
+ };
+ });
setColumns(cols);
+
+ // Reset pagination when table changes
+ setPaginationModel({ page: 0, pageSize: 100 });
} catch (error) {
console.error('Failed to fetch table schema', error);
} finally {
@@ -71,14 +65,40 @@ const MainContent: React.FC = () => {
fetchSchema();
}, [activeTable, activeDatabase]);
- // Helper to map SQL types to DevExtreme types
- const mapSqlTypeToDxType = (sqlType: string) => {
- sqlType = sqlType.toLowerCase();
- if (sqlType.includes('int') || sqlType.includes('decimal') || sqlType.includes('float')) return 'number';
- if (sqlType.includes('date') || sqlType.includes('time')) return 'date';
- if (sqlType.includes('bool')) return 'boolean';
- return 'string';
- };
+ // Fetch Data
+ const fetchData = useCallback(async () => {
+ if (!activeTable || !activeDatabase) return;
+
+ setLoadingData(true);
+ try {
+ const params = {
+ skip: paginationModel.page * paginationModel.pageSize,
+ take: paginationModel.pageSize,
+ requireTotalCount: true,
+ database: activeDatabase,
+ };
+
+ const response = await SchemaService.getTableData(activeTable, params);
+
+ // MUI X Data Grid needs a unique id for each row
+ // If the table doesn't have an 'id' column, we might need to generate one
+ const dataWithIds = response.data.data.map((row: any, index: number) => ({
+ id: row.id || row.ID || `row-${index}-${paginationModel.page}`,
+ ...row,
+ }));
+
+ setRows(dataWithIds);
+ setRowCount(response.data.totalCount || 0);
+ } catch (error) {
+ console.error('Data loading error', error);
+ } finally {
+ setLoadingData(false);
+ }
+ }, [activeTable, activeDatabase, paginationModel]);
+
+ useEffect(() => {
+ fetchData();
+ }, [fetchData]);
if (!activeTable) {
return (
@@ -89,40 +109,45 @@ const MainContent: React.FC = () => {
}
return (
-
+
{activeDatabase}.{activeTable}
-
+
{loadingSchema ? (
) : (
-
-
-
-
-
-
-
- {columns.map(col => (
-
- ))}
-
+ rows={rows}
+ columns={columns}
+ rowCount={rowCount}
+ loading={loadingData}
+ paginationMode="server"
+ paginationModel={paginationModel}
+ onPaginationModelChange={setPaginationModel}
+ pageSizeOptions={[25, 50, 100]}
+ sx={{
+ border: 'none',
+ '& .MuiDataGrid-cell:focus': {
+ outline: 'none',
+ },
+ '& .MuiDataGrid-columnHeader:focus': {
+ outline: 'none',
+ },
+ height: '100%',
+ }}
+ slotProps={{
+ loadingOverlay: {
+ variant: 'linear-progress',
+ noRowsVariant: 'linear-progress',
+ }
+ }}
+ />
)}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 7d6b462..011755d 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -13,15 +13,7 @@
--text-dark: #f1f5f9;
}
-/* Suppress DevExtreme License Watermark */
-dx-license, [class*="dx-license"] {
- display: none !important;
- visibility: hidden !important;
- height: 0 !important;
- opacity: 0 !important;
- pointer-events: none !important;
-}
-
+/* Global Styles */
* {
margin: 0;
padding: 0;
@@ -55,70 +47,6 @@ body.dark {
border: 1px solid var(--glass-border-dark);
}
-/* DevExtreme Custom Styling - Modern Dark Theme Integration */
-.dx-datagrid {
- background-color: transparent !important;
- border-radius: 12px !important;
- overflow: hidden !important;
- border: none !important;
- font-family: inherit !important;
-}
-
-.dark .dx-datagrid {
- color: var(--text-dark) !important;
-}
-
-.dx-datagrid-headers {
- background-color: rgba(255, 255, 255, 0.05) !important;
- border-bottom: 1px solid var(--glass-border) !important;
- color: inherit !important;
-}
-
-.dark .dx-datagrid-headers {
- background-color: rgba(0, 0, 0, 0.2) !important;
- border-bottom: 1px solid var(--glass-border-dark) !important;
-}
-
-.dx-datagrid-rowsview .dx-row {
- background-color: transparent !important;
- color: inherit !important;
-}
-
-.dark .dx-datagrid-rowsview .dx-row {
- border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
-}
-
-.dx-datagrid-content .dx-datagrid-table .dx-row > td {
- padding: 12px 16px !important;
-}
-
-/* Fix for Search and Group Panels in Dark Mode */
-.dark .dx-datagrid-search-panel,
-.dark .dx-datagrid-group-panel,
-.dark .dx-datagrid-filter-row {
- background-color: rgba(0, 0, 0, 0.2) !important;
- color: var(--text-dark) !important;
-}
-
-.dark .dx-textbox-input {
- color: var(--text-dark) !important;
- background-color: rgba(255, 255, 255, 0.05) !important;
-}
-
-/* Column Header text */
-.dark .dx-datagrid-text-content {
- color: rgba(255, 255, 255, 0.7) !important;
- font-weight: 600 !important;
-}
-
-/* Selection and Hover */
-.dark .dx-data-row.dx-state-hover:not(.dx-selection):not(.dx-row-inserted):not(.dx-row-removed):not(.dx-edit-row) > td {
- background-color: rgba(255, 255, 255, 0.05) !important;
-}
-
-.dark .dx-selection.dx-row > td {
- background-color: rgba(0, 97, 255, 0.2) !important;
-}
/* Scrollbar Styling */
::-webkit-scrollbar {
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 63bb10c..bef5202 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,8 +1,6 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
-import 'devextreme/dist/css/dx.common.css';
-import 'devextreme/dist/css/dx.material.blue.dark.compact.css';
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(