mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 11:21:18 +00:00
206 lines
6.2 KiB
TypeScript
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;
|