mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 06:33:13 +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 { useSelector } from 'react-redux';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
Drawer,
|
||||
List,
|
||||
|
|
@ -9,6 +9,9 @@ import {
|
|||
Toolbar,
|
||||
Box,
|
||||
Typography,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
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 AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
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 type { RootState } from '../app/store';
|
||||
import { DRAWER_WIDTH } from '../constants';
|
||||
|
||||
const COLLAPSED_WIDTH = 72;
|
||||
|
||||
const dispatcherNav = [
|
||||
{ label: 'Dashboard', path: '/', icon: <DashboardIcon 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() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
const theme = useTheme();
|
||||
const open = useSelector((state: RootState) => selectSidebarOpen(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 isSysAdmin = user?.role === 'SysAdmin';
|
||||
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 (
|
||||
<Drawer
|
||||
variant="persistent"
|
||||
variant={drawerVariant}
|
||||
open={open}
|
||||
onClose={handleDrawerClose}
|
||||
sx={{
|
||||
width: open ? DRAWER_WIDTH : 0,
|
||||
width: open ? effectiveWidth : 0,
|
||||
flexShrink: 0,
|
||||
transition: 'width 250ms ease',
|
||||
'& .MuiDrawer-paper': {
|
||||
width: DRAWER_WIDTH,
|
||||
width: effectiveWidth,
|
||||
boxSizing: 'border-box',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Toolbar sx={{ minHeight: 64 }} />
|
||||
<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 && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
letterSpacing: '1.2px',
|
||||
textTransform: 'uppercase',
|
||||
color: '#8A949E',
|
||||
px: 2.5,
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
Admin
|
||||
</Typography>
|
||||
{!isCollapsed && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.08em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#94A3B8',
|
||||
px: 2.5,
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
Admin
|
||||
</Typography>
|
||||
)}
|
||||
<List disablePadding>
|
||||
{adminNav.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>
|
||||
))}
|
||||
{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>
|
||||
))}
|
||||
{adminNav.map((item) => renderNavItem(item))}
|
||||
{isSysAdmin && sysadminNav.map((item) => renderNavItem(item))}
|
||||
</List>
|
||||
</>
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
letterSpacing: '1.2px',
|
||||
textTransform: 'uppercase',
|
||||
color: '#9aa0a6',
|
||||
px: 2.5,
|
||||
mt: isAdmin ? 2 : 0,
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
Proposals
|
||||
</Typography>
|
||||
{!isCollapsed && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.08em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#94A3B8',
|
||||
px: 2.5,
|
||||
mt: isAdmin ? 2 : 0,
|
||||
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>
|
||||
))}
|
||||
{navItems.map((item) => renderNavItem(item))}
|
||||
</List>
|
||||
</Box>
|
||||
{user && (
|
||||
{user && !isCollapsed && (
|
||||
<Box sx={{ p: 2, mt: 'auto', borderTop: '1px solid #DDE3E8' }}>
|
||||
<Box sx={{ fontSize: '13px', fontWeight: 500, color: '#1F2933' }}>{user.displayName}</Box>
|
||||
<Box sx={{ fontSize: '11px', color: '#8A949E' }}>{user.role}</Box>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue