shoc-frontend-new/src/components/layout/app-topbar.tsx
Arthur Bassi a3e8a533b1 fix(ui): align shared board plumbing with ky v2 and react-router
- api.ts: use ky v2 hook object args, prefix (not prefixUrl), and normalizeApiRequestError
- replace react-router-dom with react-router in layout, topbar, and vendor-pos view
- move MUI style props (justifyContent, alignItems, flexWrap, display, fontWeight) into sx
- resource-list-table/query-key: use local types instead of missing work-order-list module
2026-07-17 13:31:56 -03:00

231 lines
6.6 KiB
TypeScript

import { useState } from "react";
import { useNavigate } from "react-router";
import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import {
AppBar,
Box,
Divider,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Toolbar,
Typography,
} from "@mui/material";
import { ChevronDown, Menu as MenuIcon } from "lucide-react";
import { useAuthContext } from "@/providers/auth-context";
import { getPrimaryUserRole } from "@/lib/auth/user-utils";
type AppTopbarProps = {
onToggleNav: () => void;
};
export function AppTopbar({ onToggleNav }: AppTopbarProps) {
const navigate = useNavigate();
const { user, logout } = useAuthContext();
const [userAnchor, setUserAnchor] = useState<null | HTMLElement>(null);
const handleLogout = () => {
setUserAnchor(null);
logout();
};
const userInitials = user?.fullname
? user.fullname
.split(" ")
.map((part) => part[0])
.join("")
.slice(0, 2)
.toUpperCase()
: "U";
const userRole = getPrimaryUserRole(user?.userRoles);
return (
<AppBar
position="sticky"
elevation={0}
square
sx={{
height: "var(--spacing-topbar)",
background: "var(--color-header-gradient)",
boxShadow: "none",
color: "var(--color-header-text)",
borderRadius: 0,
}}
>
<Toolbar
disableGutters
className="flex items-center"
sx={{
minHeight: "var(--spacing-topbar) !important",
height: "var(--spacing-topbar)",
gap: "18px",
px: "16px",
pr: "22px",
}}
>
<Box
component="button"
type="button"
onClick={onToggleNav}
aria-label="Toggle navigation"
title="Toggle sidebar"
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
width: 38,
height: 38,
borderRadius: "6px",
border: "none",
cursor: "pointer",
backgroundColor: "var(--color-header-surface)",
color: "var(--color-header-icon)",
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
"&:hover": {
backgroundColor: "var(--color-header-surface-hover)",
},
}}
>
<MenuIcon size={18} />
</Box>
<Box sx={{ flex: 1 }} />
<Box
component="button"
type="button"
aria-haspopup="true"
aria-expanded={Boolean(userAnchor)}
onClick={(event) => setUserAnchor(event.currentTarget)}
sx={{
display: "flex",
alignItems: "center",
flexShrink: 0,
gap: "10px",
py: "5px",
pl: "6px",
pr: "10px",
borderRadius: "9999px",
border: "none",
cursor: "pointer",
backgroundColor: "transparent",
color: "var(--color-header-text)",
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
"&:hover": {
backgroundColor: "var(--color-header-chip-hover)",
},
}}
>
<Box
className="flex shrink-0 items-center justify-center"
sx={{
width: 34,
height: 34,
borderRadius: "50%",
background: "linear-gradient(135deg, var(--accent), var(--primary))",
boxShadow: "0 0 0 2px var(--color-header-avatar-ring)",
fontFamily: "var(--font-display)",
fontSize: 12,
fontWeight: 700,
color: "#ffffff",
}}
>
{userInitials}
</Box>
<Box className="text-left leading-tight">
<Typography
component="p"
sx={{
fontFamily: "var(--font-sans)",
fontSize: 13,
fontWeight: 600,
color: "#ffffff",
lineHeight: 1.25,
}}
>
{user?.fullname || "User"}
</Typography>
<Typography
component="p"
sx={{
fontSize: 11,
color: "var(--color-header-text-muted)",
lineHeight: 1.25,
}}
>
{userRole}
</Typography>
</Box>
<ChevronDown
size={15}
className="shrink-0"
style={{ color: "var(--color-header-text-muted)" }}
/>
</Box>
</Toolbar>
<Menu
anchorEl={userAnchor}
open={Boolean(userAnchor)}
onClose={() => setUserAnchor(null)}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
slotProps={{
paper: {
sx: { width: 260, mt: 1, boxShadow: "var(--shadow-lg)" },
},
}}
>
<Box className="flex items-center gap-3 px-4 py-3">
<Box
className="flex shrink-0 items-center justify-center"
sx={{
width: 40,
height: 40,
borderRadius: "50%",
background: "linear-gradient(135deg, var(--accent), var(--primary))",
fontSize: "0.875rem",
fontWeight: 600,
color: "#ffffff",
border: "2px solid var(--color-header-avatar-ring)",
}}
>
{userInitials}
</Box>
<Box>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{user?.fullname || "User"}
</Typography>
<Typography variant="caption" color="text.secondary">
{user?.email || ""}
</Typography>
</Box>
</Box>
<Divider />
<MenuItem
onClick={() => {
setUserAnchor(null);
navigate("/settings/dropdowns");
}}
>
<ListItemIcon>
<SettingsOutlinedIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Settings</ListItemText>
</MenuItem>
<MenuItem onClick={handleLogout} sx={{ color: "error.main" }}>
<ListItemIcon>
<LogoutOutlinedIcon fontSize="small" color="error" />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</MenuItem>
</Menu>
</AppBar>
);
}