mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-01 19:53:14 +00:00
sidebar: audit styling updates, responsive collapse at 1200px/900px
This commit is contained in:
parent
f7b4ab6f93
commit
2e20e7ad99
1 changed files with 103 additions and 93 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
List,
|
List,
|
||||||
|
|
@ -9,6 +9,9 @@ import {
|
||||||
Toolbar,
|
Toolbar,
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
|
Tooltip,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
import DescriptionIcon from '@mui/icons-material/Description';
|
import DescriptionIcon from '@mui/icons-material/Description';
|
||||||
|
|
@ -16,11 +19,13 @@ import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||||
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
|
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
|
||||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||||
import PeopleIcon from '@mui/icons-material/People';
|
import PeopleIcon from '@mui/icons-material/People';
|
||||||
import { selectSidebarOpen } from '../app/slices/uiSlice';
|
import { selectSidebarOpen, setSidebarOpen } from '../app/slices/uiSlice';
|
||||||
import { selectUser } from '../app/slices/authSlice';
|
import { selectUser } from '../app/slices/authSlice';
|
||||||
import type { RootState } from '../app/store';
|
import type { RootState } from '../app/store';
|
||||||
import { DRAWER_WIDTH } from '../constants';
|
import { DRAWER_WIDTH } from '../constants';
|
||||||
|
|
||||||
|
const COLLAPSED_WIDTH = 72;
|
||||||
|
|
||||||
const dispatcherNav = [
|
const dispatcherNav = [
|
||||||
{ label: 'Dashboard', path: '/', icon: <DashboardIcon fontSize="small" /> },
|
{ label: 'Dashboard', path: '/', icon: <DashboardIcon fontSize="small" /> },
|
||||||
{ label: 'My Proposals', path: '/proposals', icon: <DescriptionIcon fontSize="small" /> },
|
{ label: 'My Proposals', path: '/proposals', icon: <DescriptionIcon fontSize="small" /> },
|
||||||
|
|
@ -50,126 +55,131 @@ function isNavActive(pathname: string, itemPath: string): boolean {
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const theme = useTheme();
|
||||||
const open = useSelector((state: RootState) => selectSidebarOpen(state));
|
const open = useSelector((state: RootState) => selectSidebarOpen(state));
|
||||||
const user = useSelector((state: RootState) => selectUser(state));
|
const user = useSelector((state: RootState) => selectUser(state));
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('md')); // <900px
|
||||||
|
const isCollapsed = useMediaQuery(theme.breakpoints.between('md', 'lg')); // 900–1200px
|
||||||
|
|
||||||
const isAdmin = user?.role === 'Admin' || user?.role === 'SysAdmin';
|
const isAdmin = user?.role === 'Admin' || user?.role === 'SysAdmin';
|
||||||
const isSysAdmin = user?.role === 'SysAdmin';
|
const isSysAdmin = user?.role === 'SysAdmin';
|
||||||
const navItems = dispatcherNav;
|
const navItems = dispatcherNav;
|
||||||
|
|
||||||
|
const drawerVariant = isMobile ? 'temporary' : 'persistent';
|
||||||
|
const effectiveWidth = isCollapsed ? COLLAPSED_WIDTH : DRAWER_WIDTH;
|
||||||
|
|
||||||
|
const handleNavClick = (path: string) => {
|
||||||
|
navigate(path);
|
||||||
|
if (isMobile) {
|
||||||
|
dispatch(setSidebarOpen(false));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerClose = () => {
|
||||||
|
dispatch(setSidebarOpen(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNavItem = (item: { label: string; path: string; icon: React.ReactNode }) => {
|
||||||
|
const button = (
|
||||||
|
<ListItemButton
|
||||||
|
key={item.path}
|
||||||
|
selected={isNavActive(location.pathname, item.path)}
|
||||||
|
onClick={() => handleNavClick(item.path)}
|
||||||
|
sx={{
|
||||||
|
py: 1,
|
||||||
|
justifyContent: isCollapsed ? 'center' : 'flex-start',
|
||||||
|
minHeight: 40,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: isCollapsed ? 0 : 40,
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</ListItemIcon>
|
||||||
|
{!isCollapsed && (
|
||||||
|
<ListItemText primary={item.label} primaryTypographyProps={{ fontSize: '13px' }} />
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isCollapsed) {
|
||||||
|
return (
|
||||||
|
<Tooltip key={item.path} title={item.label} placement="right" arrow>
|
||||||
|
{button}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
variant="persistent"
|
variant={drawerVariant}
|
||||||
open={open}
|
open={open}
|
||||||
|
onClose={handleDrawerClose}
|
||||||
sx={{
|
sx={{
|
||||||
width: open ? DRAWER_WIDTH : 0,
|
width: open ? effectiveWidth : 0,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
transition: 'width 250ms ease',
|
transition: 'width 250ms ease',
|
||||||
'& .MuiDrawer-paper': {
|
'& .MuiDrawer-paper': {
|
||||||
width: DRAWER_WIDTH,
|
width: effectiveWidth,
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
|
overflowX: 'hidden',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar sx={{ minHeight: 64 }} />
|
<Toolbar sx={{ minHeight: 64 }} />
|
||||||
<Box sx={{ overflow: 'auto', flexGrow: 1, pt: 1 }}>
|
<Box sx={{ overflow: 'auto', flexGrow: 1, pt: 1 }}>
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '11px',
|
|
||||||
fontWeight: 600,
|
|
||||||
letterSpacing: '1.2px',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
color: '#8A949E',
|
|
||||||
px: 2.5,
|
|
||||||
mb: 0.5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Proposals
|
|
||||||
</Typography>
|
|
||||||
<List disablePadding>
|
|
||||||
{navItems.map((item) => (
|
|
||||||
<ListItemButton
|
|
||||||
key={item.path}
|
|
||||||
selected={isNavActive(location.pathname, item.path)}
|
|
||||||
onClick={() => navigate(item.path)}
|
|
||||||
sx={{ py: 1 }}
|
|
||||||
>
|
|
||||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
||||||
<ListItemText primary={item.label} primaryTypographyProps={{ fontSize: '13px' }} />
|
|
||||||
</ListItemButton>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<>
|
<>
|
||||||
<Typography
|
{!isCollapsed && (
|
||||||
sx={{
|
<Typography
|
||||||
fontSize: '11px',
|
sx={{
|
||||||
fontWeight: 600,
|
fontSize: '11px',
|
||||||
letterSpacing: '1.2px',
|
fontWeight: 700,
|
||||||
textTransform: 'uppercase',
|
letterSpacing: '0.08em',
|
||||||
color: '#8A949E',
|
textTransform: 'uppercase',
|
||||||
px: 2.5,
|
color: '#94A3B8',
|
||||||
mb: 0.5,
|
px: 2.5,
|
||||||
}}
|
mb: 0.5,
|
||||||
>
|
}}
|
||||||
Admin
|
>
|
||||||
</Typography>
|
Admin
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
{adminNav.map((item) => (
|
{adminNav.map((item) => renderNavItem(item))}
|
||||||
<ListItemButton
|
{isSysAdmin && sysadminNav.map((item) => renderNavItem(item))}
|
||||||
key={item.path}
|
|
||||||
selected={isNavActive(location.pathname, item.path)}
|
|
||||||
onClick={() => navigate(item.path)}
|
|
||||||
sx={{ py: 1 }}
|
|
||||||
>
|
|
||||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
||||||
<ListItemText primary={item.label} primaryTypographyProps={{ fontSize: '13px' }} />
|
|
||||||
</ListItemButton>
|
|
||||||
))}
|
|
||||||
{isSysAdmin && sysadminNav.map((item) => (
|
|
||||||
<ListItemButton
|
|
||||||
key={item.path}
|
|
||||||
selected={isNavActive(location.pathname, item.path)}
|
|
||||||
onClick={() => navigate(item.path)}
|
|
||||||
sx={{ py: 1 }}
|
|
||||||
>
|
|
||||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
||||||
<ListItemText primary={item.label} primaryTypographyProps={{ fontSize: '13px' }} />
|
|
||||||
</ListItemButton>
|
|
||||||
))}
|
|
||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Typography
|
{!isCollapsed && (
|
||||||
sx={{
|
<Typography
|
||||||
fontSize: '11px',
|
sx={{
|
||||||
fontWeight: 600,
|
fontSize: '11px',
|
||||||
letterSpacing: '1.2px',
|
fontWeight: 700,
|
||||||
textTransform: 'uppercase',
|
letterSpacing: '0.08em',
|
||||||
color: '#9aa0a6',
|
textTransform: 'uppercase',
|
||||||
px: 2.5,
|
color: '#94A3B8',
|
||||||
mt: isAdmin ? 2 : 0,
|
px: 2.5,
|
||||||
mb: 0.5,
|
mt: isAdmin ? 2 : 0,
|
||||||
}}
|
mb: 0.5,
|
||||||
>
|
}}
|
||||||
Proposals
|
>
|
||||||
</Typography>
|
Proposals
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => renderNavItem(item))}
|
||||||
<ListItemButton
|
|
||||||
key={item.path}
|
|
||||||
selected={isNavActive(location.pathname, item.path)}
|
|
||||||
onClick={() => navigate(item.path)}
|
|
||||||
sx={{ py: 1 }}
|
|
||||||
>
|
|
||||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
||||||
<ListItemText primary={item.label} primaryTypographyProps={{ fontSize: '13px' }} />
|
|
||||||
</ListItemButton>
|
|
||||||
))}
|
|
||||||
</List>
|
</List>
|
||||||
</Box>
|
</Box>
|
||||||
{user && (
|
{user && !isCollapsed && (
|
||||||
<Box sx={{ p: 2, mt: 'auto', borderTop: '1px solid #DDE3E8' }}>
|
<Box sx={{ p: 2, mt: 'auto', borderTop: '1px solid #DDE3E8' }}>
|
||||||
<Box sx={{ fontSize: '13px', fontWeight: 500, color: '#1F2933' }}>{user.displayName}</Box>
|
<Box sx={{ fontSize: '13px', fontWeight: 500, color: '#1F2933' }}>{user.displayName}</Box>
|
||||||
<Box sx={{ fontSize: '11px', color: '#8A949E' }}>{user.role}</Box>
|
<Box sx={{ fontSize: '11px', color: '#8A949E' }}>{user.role}</Box>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue