mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 10:53:13 +00:00
No Vendor opens the work order form on vendor assignment and Vendor Conflict links each work order, as the vendor reminders did. Header baselines include the notification bell, and the top bar user menu moves to its own component to stay within the function-size gate. The vendor e2e now covers the feed contract.
189 lines
5.1 KiB
TypeScript
189 lines
5.1 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 {
|
|
Box,
|
|
Divider,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Menu,
|
|
MenuItem,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import { ChevronDown } from "lucide-react";
|
|
|
|
import { useAuthContext } from "@/providers/auth-context";
|
|
import { getPrimaryUserRole } from "@/lib/auth/user-utils";
|
|
|
|
type UserMenuHeaderProps = {
|
|
initials: string;
|
|
fullname: string;
|
|
email: string;
|
|
};
|
|
|
|
function UserMenuHeader({ initials, fullname, email }: UserMenuHeaderProps) {
|
|
return (
|
|
<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)",
|
|
}}
|
|
>
|
|
{initials}
|
|
</Box>
|
|
<Box>
|
|
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
|
{fullname}
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
{email}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export function AppTopbarUserMenu() {
|
|
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 (
|
|
<>
|
|
<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>
|
|
|
|
<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)" },
|
|
},
|
|
}}
|
|
>
|
|
<UserMenuHeader
|
|
initials={userInitials}
|
|
fullname={user?.fullname || "User"}
|
|
email={user?.email || ""}
|
|
/>
|
|
<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>
|
|
</>
|
|
);
|
|
}
|