172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
import { NavLink, useLocation } from "react-router-dom";
|
|
import Box from "@mui/material/Box";
|
|
import List from "@mui/material/List";
|
|
import ListItemButton from "@mui/material/ListItemButton";
|
|
import ListItemIcon from "@mui/material/ListItemIcon";
|
|
import ListItemText from "@mui/material/ListItemText";
|
|
import Typography from "@mui/material/Typography";
|
|
import Avatar from "@mui/material/Avatar";
|
|
import Button from "@mui/material/Button";
|
|
import { useAuth } from "../context/AuthContext";
|
|
import { useTheme as useAppTheme } from "../../context/ThemeContext";
|
|
import { menuSections, isItemActive, hasItemPermission } from "./navData";
|
|
|
|
interface SidebarNavProps {
|
|
/** Closes the mobile drawer on item click (optional — desktop omits it). */
|
|
onNavigate?: () => void;
|
|
/** Logout handler, owned by the parent. */
|
|
onLogout: () => void;
|
|
}
|
|
|
|
export default function SidebarNav({ onNavigate, onLogout }: SidebarNavProps) {
|
|
const { user, hasPermission } = useAuth();
|
|
const { theme } = useAppTheme();
|
|
const location = useLocation();
|
|
|
|
const visibleSections = menuSections
|
|
.map((section) => ({
|
|
...section,
|
|
items: section.items.filter((item) =>
|
|
hasItemPermission(item, hasPermission),
|
|
),
|
|
}))
|
|
.filter((section) => section.items.length > 0);
|
|
|
|
return (
|
|
<Box
|
|
sx={{ display: "flex", flexDirection: "column", height: "100%", p: 1.5 }}
|
|
>
|
|
{/* Logo header */}
|
|
<Box sx={{ px: 1, py: 1 }}>
|
|
<Box
|
|
component="img"
|
|
src={`/api/admin/company-settings/logo?variant=${theme === "dark" ? "dark" : "light"}`}
|
|
alt="Logo"
|
|
onError={(e) => {
|
|
e.currentTarget.src =
|
|
theme === "dark"
|
|
? "/images/logo-dark.png"
|
|
: "/images/logo-light.png";
|
|
}}
|
|
sx={{
|
|
maxHeight: 40,
|
|
maxWidth: "100%",
|
|
objectFit: "contain",
|
|
display: "block",
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Nav */}
|
|
<Box sx={{ flex: 1, overflowY: "auto", mt: 1 }}>
|
|
{visibleSections.map((section) => (
|
|
<Box key={section.label}>
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
display: "block",
|
|
px: 1.5,
|
|
py: 0.5,
|
|
textTransform: "uppercase",
|
|
letterSpacing: ".06em",
|
|
fontWeight: 700,
|
|
color: "text.secondary",
|
|
fontSize: ".64rem",
|
|
}}
|
|
>
|
|
{section.label}
|
|
</Typography>
|
|
<List disablePadding>
|
|
{section.items.map((item) => (
|
|
<ListItemButton
|
|
key={item.path}
|
|
component={NavLink}
|
|
to={item.path}
|
|
end={item.end}
|
|
onClick={onNavigate}
|
|
selected={isItemActive(item, location.pathname)}
|
|
sx={{
|
|
borderRadius: 2,
|
|
mb: 0.25,
|
|
py: 0.6,
|
|
color: "text.secondary",
|
|
"& svg": { width: 18, height: 18 },
|
|
"&.Mui-selected, &.Mui-selected:hover": {
|
|
bgcolor: "background.paper",
|
|
color: "primary.main",
|
|
fontWeight: 700,
|
|
boxShadow: "0 2px 8px rgba(20,20,40,0.07)",
|
|
},
|
|
}}
|
|
>
|
|
<ListItemIcon sx={{ minWidth: 32, color: "inherit" }}>
|
|
{item.icon}
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary={item.label}
|
|
slotProps={{
|
|
primary: { fontSize: ".82rem", fontWeight: "inherit" },
|
|
}}
|
|
/>
|
|
</ListItemButton>
|
|
))}
|
|
</List>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
|
|
{/* Footer */}
|
|
<Box sx={{ pt: 1, mt: 1, borderTop: 1, borderColor: "divider" }}>
|
|
<Box
|
|
sx={{ display: "flex", alignItems: "center", gap: 1, px: 0.5, mb: 1 }}
|
|
>
|
|
<Avatar
|
|
sx={{
|
|
width: 34,
|
|
height: 34,
|
|
bgcolor: "primary.main",
|
|
fontSize: ".8rem",
|
|
fontWeight: 700,
|
|
fontFamily: "'Urbanist', sans-serif",
|
|
}}
|
|
>
|
|
{user?.fullName?.charAt(0) || user?.username?.charAt(0) || "U"}
|
|
</Avatar>
|
|
<Box sx={{ minWidth: 0 }}>
|
|
<Typography noWrap sx={{ fontSize: ".82rem", fontWeight: 600 }}>
|
|
{user?.fullName || user?.username}
|
|
</Typography>
|
|
<Typography
|
|
noWrap
|
|
sx={{ fontSize: ".7rem", color: "text.secondary" }}
|
|
>
|
|
{user?.roleDisplay}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
<Button
|
|
fullWidth
|
|
color="inherit"
|
|
onClick={onLogout}
|
|
startIcon={
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
width="18"
|
|
height="18"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
>
|
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
<polyline points="16 17 21 12 16 7" />
|
|
<line x1="21" y1="12" x2="9" y2="12" />
|
|
</svg>
|
|
}
|
|
>
|
|
Odhlásit se
|
|
</Button>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|