diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingMileServicesCard.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingMileServicesCard.tsx
index 2b09618df..3236e5cae 100644
--- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingMileServicesCard.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingMileServicesCard.tsx
@@ -17,7 +17,7 @@ export function BookingMileServicesCard({ booking }: BookingMileServicesCardProp
}
return (
-
+
{booking.firstMilePickupAddress && (
diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRequestHero.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRequestHero.tsx
index 193ea5daa..67a7fbc52 100644
--- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRequestHero.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRequestHero.tsx
@@ -1,13 +1,28 @@
-import { Building2, Calendar, Clock, RefreshCw, ArrowLeft } from "lucide-react";
-import { Paper, Group, Stack, Title, Text, Button, Box } from "@mantine/core";
+import type { ReactNode } from "react";
+import {
+ ArrowLeft,
+ Building2,
+ Calendar,
+ Clock,
+ Container as ContainerIcon,
+ Flame,
+ RefreshCw,
+ Wallet,
+ Weight,
+} from "lucide-react";
+import { Box, Button, Group, Paper, Stack, Text, Title } from "@mantine/core";
+import type { LucideIcon } from "lucide-react";
import type { BookingDetail } from "@/types/booking";
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge";
import { SchedulingStatusBadge } from "@/components/trainScheduling/ScheduleStatusBadge";
import { NextStepBanner } from "@/components/bookings/NextStepBanner";
+import { freightBrand } from "@/theme/freight-brand";
-import { detailStyles, formatDate } from "./booking-detail.styles";
+import { formatDate } from "./booking-detail.styles";
+
+const HERO_GRADIENT = `linear-gradient(135deg, ${freightBrand.primaryDark} 0%, ${freightBrand.primary} 48%, ${freightBrand.primaryLight} 120%)`;
export interface BookingRequestHeroProps {
booking: BookingDetail;
@@ -17,7 +32,7 @@ export interface BookingRequestHeroProps {
isFetching?: boolean;
}
-/** Top hero for the request detail page: identity, status, next step, total value. */
+/** Top hero for the request detail page: identity, status, next step, key figures. */
export function BookingRequestHero({
booking,
customerLabel,
@@ -26,93 +41,198 @@ export function BookingRequestHero({
isFetching,
}: BookingRequestHeroProps) {
const amount = Number(booking.totalAmount);
+ const containers = booking.bookingContainers ?? [];
+ const containerCount = containers.reduce(
+ (sum, c) => sum + Number(c.quantity ?? 0),
+ 0,
+ );
+ const weight = Number(booking.cargoTotalWeightVgm ?? 0);
return (
-
- }
- onClick={onBack}
- mb="md"
- ml={-8}
- fw={600}
- >
- Back to list
-
+
+
-
-
-
- Booking reference
-
-
-
- {booking.reference}
-
-
-
- {booking.schedulingStatus ? (
-
- ) : null}
-
- {booking.holdExpiresAt && booking.schedulingStatus === "HOLDING" ? (
-
- Hold expires {new Date(booking.holdExpiresAt).toLocaleString()}
-
- ) : null}
-
- {booking.nextStep && (
-
-
-
- )}
-
-
-
-
-
- {customerLabel}
-
-
-
-
-
- Scheduled {booking.scheduledDate}
-
-
-
-
-
- Created {formatDate(booking.createdAt)}
-
-
-
-
-
-
-
-
- Total value
-
-
- {booking.paymentCurrency}{" "}
- {amount.toLocaleString(undefined, { minimumFractionDigits: 2 })}
-
-
- {booking.paymentStatus}
-
-
+
+
}
+ onClick={onBack}
+ style={{ background: "rgba(255,255,255,0.15)", border: "1px solid rgba(255,255,255,0.25)" }}
+ >
+ Back to list
+
+ }
loading={isFetching}
onClick={onRefresh}
>
Refresh
+
+
+
+
+
+ Booking reference
+
+
+
+ {booking.reference}
+
+
+
+ {booking.schedulingStatus ? (
+
+ ) : null}
+
+
+ {booking.holdExpiresAt && booking.schedulingStatus === "HOLDING" ? (
+
+ Hold expires {new Date(booking.holdExpiresAt).toLocaleString()}
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+ {booking.nextStep ? (
+
+
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function MetaItem({
+ icon: Icon,
+ text,
+ strong,
+}: {
+ icon: LucideIcon;
+ text: ReactNode;
+ strong?: boolean;
+}) {
+ return (
+
+
+
+ {text}
+
+
+ );
+}
+
+function HeroTile({
+ icon: Icon,
+ label,
+ value,
+ hint,
+}: {
+ icon: LucideIcon;
+ label: string;
+ value: ReactNode;
+ hint?: ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+
+ {label}
+
+
+ {value}
+
+ {hint ? (
+
+ {hint}
+
+ ) : null}
diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingReviewNotesCard.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingReviewNotesCard.tsx
index 6f2f1f76f..218f0c7c5 100644
--- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingReviewNotesCard.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingReviewNotesCard.tsx
@@ -12,7 +12,7 @@ export interface BookingReviewNotesCardProps {
export function BookingReviewNotesCard({ notes }: BookingReviewNotesCardProps) {
if (notes.length === 0) {
return (
-
+
No review notes have been added yet.
diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteCard.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteCard.tsx
index 964d83803..05fa58100 100644
--- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteCard.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteCard.tsx
@@ -10,7 +10,7 @@ export interface BookingRouteCardProps {
export function BookingRouteCard({ booking }: BookingRouteCardProps) {
return (
-
+
{/* Origin */}
diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteServiceCard.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteServiceCard.tsx
index ea2aa491e..a4976ec61 100644
--- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteServiceCard.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteServiceCard.tsx
@@ -63,7 +63,7 @@ export function BookingRouteServiceCard({
];
return (
-
+
diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/SectionCard.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/SectionCard.tsx
index 1eadf24bc..1756600c9 100644
--- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/SectionCard.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/SectionCard.tsx
@@ -7,20 +7,68 @@ import { detailStyles } from "./booking-detail.styles";
export interface SectionCardProps {
icon: LucideIcon;
title: string;
+ /** Optional one-line context shown under the title. */
+ subtitle?: string;
+ /** Mantine palette key used to tint the icon chip + top accent (default green). */
+ accent?: string;
extra?: ReactNode;
children: ReactNode;
}
-/** Consistent flat card with a minimal icon + title header used by every detail section. */
-export function SectionCard({ icon: Icon, title, extra, children }: SectionCardProps) {
+/** Consistent card with a colored icon chip + accent stripe header used by every detail section. */
+export function SectionCard({
+ icon: Icon,
+ title,
+ subtitle,
+ accent = "green",
+ extra,
+ children,
+}: SectionCardProps) {
return (
-
-
-
-
-
- {title}
-
+
+
+
+
+
+
+
+
+
+ {title}
+
+ {subtitle ? (
+
+ {subtitle}
+
+ ) : null}
+
{extra}
diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css b/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css
new file mode 100644
index 000000000..f360d4139
--- /dev/null
+++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css
@@ -0,0 +1,126 @@
+/* ============================================================
+ EDR Freight — Header styles
+ ============================================================ */
+
+.fdh-root {
+ display: flex;
+ height: 80px;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 0 22px;
+}
+
+/* eyebrow above the page title */
+.fdh-eyebrow {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.6px;
+ text-transform: uppercase;
+ color: #15803d;
+ margin-bottom: 3px;
+}
+.fdh-eyebrow-dot {
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ background: #22c55e;
+ box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16);
+}
+
+/* action icon buttons */
+.fdh-icon-btn {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: 12px;
+ background: #f7f9fb;
+ border: 1px solid #eef1f4;
+ color: #475569;
+ cursor: pointer;
+ transition: all 160ms ease;
+}
+.fdh-icon-btn:hover {
+ background: #ffffff;
+ border-color: rgba(21, 128, 61, 0.28);
+ color: #15803d;
+ box-shadow: 0 4px 12px -4px rgba(21, 128, 61, 0.28);
+ transform: translateY(-1px);
+}
+.fdh-icon-btn:active {
+ transform: translateY(0);
+}
+
+/* notification badge */
+.fdh-badge {
+ position: absolute;
+ top: -5px;
+ right: -5px;
+ min-width: 17px;
+ height: 17px;
+ padding: 0 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 9px;
+ background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
+ color: #ffffff;
+ font-size: 10px;
+ font-weight: 700;
+ line-height: 1;
+ border: 2px solid #ffffff;
+ box-shadow: 0 2px 6px -1px rgba(239, 68, 68, 0.45);
+}
+
+.fdh-divider {
+ width: 1px;
+ height: 30px;
+ background: #e9eef3;
+ margin: 0 2px;
+}
+
+/* user button */
+.fdh-user {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 5px 12px 5px 5px;
+ border-radius: 13px;
+ cursor: pointer;
+ border: 1px solid transparent;
+ transition: all 160ms ease;
+}
+.fdh-user:hover {
+ background: #f7f9fb;
+ border-color: #eef1f4;
+}
+
+.fdh-avatar-ring {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ padding: 2px;
+ background: linear-gradient(135deg, #22c55e 0%, #15803d 100%);
+ box-shadow: 0 4px 10px -3px rgba(21, 128, 61, 0.4);
+}
+.fdh-avatar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #16a34a 0%, #166534 100%);
+ color: #ffffff;
+ font-size: 13px;
+ font-weight: 700;
+ letter-spacing: 0.3px;
+ border: 2px solid #ffffff;
+}
diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.tsx b/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.tsx
index ba69363d4..14336e70a 100644
--- a/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.tsx
@@ -9,10 +9,10 @@ import {
Sun,
User,
} from "lucide-react";
-import { Group, Stack, Text, Avatar, Menu, ActionIcon, Badge, Box } from "@mantine/core";
+import { Group, Stack, Text, Menu, Tooltip, Box } from "@mantine/core";
import type { PageMeta } from "./types";
-import { freightBrand } from "@/theme/freight-brand";
+import "./FreightDashboardHeader.css";
export interface FreightDashboardHeaderProps {
pageMeta: PageMeta;
@@ -39,12 +39,13 @@ const FreightDashboardHeader = ({
}: FreightDashboardHeaderProps) => {
const initials =
userInitials ??
- userName
+ (userName
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((n) => n[0].toUpperCase())
- .join("");
+ .join("") ||
+ "U");
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
const userMenuRef = useRef(null);
@@ -74,133 +75,137 @@ const FreightDashboardHeader = ({
}, [isUserMenuOpen]);
return (
-
-
-
+
+
+
+
+ Freight Backoffice
+
+
{pageMeta.title}
-
+
{pageMeta.subtitle}
-
+
{enableThemeToggle && (
-
- {theme === "dark" ? : }
-
+
+
)}
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
);
}
if (!child.href) return null;
- const childHref = child.href.toLowerCase();
- const childActiveHref = isHrefActive(childHref);
+ const childActive = isHrefActive(child.href);
return (
-