Files
edr-platform/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.tsx
2026-07-06 06:52:09 +00:00

206 lines
6.2 KiB
TypeScript

import {
AppShell,
Avatar,
Box,
Burger,
Divider,
Group,
Menu,
Text,
Tooltip,
UnstyledButton,
} from "@mantine/core";
import {
ChevronDown,
FileSignature,
Languages,
LogOut,
Moon,
Search,
Sun,
User,
} from "lucide-react";
import { type ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import NotificationBellContainer from "@/features/notifications/NotificationBellContainer";
import type { PageMeta } from "./types";
export interface FreightDashboardHeaderProps {
pageMeta: PageMeta;
headerRight?: ReactNode;
enableThemeToggle?: boolean;
userName?: string;
userEmail?: string;
userInitials?: string;
onLogout?: () => void;
theme: "light" | "dark";
onToggleTheme: () => void;
mobileOpened: boolean;
onToggleMobile: () => void;
}
// Every header control is a consistent 36px frosted chip — same language as the
// portal AppLayout's floating "islands".
const ISLAND =
"flex size-9 shrink-0 items-center justify-center rounded-full border border-edr-border bg-white text-edr-text transition-colors hover:bg-[#F1F4F7]";
const FreightDashboardHeader = ({
headerRight,
enableThemeToggle = false,
userName = "User",
userEmail,
userInitials,
onLogout,
theme,
onToggleTheme,
mobileOpened,
onToggleMobile,
}: FreightDashboardHeaderProps) => {
const navigate = useNavigate();
const initials =
userInitials ??
(userName
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((n) => n[0].toUpperCase())
.join("") ||
"U");
return (
<AppShell.Header
withBorder={false}
// Frosted glass: fully transparent background + backdrop blur, mirroring
// the portal AppLayout. Inline styles win over Mantine's cascade layer
// (a Tailwind bg-transparent would lose to --mantine-color-body).
style={{
backdropFilter: "blur(14px)",
WebkitBackdropFilter: "blur(14px)",
border: "none",
boxShadow: "none",
background: "transparent",
}}
>
<Group h="100%" px={20} justify="space-between" wrap="nowrap">
{/* Left: burger (mobile) + search — the search now occupies the slot
the page title used to hold; each page owns its own title. */}
<Group gap={12} wrap="nowrap" align="center" style={{ minWidth: 0 }}>
<Burger
opened={mobileOpened}
onClick={onToggleMobile}
hiddenFrom="sm"
size="sm"
aria-label="Toggle sidebar"
/>
<Group
gap={8}
align="center"
visibleFrom="sm"
className="h-9 cursor-text rounded-full border border-edr-border bg-white px-3.5"
style={{ width: 280 }}
>
<Search size={15} className="text-edr-muted" strokeWidth={1.8} />
<Text size="sm" className="select-none text-edr-muted!">
Search bookings, trains
</Text>
</Group>
</Group>
{/* Right: actions + avatar */}
<Group gap={10} wrap="nowrap" align="center">
<Tooltip label="Language" withArrow openDelay={300}>
<UnstyledButton className={ISLAND} aria-label="Language">
<Languages size={17} strokeWidth={1.8} />
</UnstyledButton>
</Tooltip>
<NotificationBellContainer />
{enableThemeToggle && (
<Tooltip
label={theme === "dark" ? "Light mode" : "Dark mode"}
withArrow
openDelay={300}
>
<UnstyledButton
className={ISLAND}
onClick={onToggleTheme}
aria-label="Toggle theme"
>
{theme === "dark" ? (
<Sun size={17} strokeWidth={1.8} />
) : (
<Moon size={17} strokeWidth={1.8} />
)}
</UnstyledButton>
</Tooltip>
)}
{/* Avatar pill */}
<Menu width={240} position="bottom-end" withinPortal shadow="md" offset={8} radius="md">
<Menu.Target>
<UnstyledButton className="flex h-9 cursor-pointer items-center gap-2 rounded-full border border-edr-border bg-white py-0 pl-1 pr-2.5">
<Avatar radius="xl" size={28} color="edr-green.5">
<Text fw={700} fz={12} c="white">
{initials}
</Text>
</Avatar>
<Box visibleFrom="sm" style={{ minWidth: 0 }}>
<Text size="xs" fw={600} truncate className="text-edr-text!" style={{ lineHeight: 1.2, maxWidth: 120 }}>
{userName}
</Text>
<Text size="xs" truncate className="text-edr-muted!" style={{ lineHeight: 1.2, maxWidth: 120, fontSize: 11 }}>
{userEmail ?? "Administrator"}
</Text>
</Box>
<ChevronDown size={15} className="text-edr-muted" strokeWidth={1.8} />
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
<Box px="sm" py="xs">
<Text size="sm" fw={600} truncate className="text-edr-text!">
{userName}
</Text>
{userEmail && (
<Text size="xs" c="dimmed" truncate>
{userEmail}
</Text>
)}
</Box>
<Divider />
<Menu.Item
leftSection={<User size={15} />}
onClick={() => navigate("/dashboard/profile")}
>
Profile
</Menu.Item>
<Menu.Item
leftSection={<FileSignature size={15} />}
onClick={() => navigate("/dashboard/profile#signature")}
>
My signature
</Menu.Item>
<Divider />
<Menu.Item
leftSection={<LogOut size={15} />}
color="red"
onClick={() => onLogout?.()}
>
Logout
</Menu.Item>
</Menu.Dropdown>
</Menu>
{headerRight}
</Group>
</Group>
</AppShell.Header>
);
};
export default FreightDashboardHeader;