- Updated `AuthenticationController` to return user data instead of token. - Added `ITargetService` and `ITargetRepository` to `Program.cs`. - Enhanced `appsettings.json` with connection timeout and security notes. - Modified `IAuthService` to reflect new authentication response structure. - Implemented connection retry mechanism in `DataAccess.cs`. - Refactored UI components to use MUI styling in `Layout.tsx`, `App.tsx`, and others. - Created new files for target management, including `TargetsController`, `TargetUpdateDto`, and related services. - Added `TargetEdit` modal for editing target details and `LineChartSample` for data visualization. - Updated package dependencies in `package-lock.json` and project file.
212 lines
8.5 KiB
TypeScript
212 lines
8.5 KiB
TypeScript
// src/components/layouts/Layout.tsx
|
|
import React, { ReactNode } from 'react';
|
|
import { styled, useColorScheme } from '@mui/material/styles';
|
|
import Box from '@mui/material/Box';
|
|
import Drawer from '@mui/material/Drawer';
|
|
import AppBar from '@mui/material/AppBar';
|
|
import Toolbar from '@mui/material/Toolbar';
|
|
import List from '@mui/material/List';
|
|
import Typography from '@mui/material/Typography';
|
|
import Divider from '@mui/material/Divider';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import MenuIcon from '@mui/icons-material/Menu';
|
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
import ListItem from '@mui/material/ListItem';
|
|
import ListItemButton from '@mui/material/ListItemButton';
|
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
import ListItemText from '@mui/material/ListItemText';
|
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
|
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
|
|
import PeopleIcon from '@mui/icons-material/People';
|
|
import { Link as RouterLink } from 'react-router-dom';
|
|
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
|
import MenuItem from '@mui/material/MenuItem';
|
|
import FormControl from '@mui/material/FormControl';
|
|
import InputLabel from '@mui/material/InputLabel';
|
|
|
|
// Constants
|
|
const drawerWidth = 240;
|
|
|
|
// Styled components
|
|
const DrawerHeader = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
padding: theme.spacing(0, 1),
|
|
...theme.mixins.toolbar,
|
|
justifyContent: 'flex-end',
|
|
}));
|
|
|
|
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
|
|
open?: boolean;
|
|
}>(({ theme, open }) => ({
|
|
flexGrow: 1,
|
|
padding: theme.spacing(3),
|
|
transition: theme.transitions.create(['margin', 'width', 'padding'], {
|
|
easing: theme.transitions.easing.sharp,
|
|
duration: theme.transitions.duration.enteringScreen,
|
|
}),
|
|
marginLeft: "0px !important", // Force remove any margin on the left
|
|
marginRight: "0px !important", // Force remove any margin on the left
|
|
...(open && {/*Opened specific types go here*/}),
|
|
...(!open && {/*closed specific styles go here*/})
|
|
}));
|
|
|
|
interface LayoutProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
const Layout = ({ children }: LayoutProps) => {
|
|
const [open, setOpen] = React.useState(false);
|
|
const { mode, setMode } = useColorScheme(); // MUI v6 hook for theme switching
|
|
const iconButtonRef = React.useRef<HTMLButtonElement>(null)
|
|
|
|
const handleDrawerOpen = () => {
|
|
setOpen(true);
|
|
sendResize();
|
|
};
|
|
|
|
const handleDrawerClose = () => {
|
|
setOpen(false);
|
|
sendResize();
|
|
};
|
|
|
|
const sendResize = () => {
|
|
//// Force window resize event after drawer state changes
|
|
//setTimeout(() => {
|
|
// window.dispatchEvent(new Event("resize"));
|
|
//}); // Delay slightly to ensure UI updates
|
|
};
|
|
const handleThemeChange = (event: SelectChangeEvent) => {
|
|
setMode(event.target.value as 'light' | 'dark');
|
|
if (iconButtonRef.current) {
|
|
const selectElement = iconButtonRef.current;
|
|
if (selectElement) {
|
|
if (selectElement instanceof HTMLElement) {
|
|
setTimeout(() => {
|
|
selectElement.focus(); // Blur the focusable input
|
|
}, 0);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Box sx={{ display: 'flex' }}>
|
|
{/* App Bar */}
|
|
<AppBar
|
|
position="fixed"
|
|
sx={(theme) => ({
|
|
zIndex: theme.zIndex.drawer + 1,
|
|
transition: theme.transitions.create(['width', 'margin','padding'], {
|
|
easing: theme.transitions.easing.easeInOut,
|
|
duration: theme.transitions.duration.leavingScreen,
|
|
}),
|
|
...(open && {
|
|
width: `calc(100% - ${drawerWidth}px)`,
|
|
ml: `${drawerWidth}px`,
|
|
transition: theme.transitions.create(['width', 'margin', 'padding'], {
|
|
easing: theme.transitions.easing.easeInOut,
|
|
duration: theme.transitions.duration.enteringScreen,
|
|
}),
|
|
}),
|
|
})}
|
|
>
|
|
<Toolbar>
|
|
<IconButton
|
|
color="inherit"
|
|
aria-label="open drawer"
|
|
onClick={handleDrawerOpen}
|
|
edge="start"
|
|
ref={iconButtonRef}
|
|
sx={{ mr: 2, ...(open && { display: 'none' }) }}
|
|
>
|
|
<MenuIcon />
|
|
</IconButton>
|
|
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
|
Surge365 Dashboard
|
|
</Typography>
|
|
<FormControl sx={{ minWidth: 120 }} size="small">
|
|
<InputLabel
|
|
id="theme-select-label"
|
|
sx={{
|
|
color: 'white', // White in both modes
|
|
'&.Mui-focused': { color: 'white' }, // Keep white when focused
|
|
}}
|
|
>
|
|
Theme
|
|
</InputLabel>
|
|
<Select
|
|
labelId="theme-select-label"
|
|
id="theme-select"
|
|
value={mode || 'light'}
|
|
label="Theme"
|
|
onChange={handleThemeChange}
|
|
sx={{
|
|
color: 'white', // White text
|
|
'& .MuiSvgIcon-root': { color: 'white' }, // White dropdown arrow
|
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
borderColor: 'white', // Gray in light, white in dark
|
|
},
|
|
'&:hover .MuiOutlinedInput-notchedOutline': {
|
|
borderColor: 'white', // Darker gray on hover in light
|
|
borderWidth: 2
|
|
},
|
|
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
|
borderColor: 'white', // Even darker gray when focused in light
|
|
borderWidth: 2
|
|
},
|
|
}}
|
|
>
|
|
<MenuItem value="light">Light</MenuItem>
|
|
<MenuItem value="dark">Dark</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Toolbar>
|
|
</AppBar>
|
|
|
|
{/* Sidebar */}
|
|
<Drawer
|
|
variant="persistent"
|
|
anchor="left"
|
|
open={open}
|
|
sx={{
|
|
width: open ? `var(--mui-drawer-width, ${drawerWidth}px)` : 0,
|
|
flexShrink: 0,
|
|
'& .MuiDrawer-paper': {
|
|
width: open ? `var(--mui-drawer-width, ${drawerWidth}px)` : 0,
|
|
boxSizing: 'border-box',
|
|
},
|
|
}}
|
|
>
|
|
<DrawerHeader>
|
|
<IconButton onClick={handleDrawerClose}>
|
|
<ChevronLeftIcon />
|
|
</IconButton>
|
|
</DrawerHeader>
|
|
<Divider />
|
|
<List>
|
|
{[
|
|
{ text: 'Home', icon: <DashboardIcon />, path: '/home' },
|
|
{ text: 'Targets', icon: <DirectionsCarIcon />, path: '/targets' },
|
|
{ text: 'Templates', icon: <PeopleIcon />, path: '/templates' },
|
|
].map((item) => (
|
|
<ListItem key={item.text} disablePadding>
|
|
<ListItemButton component={RouterLink} to={item.path}>
|
|
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
<ListItemText primary={item.text} />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</Drawer>
|
|
|
|
{/* Main Content */}
|
|
<Main open={open}>
|
|
<DrawerHeader />
|
|
{children}
|
|
</Main>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default Layout; |