feat: implement App layout and server-side paginated DataGrid for table views

This commit is contained in:
Ümit Tunç
2026-04-24 07:49:05 +03:00
parent f546a0e20b
commit 14abf1223f
4 changed files with 44 additions and 19 deletions
+1
View File
@@ -8,6 +8,7 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
+1
View File
@@ -10,6 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
+26 -14
View File
@@ -1,5 +1,7 @@
import React, { useMemo, useEffect } from 'react'; import React, { useMemo, useEffect } from 'react';
import { ThemeProvider, CssBaseline, Box } from '@mui/material'; import { ThemeProvider, CssBaseline, Box } from '@mui/material';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { getTheme } from './theme/theme'; import { getTheme } from './theme/theme';
import { useAppStore } from './store/useAppStore'; import { useAppStore } from './store/useAppStore';
import Sidebar from './components/Sidebar.tsx'; import Sidebar from './components/Sidebar.tsx';
@@ -8,6 +10,12 @@ import Header from './components/Header.tsx';
import Login from './components/Login.tsx'; import Login from './components/Login.tsx';
import NavigationRail from './components/NavigationRail.tsx'; import NavigationRail from './components/NavigationRail.tsx';
// Create emotion cache to handle the :first-child warning and ensure proper style injection
const cache = createCache({
key: 'mui',
prepend: true,
});
const App: React.FC = () => { const App: React.FC = () => {
const { darkMode, connected, activeTab, setActiveTab } = useAppStore(); const { darkMode, connected, activeTab, setActiveTab } = useAppStore();
const theme = useMemo(() => getTheme(darkMode ? 'dark' : 'light'), [darkMode]); const theme = useMemo(() => getTheme(darkMode ? 'dark' : 'light'), [darkMode]);
@@ -22,25 +30,29 @@ const App: React.FC = () => {
if (!connected) { if (!connected) {
return ( return (
<ThemeProvider theme={theme}> <CacheProvider value={cache}>
<CssBaseline /> <ThemeProvider theme={theme}>
<Login /> <CssBaseline />
</ThemeProvider> <Login />
</ThemeProvider>
</CacheProvider>
); );
} }
return ( return (
<ThemeProvider theme={theme}> <CacheProvider value={cache}>
<CssBaseline /> <ThemeProvider theme={theme}>
<Box sx={{ display: 'flex', height: '100vh', overflow: 'hidden' }}> <CssBaseline />
<NavigationRail activeTab={activeTab} onTabChange={setActiveTab} /> <Box sx={{ display: 'flex', height: '100vh', overflow: 'hidden' }}>
{activeTab === 'explorer' && <Sidebar />} <NavigationRail activeTab={activeTab} onTabChange={setActiveTab} />
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}> {activeTab === 'explorer' && <Sidebar />}
<Header /> <Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', minWidth: 0, overflow: 'hidden' }}>
<MainContent /> <Header />
<MainContent />
</Box>
</Box> </Box>
</Box> </ThemeProvider>
</ThemeProvider> </CacheProvider>
); );
}; };
+16 -5
View File
@@ -41,8 +41,8 @@ const MainContent: React.FC = () => {
field: col.Field, field: col.Field,
headerName: col.Field, headerName: col.Field,
type: type, type: type,
flex: 1, width: 200,
minWidth: 150, minWidth: 100,
valueGetter: (value: any) => { valueGetter: (value: any) => {
if ((type === 'date' || type === 'dateTime') && value && typeof value === 'string') { if ((type === 'date' || type === 'dateTime') && value && typeof value === 'string') {
return new Date(value); return new Date(value);
@@ -109,14 +109,14 @@ const MainContent: React.FC = () => {
} }
return ( return (
<Box sx={{ flexGrow: 1, p: 3, bgcolor: 'background.default', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}> <Box sx={{ flexGrow: 1, p: 3, bgcolor: 'background.default', display: 'flex', flexDirection: 'column', width: '100%', minWidth: 0, overflow: 'hidden' }}>
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700 }}>
{activeDatabase}.{activeTable} {activeDatabase}.{activeTable}
</Typography> </Typography>
</Box> </Box>
<Paper sx={{ flexGrow: 1, borderRadius: 2, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}> <Paper sx={{ flexGrow: 1, borderRadius: 2, overflow: 'hidden', display: 'flex', flexDirection: 'column', width: '100%' }}>
{loadingSchema ? ( {loadingSchema ? (
<Box sx={{ display: 'flex', height: '100%', alignItems: 'center', justifyContent: 'center' }}> <Box sx={{ display: 'flex', height: '100%', alignItems: 'center', justifyContent: 'center' }}>
<CircularProgress /> <CircularProgress />
@@ -133,13 +133,24 @@ const MainContent: React.FC = () => {
pageSizeOptions={[25, 50, 100]} pageSizeOptions={[25, 50, 100]}
sx={{ sx={{
border: 'none', border: 'none',
width: '100%',
height: '100%',
'& .MuiDataGrid-cell:focus': { '& .MuiDataGrid-cell:focus': {
outline: 'none', outline: 'none',
}, },
'& .MuiDataGrid-columnHeader:focus': { '& .MuiDataGrid-columnHeader:focus': {
outline: 'none', outline: 'none',
}, },
height: '100%', '& .MuiDataGrid-row:nth-of-type(even)': {
bgcolor: (theme) => theme.palette.mode === 'light'
? 'rgba(0, 0, 0, 0.03)'
: 'rgba(255, 255, 255, 0.03)',
},
'& .MuiDataGrid-row:hover': {
bgcolor: (theme) => theme.palette.mode === 'light'
? 'rgba(0, 97, 255, 0.08)'
: 'rgba(0, 97, 255, 0.15)',
},
}} }}
slotProps={{ slotProps={{
loadingOverlay: { loadingOverlay: {