diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx index bbdc9e075..c7f532154 100644 --- a/apps/edr-freight-web/portal/src/App.tsx +++ b/apps/edr-freight-web/portal/src/App.tsx @@ -8,6 +8,7 @@ import { Settings, User, } from "lucide-react"; +import { useEffect } from "react"; import { Outlet, Route, @@ -18,7 +19,16 @@ import { import useAuth from "./hooks/useAuth"; -import { useEffect } from "react"; +function LogoutHandler() { + const { logout } = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + logout().then(() => navigate("/login", { replace: true })); + }, []); + + return null; +} import EDRFreightLandingPage from "./pages/EDRFreightLandingPage"; import MyPortalPage from "./pages/MyPortalPage"; import ProfilePage from "./pages/ProfilePage"; @@ -71,7 +81,7 @@ const sidebarItems: SidebarItem[] = [ const App = () => { const navigate = useNavigate(); const location = useLocation(); - const { user, isPending, logout, customer, customerQuery } = useAuth(); + const { user, isPending, customer, customerQuery } = useAuth(); useEffect(() => { if (isPending || customerQuery.isPending) return; @@ -103,6 +113,7 @@ const App = () => { } /> + } /> } /> } /> } /> @@ -123,7 +134,6 @@ const App = () => { enableThemeToggle userName={displayName} userEmail={userEmail} - onLogout={logout} > diff --git a/apps/edr-freight-web/portal/src/components/AppLayout.tsx b/apps/edr-freight-web/portal/src/components/AppLayout.tsx index b66bbd85b..8188689c4 100644 --- a/apps/edr-freight-web/portal/src/components/AppLayout.tsx +++ b/apps/edr-freight-web/portal/src/components/AppLayout.tsx @@ -10,6 +10,9 @@ import { Stack, Text, UnstyledButton, + useComputedColorScheme, + useMantineColorScheme, + useMantineTheme, } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { @@ -18,11 +21,14 @@ import { LogOut, Menu as MenuIcon, Moon, + Plus, Search, + Settings, Sun, User, + X, } from "lucide-react"; -import { type CSSProperties, Fragment, type ReactNode, useState } from "react"; +import { type CSSProperties, Fragment, type ReactNode } from "react"; export interface SidebarItem { label: string; @@ -40,32 +46,9 @@ export interface AppLayoutProps { enableThemeToggle?: boolean; userName?: string; userEmail?: string; - onLogout?: () => void; children: ReactNode; } -type Theme = "light" | "dark"; -const THEME_KEY = "edr-theme"; - -const EDR = { - primary: "#0EA371", - primaryDark: "#0A6F4D", - soft: "#ECF6F1", - border: "#E6ECF2", - bg: "#F4F7FA", - text: "#10202F", - muted: "#6B7C8E", - ink: "#0C1A2B", - accent: "#F2A516", -}; - -function getStoredTheme(): Theme { - if (typeof window === "undefined") return "light"; - const stored = localStorage.getItem(THEME_KEY); - if (stored === "dark" || stored === "light") return stored; - return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light"; -} - function getInitials(name: string): string { return name .split(" ") @@ -75,15 +58,23 @@ function getInitials(name: string): string { .join(""); } -function getActivePage(items: SidebarItem[], activePath: string): { label: string } | null { +function getActivePage( + items: SidebarItem[], + activePath: string, +): { label: string } | null { const path = activePath.toLowerCase(); for (const item of items) { - if (path === item.href.toLowerCase() || path.startsWith(item.href.toLowerCase() + "/")) { + if ( + path === item.href.toLowerCase() || + path.startsWith(item.href.toLowerCase() + "/") + ) { return { label: item.label }; } if (item.children) { const childMatch = item.children.find( - (c) => path === c.href.toLowerCase() || path.startsWith(c.href.toLowerCase() + "/"), + (c) => + path === c.href.toLowerCase() || + path.startsWith(c.href.toLowerCase() + "/"), ); if (childMatch) return { label: childMatch.label }; } @@ -101,8 +92,8 @@ const navClassNames = (active: boolean) => { } return { root: `rounded-[10px] font-medium transition-all duration-150 hover:bg-[#F1F4F7]!`, - label: `text-[#3D4D5C]! font-semibold! hover:text-[#0C1A2B]!`, - section: `text-[#54657A]! hover:text-[#0C1A2B]!`, + label: `text-edr-text! font-semibold! hover:text-[#0C1A2B]!`, + section: `text-edr-text! hover:text-[#0C1A2B]!`, }; }; @@ -114,22 +105,26 @@ export function AppLayout({ enableThemeToggle = false, userName = "User", userEmail, - onLogout, children, }: AppLayoutProps) { const [mobileOpen, { toggle: toggleMobile }] = useDisclosure(); - const [theme, setTheme] = useState(() => - enableThemeToggle ? getStoredTheme() : "light", - ); + const theme = useMantineTheme(); + const { setColorScheme } = useMantineColorScheme(); + const computedColorScheme = useComputedColorScheme("light"); + + const borderColor = theme.colors["edr-border"][6]; + const mutedColor = theme.colors["edr-muted"][6]; + const textColor = theme.colors["edr-text"][6]; + const accentColor = theme.colors["edr-accent"][6]; + const bgColor = theme.colors["edr-bg"][6]; + const primaryColor = theme.colors["edr-green"][5]; + const primaryDarkColor = theme.colors["edr-green"][7]; const activePath = activeHref.toLowerCase(); const navigate = (href: string) => onNavigate?.(href); const toggleTheme = () => { - const next: Theme = theme === "dark" ? "light" : "dark"; - setTheme(next); - document.documentElement.classList.toggle("dark", next === "dark"); - localStorage.setItem(THEME_KEY, next); + setColorScheme(computedColorScheme === "dark" ? "light" : "dark"); }; const initials = getInitials(userName); @@ -144,7 +139,7 @@ export function AppLayout({ width: 36, height: 36, borderRadius: 999, - border: `1px solid ${EDR.border}`, + border: `1px solid ${borderColor}`, backgroundColor: "#fff", display: "flex", alignItems: "center", @@ -157,8 +152,12 @@ export function AppLayout({ return ( {/* ── Header (frosted glass; compact floating islands, mirrors the pen) ── */} @@ -169,21 +168,32 @@ export function AppLayout({ WebkitBackdropFilter: "blur(14px)", border: "none", boxShadow: "none", - background:'transparent', + background: "transparent", }} > - + {/* Left: sidebar toggle + page title */} - - + + @@ -202,14 +212,14 @@ export function AppLayout({ width: 260, height: 36, borderRadius: 999, - border: `1px solid ${EDR.border}`, + border: `1px solid ${borderColor}`, backgroundColor: "#fff", padding: "0 14px", cursor: "text", }} > - - + + Search shipments, bookings… @@ -217,7 +227,7 @@ export function AppLayout({ {/* Bell */} - + {enableThemeToggle && ( - - {theme === "dark" - ? - : } + + {computedColorScheme === "dark" ? ( + + ) : ( + + )} )} {/* Avatar pill */} - + - {initials} + + {initials} + - + - {userName} - {userEmail && {userEmail}} + + {userName} + + {userEmail && ( + + {userEmail} + + )} - } onClick={() => navigate("/profile")}> + } + onClick={() => navigate("/profile")} + > Profile - } color="red" onClick={onLogout}> + } + onClick={() => navigate("/settings")} + > + Settings + + + } + color="edr-green" + onClick={() => navigate("/bookings/new")} + > + New Booking + + + } + color="red" + onClick={() => navigate("/logout")} + > Logout @@ -288,7 +346,7 @@ export function AppLayout({ withBorder={false} style={{ backgroundColor: "#ffffff", - borderRight: `1px solid ${EDR.border}`, + borderRight: `1px solid ${borderColor}`, display: "flex", flexDirection: "column", }} @@ -301,13 +359,19 @@ export function AppLayout({ display: "flex", alignItems: "center", padding: "0 18px", + justifyContent: "space-between", }} > EDR @@ -326,7 +390,7 @@ export function AppLayout({ style={{ fontSize: 10.5, fontWeight: 500, - color: EDR.muted, + color: mutedColor, lineHeight: 1.2, }} > @@ -334,6 +398,13 @@ export function AppLayout({ + + + {/* Nav */} @@ -343,7 +414,9 @@ export function AppLayout({ const active = isItemActive(item); const hasChildren = !!item.children?.length; const childActive = - item.children?.some((c) => activePath.startsWith(c.href.toLowerCase())) ?? false; + item.children?.some((c) => + activePath.startsWith(c.href.toLowerCase()), + ) ?? false; const prevSection = sidebarItems[i - 1]?.section; const sectionLabel = @@ -357,9 +430,8 @@ export function AppLayout({ mb={4} style={{ fontWeight: 600, - letterSpacing: "0.08em", - color: EDR.muted, - fontSize: 11, + color: textColor, + fontSize: 12, }} > {item.section} @@ -427,9 +499,9 @@ export function AppLayout({ alt="" style={{ position: "absolute", - bottom:"-16px", - left:0, - right:"-70px", + bottom: "-16px", + left: 0, + right: "-70px", width: "120%", objectFit: "cover", }} @@ -440,18 +512,40 @@ export function AppLayout({ style={{ position: "absolute", inset: 0, - top:"-75%", - background: "linear-gradient(165deg, #006149 40%, #0DC9A4 70%, #0DC9A402 80%)", + top: "-75%", + background: + "linear-gradient(165deg, #006149 40%, #0DC9A4 70%, #0DC9A402 80%)", clipPath: "polygon(0 0, 100% 0, 100% 58%, 0 72%)", }} /> {/* Text sits on top of the green overlay */} - - + + Moving Africa Forward - + Reliable. Efficient. Connected. @@ -472,8 +566,16 @@ export function AppLayout({ cursor: "pointer", }} > - Learn More - + + Learn More + + @@ -481,7 +583,7 @@ export function AppLayout({ - {initials} + + {initials} + - + {userName} {userEmail && ( - + {userEmail} )} - + @@ -515,7 +632,7 @@ export function AppLayout({ {/* ── Main ── */} = { - DRAFT: { title: "Draft — not submitted", description: "This booking is being prepared and hasn’t been submitted for review yet.", stage: 0 }, - CHANGES_REQUESTED: { title: "Changes requested", description: "Staff has requested changes. Please review and resubmit.", stage: 0 }, - SUBMITTED: { title: "Submitted for review", description: "Your booking has been submitted and is awaiting review.", stage: 1 }, - PENDING_APPROVAL: { title: "Pending approval", description: "Your booking is moving through the approval process.", stage: 1 }, - APPROVED_PENDING_SIGNATURE: { title: "Approved — awaiting signature", description: "Approved. Your contract will be ready to sign shortly.", stage: 2 }, - APPROVED: { title: "Approved", description: "Your booking has been fully approved.", stage: 2 }, - CONTRACT_READY: { title: "Contract ready to sign", description: "Your contract is ready. Review and apply your signature to proceed.", stage: 2 }, - SIGNED_CUSTOMER: { title: "Signed — awaiting staff", description: "Your signature has been submitted. Awaiting the final staff signature.", stage: 2 }, - FULLY_EXECUTED: { title: "Contract fully executed", description: "Signed by all parties. You can now proceed to payment.", stage: 2 }, - PNR_GENERATED: { title: "Payment reference generated", description: "A payment reference number has been generated for this booking.", stage: 3 }, - PAYMENT_VERIFICATION_IN_PROGRESS: { title: "Verifying payment", description: "Your payment is being verified.", stage: 3 }, - PAID: { title: "Payment confirmed", description: "Payment has been confirmed for this booking.", stage: 3 }, - IN_TRANSIT: { title: "Cargo moving", description: "Your shipment is currently moving through the rail network.", stage: 3 }, - PENDING_CONSOLIDATION: { title: "Pending consolidation", description: "Awaiting a consolidation partner shipment.", stage: 3 }, - CONSOLIDATED: { title: "Consolidated", description: "Cargo has been consolidated with a partner shipment.", stage: 3 }, - COMPLETED: { title: "Service complete", description: "Cargo delivered and service successfully terminated.", stage: 4 }, - DELIVERED: { title: "Service complete", description: "Cargo delivered and service successfully terminated.", stage: 4 }, - REJECTED: { title: "Booking rejected", description: "This booking request has been rejected.", stage: -1 }, - CANCELLED: { title: "Booking cancelled", description: "This booking process has been terminated.", stage: -1 }, +const STATUS_MAP: Record< + string, + { title: string; description: string; stage: number } +> = { + DRAFT: { + title: "Draft — not submitted", + description: + "This booking is being prepared and hasn’t been submitted for review yet.", + stage: 0, + }, + CHANGES_REQUESTED: { + title: "Changes requested", + description: "Staff has requested changes. Please review and resubmit.", + stage: 0, + }, + SUBMITTED: { + title: "Submitted for review", + description: "Your booking has been submitted and is awaiting review.", + stage: 1, + }, + PENDING_APPROVAL: { + title: "Pending approval", + description: "Your booking is moving through the approval process.", + stage: 1, + }, + APPROVED_PENDING_SIGNATURE: { + title: "Approved — awaiting signature", + description: "Approved. Your contract will be ready to sign shortly.", + stage: 2, + }, + APPROVED: { + title: "Approved", + description: "Your booking has been fully approved.", + stage: 2, + }, + CONTRACT_READY: { + title: "Contract ready to sign", + description: + "Your contract is ready. Review and apply your signature to proceed.", + stage: 2, + }, + SIGNED_CUSTOMER: { + title: "Signed — awaiting staff", + description: + "Your signature has been submitted. Awaiting the final staff signature.", + stage: 2, + }, + FULLY_EXECUTED: { + title: "Contract fully executed", + description: "Signed by all parties. You can now proceed to payment.", + stage: 2, + }, + PNR_GENERATED: { + title: "Payment reference generated", + description: + "A payment reference number has been generated for this booking.", + stage: 3, + }, + PAYMENT_VERIFICATION_IN_PROGRESS: { + title: "Verifying payment", + description: "Your payment is being verified.", + stage: 3, + }, + PAID: { + title: "Payment confirmed", + description: "Payment has been confirmed for this booking.", + stage: 3, + }, + IN_TRANSIT: { + title: "Cargo moving", + description: "Your shipment is currently moving through the rail network.", + stage: 3, + }, + PENDING_CONSOLIDATION: { + title: "Pending consolidation", + description: "Awaiting a consolidation partner shipment.", + stage: 3, + }, + CONSOLIDATED: { + title: "Consolidated", + description: "Cargo has been consolidated with a partner shipment.", + stage: 3, + }, + COMPLETED: { + title: "Service complete", + description: "Cargo delivered and service successfully terminated.", + stage: 4, + }, + DELIVERED: { + title: "Service complete", + description: "Cargo delivered and service successfully terminated.", + stage: 4, + }, + REJECTED: { + title: "Booking rejected", + description: "This booking request has been rejected.", + stage: -1, + }, + CANCELLED: { + title: "Booking cancelled", + description: "This booking process has been terminated.", + stage: -1, + }, }; const REQUIRED_DOC_FIELDS = [ @@ -102,7 +218,9 @@ function containerSummary(b: Freight.IBooking) { } function bookingSubtitle(b: Freight.IBooking) { - const cargo = b.freightSubtype || (b.freightType === "BULK" ? "Bulk freight" : "Container freight"); + const cargo = + b.freightSubtype || + (b.freightType === "BULK" ? "Bulk freight" : "Container freight"); const load = containerSummary(b); const route = `${yardLabel(b.originYard)} → ${yardLabel(b.destinationYard)}`; return [cargo, load, route].filter((p) => p && p !== "—").join(" · "); @@ -114,12 +232,19 @@ export default function BookingDetailPage() { const { id } = useParams<{ id: string }>(); const queryClient = useQueryClient(); - const { data: booking, isLoading, isError, error } = useQuery( + const { + data: booking, + isLoading, + isError, + error, + } = useQuery( api.bookings.get.queryOptions({ input: { id: id! }, enabled: !!id }), ); const refetchBooking = () => { - queryClient.invalidateQueries({ queryKey: api.bookings.get.queryKey({ id: id! }) }); + queryClient.invalidateQueries({ + queryKey: api.bookings.get.queryKey({ id: id! }), + }); }; if (isLoading) { @@ -127,7 +252,9 @@ export default function BookingDetailPage() {
- Loading booking details… + + Loading booking details… +
); @@ -145,7 +272,9 @@ export default function BookingDetailPage() { {isError && ( - {error instanceof Error ? error.message : "An unexpected error occurred."} + {error instanceof Error + ? error.message + : "An unexpected error occurred."} )} @@ -154,7 +283,9 @@ export default function BookingDetailPage() { } if (isDraftLike(booking.status)) { - return ; + return ( + + ); } return ; } @@ -173,7 +304,9 @@ function DraftBookingView({ const fileInputRefs = useRef>({}); const documentsRef = useRef(null); - const [selectedFiles, setSelectedFiles] = useState>({}); + const [selectedFiles, setSelectedFiles] = useState< + Record + >({}); const [cancelDialogOpen, setCancelDialogOpen] = useState(false); const [cancelReason, setCancelReason] = useState(""); const [docError, setDocError] = useState(""); @@ -183,7 +316,9 @@ function DraftBookingView({ () => new Set(booking.files?.map((f) => f.code) ?? []), [booking.files], ); - const uploadedCount = REQUIRED_DOC_FIELDS.filter((d) => uploadedCodes.has(d.key)).length; + const uploadedCount = REQUIRED_DOC_FIELDS.filter((d) => + uploadedCodes.has(d.key), + ).length; const allDocsUploaded = uploadedCount === REQUIRED_DOC_FIELDS.length; const { data: generatedPricing } = useQuery({ @@ -211,7 +346,8 @@ function DraftBookingView({ }); const cancelMutation = useMutation({ - mutationFn: (reason: string) => api.bookings.cancel.call({ id: booking.id, reason }), + mutationFn: (reason: string) => + api.bookings.cancel.call({ id: booking.id, reason }), onSuccess: () => { setCancelDialogOpen(false); onBookingUpdated(); @@ -225,14 +361,17 @@ function DraftBookingView({ function handleUploadAll() { const filesToUpload: Record = {}; for (const doc of REQUIRED_DOC_FIELDS) { - if (selectedFiles[doc.key]) filesToUpload[doc.key] = selectedFiles[doc.key]!; + if (selectedFiles[doc.key]) + filesToUpload[doc.key] = selectedFiles[doc.key]!; } if (Object.keys(filesToUpload).length === 0) return; uploadMutation.mutate(filesToUpload); } function handleSubmitRequest() { - const missing = REQUIRED_DOC_FIELDS.filter((doc) => !uploadedCodes.has(doc.key)); + const missing = REQUIRED_DOC_FIELDS.filter( + (doc) => !uploadedCodes.has(doc.key), + ); if (missing.length > 0) { setDocError("Please upload all required documents before submitting."); documentsRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -249,24 +388,28 @@ function DraftBookingView({ - } label="Documents" onClick={() => documentsRef.current?.scrollIntoView({ behavior: "smooth" })} /> - } - label="Continue editing" - onClick={() => navigate(`/bookings/${booking.id}/edit`)} - /> - + } + label="Continue editing" + onClick={() => navigate(`/bookings/${booking.id}/edit`)} + /> } /> - {booking.status === "CHANGES_REQUESTED" && booking.latestChangeRequestNote && ( - } title="Changes requested by staff"> - {booking.latestChangeRequestNote} - - )} - + {booking.status === "CHANGES_REQUESTED" && + booking.latestChangeRequestNote && ( + } + title="Changes requested by staff" + > + {booking.latestChangeRequestNote} + + )} + @@ -288,7 +431,9 @@ function DraftBookingView({ title="Booking details" desc="Route, cargo and service are set." action={ - navigate(`/bookings/${booking.id}/edit`)}> + navigate(`/bookings/${booking.id}/edit`)} + > Edit } @@ -306,7 +451,13 @@ function DraftBookingView({ } action={ !allDocsUploaded && ( - documentsRef.current?.scrollIntoView({ behavior: "smooth" })}> + + documentsRef.current?.scrollIntoView({ + behavior: "smooth", + }) + } + > Upload ) @@ -324,7 +475,13 @@ function DraftBookingView({ radius={10} mt="lg" color="dark" - styles={{ root: { backgroundColor: "#0C1A2B", height: 46, fontWeight: 700 } }} + styles={{ + root: { + backgroundColor: "#0C1A2B", + height: 46, + fontWeight: 700, + }, + }} leftSection={} onClick={handleSubmitRequest} disabled={submitMutation.isPending} @@ -341,12 +498,19 @@ function DraftBookingView({ Documents - + {docError && ( - } className="mb-3"> + } + className="mb-3" + > {docError} )} @@ -363,12 +527,14 @@ function DraftBookingView({ title={doc.label} meta={ isUploaded - ? file?.name ?? "Uploaded" + ? (file?.name ?? "Uploaded") : selected ? selected.name : "Required · not uploaded" } - status={isUploaded ? "verified" : selected ? "ready" : "missing"} + status={ + isUploaded ? "verified" : selected ? "ready" : "missing" + } action={ isUploaded ? ( { fileInputRefs.current[doc.key] = el; }} + ref={(el) => { + fileInputRefs.current[doc.key] = el; + }} type="file" accept=".pdf,.jpg,.jpeg,.png" className="hidden" - onChange={(e) => handleFileSelect(doc.key, e.target.files?.[0] ?? null)} + onChange={(e) => + handleFileSelect( + doc.key, + e.target.files?.[0] ?? null, + ) + } /> )} @@ -430,7 +611,11 @@ function DraftBookingView({ } right={ <> - + setCancelDialogOpen(true)} /> @@ -446,8 +631,8 @@ function DraftBookingView({ > - Are you sure you want to cancel {booking.reference}? This action - cannot be undone. + Are you sure you want to cancel {booking.reference} + ? This action cannot be undone. - @@ -492,7 +687,8 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) { }); const pricing = booking.pricingBreakdown; - const canPay = status === "FULLY_EXECUTED" && booking.paymentStatus !== "PAID"; + const canPay = + status === "FULLY_EXECUTED" && booking.paymentStatus !== "PAID"; return ( @@ -500,7 +696,6 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) { booking={booking} actions={ <> - } label="Documents" /> {canPay && ( } />} + action={ + } + /> + } /> ))} @@ -552,7 +752,11 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) { right={ <> - + } @@ -566,7 +770,7 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) { function PageShell({ children }: { children: ReactNode }) { return ( - + {children} @@ -577,17 +781,26 @@ function BodyGrid({ left, right }: { left: ReactNode; right: ReactNode }) { return (
{left}
-
{right}
+
+ {right} +
); } const SectionCard = (() => { - type Props = { children: ReactNode; className?: string; ref?: React.Ref }; + type Props = { + children: ReactNode; + className?: string; + ref?: React.Ref; + }; const Comp = ({ children, className, ref }: Props) => (
{children}
@@ -596,12 +809,22 @@ const SectionCard = (() => { })(); function CardTitle({ children }: { children: ReactNode }) { - return {children}; + return ( + + {children} + + ); } // ─── Page header ────────────────────────────────────────────────────────────── -function PageHeader({ booking, actions }: { booking: Freight.IBooking; actions: ReactNode }) { +function PageHeader({ + booking, + actions, +}: { + booking: Freight.IBooking; + actions: ReactNode; +}) { const status = booking.status as string; const negative = isNegative(status); const draft = isDraftLike(status); @@ -616,24 +839,39 @@ function PageHeader({ booking, actions }: { booking: Freight.IBooking; actions: - + {booking.reference} - + {status.replace(/_/g, " ").replace(/\b\w/g, (m) => m.toUpperCase())} - {isExport ? : } + {isExport ? ( + + ) : ( + + )} {isExport ? "Export" : "Import"} - {bookingSubtitle(booking)} + + {bookingSubtitle(booking)} + - {actions} + + {actions} + ); } @@ -653,14 +891,20 @@ function HeaderButton({ green?: boolean; disabled?: boolean; }) { - const base = "inline-flex items-center gap-2 rounded-[10px] px-4 py-[11px] text-[13px] font-bold transition-colors disabled:opacity-60"; + const base = + "inline-flex items-center gap-2 rounded-[10px] px-4 py-[11px] text-[13px] font-bold transition-colors disabled:opacity-60"; const variant = green ? "bg-[#0EA371] text-white hover:bg-[#0A8A5F]" : dark ? "bg-[#0C1A2B] text-white hover:bg-[#16273A]" : "border border-[#E6ECF2] bg-white text-[#10202F] hover:border-[#CBD5E1]"; return ( - @@ -675,16 +919,28 @@ function StatusHero({ booking }: { booking: Freight.IBooking }) { const negative = isNegative(status); const draft = isDraftLike(status); - const tone: "green" | "slate" | "red" = negative ? "red" : draft ? "slate" : "green"; - const tileBg = tone === "red" ? "#FBEAE7" : tone === "slate" ? "#F1F4F7" : "#ECF6F1"; - const tileFg = tone === "red" ? "#C0392B" : tone === "slate" ? "#475569" : "#0EA371"; - const HeroIcon = negative ? AlertTriangle : draft ? FileText : (PROGRESS_STAGES[cfg.stage]?.icon ?? History); + const tone: "green" | "slate" | "red" = negative + ? "red" + : draft + ? "slate" + : "green"; + const tileBg = + tone === "red" ? "#FBEAE7" : tone === "slate" ? "#F1F4F7" : "#ECF6F1"; + const tileFg = + tone === "red" ? "#C0392B" : tone === "slate" ? "#475569" : "#0EA371"; + const HeroIcon = negative + ? AlertTriangle + : draft + ? FileText + : (PROGRESS_STAGES[cfg.stage]?.icon ?? History); const chipLabel = draft ? "Last edited" : negative ? "Updated" : "Scheduled"; - const chipValue = fmtDate(draft || negative ? booking.updatedAt : booking.scheduledDate); + const chipValue = fmtDate( + draft || negative ? booking.updatedAt : booking.scheduledDate, + ); return ( - +
- {cfg.title} - {cfg.description} + + {cfg.title} + + + {cfg.description} +
- + - {chipLabel} - {chipValue} + + {chipLabel} + + + {chipValue} + - +
); } @@ -731,26 +1003,44 @@ function ProgressTracker({ return (
{PROGRESS_STAGES.map((stage, idx) => { - const state = idx < current ? "done" : idx === current ? "active" : "idle"; + const state = + idx < current ? "done" : idx === current ? "active" : "idle"; const Icon = stage.icon; const reachedLeft = current >= idx && current >= 0; const reachedRight = current > idx && current >= 0; return ( -
+
{state === "done" ? ( @@ -761,7 +1051,14 @@ function ProgressTracker({
- {state === "done" ? "Completed" : state === "active" ? (negative ? "Stopped" : "In progress") : "Pending"} + {state === "done" + ? "Completed" + : state === "active" + ? negative + ? "Stopped" + : "In progress" + : "Pending"}
); @@ -797,14 +1100,30 @@ function ShipmentDetailsCard({ booking }: { booking: Freight.IBooking }) { ], [ ["Containers / load", containerSummary(booking)], - ["Total weight (VGM)", booking.cargoTotalWeightVgm ? `${booking.cargoTotalWeightVgm} t` : "—"], + [ + "Total weight (VGM)", + booking.cargoTotalWeightVgm ? `${booking.cargoTotalWeightVgm} t` : "—", + ], ], [ - ["Service type", booking.serviceType === "RAIL_AND_FORWARDING" ? "Rail + Forwarding" : "Rail only"], - ["Equipment return", booking.equipmentReturn === "WITH_RETURN" ? "With return" : "Without return"], + [ + "Service type", + booking.serviceType === "RAIL_AND_FORWARDING" + ? "Rail + Forwarding" + : "Rail only", + ], + [ + "Equipment return", + booking.equipmentReturn === "WITH_RETURN" + ? "With return" + : "Without return", + ], ], [ - ["Trade direction", booking.tradeDirection === "IMPORT" ? "Import" : "Export"], + [ + "Trade direction", + booking.tradeDirection === "IMPORT" ? "Import" : "Export", + ], ["Scheduled date", fmtDate(booking.scheduledDate)], ], [ @@ -819,7 +1138,9 @@ function ShipmentDetailsCard({ booking }: { booking: Freight.IBooking }) { Shipment Details - {booking.contractType === "RENEWAL" ? "Renewal contract" : "New contract"} + {booking.contractType === "RENEWAL" + ? "Renewal contract" + : "New contract"} @@ -833,8 +1154,12 @@ function ShipmentDetailsCard({ booking }: { booking: Freight.IBooking }) { > {pair.map(([k, v]) => (
- {k} - {v} + + {k} + + + {v} +
))}
@@ -859,7 +1184,12 @@ function DocRow({ action: ReactNode; last?: boolean; }) { - const tileBg = status === "missing" ? "#F1F4F7" : status === "ready" ? "#EAF1FB" : "#F1F4F7"; + const tileBg = + status === "missing" + ? "#F1F4F7" + : status === "ready" + ? "#EAF1FB" + : "#F1F4F7"; const tileFg = status === "ready" ? "#2E5B96" : "#475569"; return ( @@ -876,7 +1206,9 @@ function DocRow({
- {title} + + {title} + {meta}
{status === "verified" && ( @@ -956,7 +1288,11 @@ function StepLine({ ? { backgroundColor: "#0EA371", color: "#fff" } : active ? { backgroundColor: "#0C1A2B", color: "#fff" } - : { backgroundColor: "#EEF2F6", color: "#9AA8B5", border: "1px solid #E1E7EE" } + : { + backgroundColor: "#EEF2F6", + color: "#9AA8B5", + border: "1px solid #E1E7EE", + } } > {done ? : index} @@ -970,7 +1306,13 @@ function StepLine({ ); } -function StepGhostButton({ children, onClick }: { children: ReactNode; onClick?: () => void }) { +function StepGhostButton({ + children, + onClick, +}: { + children: ReactNode; + onClick?: () => void; +}) { return (