David Headrick 4180e50c9c Refactor authentication and implement target management
- 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.
2025-02-26 17:42:40 -06:00

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;