From 0335555892670078546c029f1eeb04a0f341b41c Mon Sep 17 00:00:00 2001 From: Marshal Date: Wed, 10 Jun 2026 07:53:15 +0000 Subject: [PATCH] overview, booking and train scheduling ui --- apps/edr-freight-web/backoffice/index.css | 12 +- .../components/bookings/BookingStatusTabs.tsx | 126 +++++------ .../layout/FreightDashboardHeader.css | 16 +- .../src/components/layout/FreightSidebar.css | 30 +-- .../src/components/layout/FreightSidebar.tsx | 4 +- .../components/overview/OverviewKpiCard.tsx | 201 ++++++++---------- .../components/overview/OverviewKpiStrip.tsx | 4 +- .../src/components/overview/overview.css | 6 +- .../components/overview/overview.styles.ts | 4 +- .../pages/bookings/BookingRequestsPage.tsx | 25 +-- .../src/pages/dashboard/OverviewPage.tsx | 2 +- .../backoffice/src/theme/freight-brand.ts | 34 +-- 12 files changed, 207 insertions(+), 257 deletions(-) diff --git a/apps/edr-freight-web/backoffice/index.css b/apps/edr-freight-web/backoffice/index.css index 8d40838ea..5dd466b0c 100644 --- a/apps/edr-freight-web/backoffice/index.css +++ b/apps/edr-freight-web/backoffice/index.css @@ -2,12 +2,12 @@ @import "@edr/ui-common/theme.css" layer(theme); :root { - --freight-brand: #15803d; - --freight-brand-dark: #166534; - --freight-brand-light: #22c55e; - --freight-brand-muted: #f0fdf4; - --freight-brand-border: #bbf7d0; - --freight-brand-ring: rgb(21 128 61 / 0.2); + --freight-brand: #1B9E7A; + --freight-brand-dark: #15805F; + --freight-brand-light: #2DBF95; + --freight-brand-muted: #E7F8F2; + --freight-brand-border: #B7EBDC; + --freight-brand-ring: rgb(27 158 122 / 0.2); } html, diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/BookingStatusTabs.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/BookingStatusTabs.tsx index 2743d0c31..ec0880189 100644 --- a/apps/edr-freight-web/backoffice/src/components/bookings/BookingStatusTabs.tsx +++ b/apps/edr-freight-web/backoffice/src/components/bookings/BookingStatusTabs.tsx @@ -8,22 +8,23 @@ import { Wallet, XCircle, } from "lucide-react"; -import { Group, Badge, UnstyledButton, Text } from "@mantine/core"; +import { Badge, Tabs } from "@mantine/core"; import { BOOKING_LIST_TABS, type BookingStatusTabKey, } from "@/features/bookings/booking-status.config"; +import "@/components/overview/overview.css"; const TAB_ICONS: Record = { - all: , - intake: , - in_approval: , - approved_contract: , - payment: , - operations: , - completed: , - closed: , + all: , + intake: , + in_approval: , + approved_contract: , + payment: , + operations: , + completed: , + closed: , }; interface BookingStatusTabsProps { @@ -38,74 +39,47 @@ export function BookingStatusTabs({ counts, }: BookingStatusTabsProps) { return ( - onChange((value as BookingStatusTabKey) ?? "all")} + variant="pills" + color="green" + keepMounted={false} + classNames={{ list: "ov-tablist", tab: "ov-tab" }} > - {BOOKING_LIST_TABS.map((tab) => { - const isActive = active === tab.key; - const count = counts?.[tab.key]; - return ( - onChange(tab.key)} - style={{ - flexShrink: 0, - background: isActive ? "white" : "transparent", - border: isActive ? "1px solid var(--freight-brand-border)" : "1px solid var(--mantine-color-gray-2)", - borderRadius: "10px", - padding: "10px 16px", - transition: "all 0.2s ease", - cursor: "pointer", - boxShadow: isActive ? "0 2px 8px rgb(21 128 61 / 0.12)" : "none", - }} - > - - -
- {TAB_ICONS[tab.key]} -
- - {tab.label} - -
- {count !== undefined && count > 0 && ( - - {count} - - )} -
-
- ); - })} -
+ + {BOOKING_LIST_TABS.map((tab) => { + const isActive = active === tab.key; + const count = counts?.[tab.key]; + return ( + + {count} + + ) : undefined + } + > + {tab.label} + + ); + })} + + ); } diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css b/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css index f360d4139..55410fe26 100644 --- a/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css +++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightDashboardHeader.css @@ -20,14 +20,14 @@ font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; - color: #15803d; + color: #1B9E7A; margin-bottom: 3px; } .fdh-eyebrow-dot { width: 5px; height: 5px; border-radius: 50%; - background: #22c55e; + background: #2DBF95; box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16); } @@ -48,9 +48,9 @@ } .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); + border-color: rgba(27, 158, 122, 0.28); + color: #1B9E7A; + box-shadow: 0 4px 12px -4px rgba(27, 158, 122, 0.28); transform: translateY(-1px); } .fdh-icon-btn:active { @@ -107,8 +107,8 @@ 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); + background: linear-gradient(135deg, #2DBF95 0%, #1B9E7A 100%); + box-shadow: 0 4px 10px -3px rgba(27, 158, 122, 0.4); } .fdh-avatar { display: flex; @@ -117,7 +117,7 @@ width: 34px; height: 34px; border-radius: 50%; - background: linear-gradient(135deg, #16a34a 0%, #166534 100%); + background: linear-gradient(135deg, #1B9E7A 0%, #15805F 100%); color: #ffffff; font-size: 13px; font-weight: 700; diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css index 961afb39c..8d14daad9 100644 --- a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css +++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css @@ -50,9 +50,9 @@ width: 44px; height: 44px; border-radius: 13px; - background: linear-gradient(135deg, #22c55e 0%, #15803d 60%, #166534 100%); + background: linear-gradient(135deg, #2DBF95 0%, #1B9E7A 60%, #15805F 100%); box-shadow: - 0 6px 16px -4px rgba(21, 128, 61, 0.45), + 0 6px 16px -4px rgba(27, 158, 122, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.25); flex-shrink: 0; } @@ -124,9 +124,9 @@ background: linear-gradient( 135deg, rgba(34, 197, 94, 0.12) 0%, - rgba(21, 128, 61, 0.06) 100% + rgba(27, 158, 122, 0.06) 100% ); - color: #15803d; + color: #1B9E7A; font-weight: 600; } @@ -139,7 +139,7 @@ width: 3px; height: 22px; border-radius: 0 4px 4px 0; - background: linear-gradient(180deg, #22c55e 0%, #15803d 100%); + background: linear-gradient(180deg, #2DBF95 0%, #1B9E7A 100%); } .fsb-item-label { @@ -170,9 +170,9 @@ } .fsb-item[data-active="true"] .fsb-icon { - background: linear-gradient(135deg, #22c55e 0%, #15803d 100%); + background: linear-gradient(135deg, #2DBF95 0%, #1B9E7A 100%); color: #ffffff; - box-shadow: 0 5px 12px -2px rgba(21, 128, 61, 0.45); + box-shadow: 0 5px 12px -2px rgba(27, 158, 122, 0.45); } .fsb-chevron { @@ -214,7 +214,7 @@ color: #94a3b8; } .fsb-group[data-active="true"] .fsb-group-label { - color: #15803d; + color: #1B9E7A; } /* child leaf */ @@ -240,9 +240,9 @@ color: #0f172a; } .fsb-child[data-active="true"] { - color: #15803d; + color: #1B9E7A; font-weight: 600; - background-color: rgba(21, 128, 61, 0.08); + background-color: rgba(27, 158, 122, 0.08); } .fsb-dot { @@ -257,8 +257,8 @@ background: #94a3b8; } .fsb-child[data-active="true"] .fsb-dot { - background: #15803d; - box-shadow: 0 0 0 3px rgba(21, 128, 61, 0.16); + background: #1B9E7A; + box-shadow: 0 0 0 3px rgba(27, 158, 122, 0.16); } /* ---- Footer status card ---- */ @@ -273,7 +273,7 @@ gap: 10px; padding: 10px 12px; border-radius: 11px; - background: linear-gradient(135deg, #f0fdf4 0%, #f8fafc 100%); + background: linear-gradient(135deg, #E7F8F2 0%, #f8fafc 100%); border: 1px solid #e7f3ec; } .fsb-pulse { @@ -281,7 +281,7 @@ width: 9px; height: 9px; border-radius: 50%; - background: #22c55e; + background: #2DBF95; flex-shrink: 0; } .fsb-pulse::after { @@ -289,7 +289,7 @@ position: absolute; inset: 0; border-radius: 50%; - background: #22c55e; + background: #2DBF95; animation: fsb-pulse 2s ease-out infinite; } @keyframes fsb-pulse { diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx index c33d9e269..708888c62 100644 --- a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx +++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx @@ -129,7 +129,7 @@ const FreightSidebar = ({ className="fsb-chevron" style={{ transform: isOpen ? "rotate(0deg)" : "rotate(-90deg)", - color: groupActive ? "#15803d" : undefined, + color: groupActive ? "#1B9E7A" : undefined, }} /> @@ -248,7 +248,7 @@ const FreightSidebar = ({
- + All systems operational diff --git a/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiCard.tsx b/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiCard.tsx index 25e63b163..43108ffd7 100644 --- a/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiCard.tsx +++ b/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiCard.tsx @@ -1,4 +1,3 @@ -import { useId } from "react"; import type { LucideIcon } from "lucide-react"; import { Card, Group, Stack, Text } from "@mantine/core"; @@ -7,20 +6,14 @@ import { type OverviewAccent, } from "./overview.styles"; -const accentColors = { - default: { bg: "var(--mantine-color-gray-1)", color: "var(--mantine-color-gray-6)" }, - amber: { bg: "var(--mantine-color-yellow-1)", color: "var(--mantine-color-yellow-6)" }, - emerald: { bg: "var(--freight-brand-muted)", color: "var(--freight-brand)" }, - rose: { bg: "var(--mantine-color-red-1)", color: "var(--mantine-color-red-6)" }, - sky: { bg: "var(--mantine-color-blue-1)", color: "var(--mantine-color-blue-6)" }, -}; - -const ACCENT_TINT: Record = { - default: "#f8fafc", - amber: "#fffbeb", - emerald: "#f0fdf4", - rose: "#fff1f2", - sky: "#f0f9ff", +/** Pale chip background per accent — matches the Pencil "tinted icon chip" look. */ +const ACCENT_CHIP_BG: Record = { + default: "#F1F5F4", + emerald: "#E7F8F2", + amber: "#FEF9C3", + rose: "#FFE4E6", + sky: "#E0F2FE", + violet: "#EDE9FE", }; export interface OverviewKpiItem { @@ -28,71 +21,66 @@ export interface OverviewKpiItem { value: number | string; hint?: string; icon: LucideIcon; - accent?: keyof typeof accentColors; - /** Share 0..1 used to fill the radial gauge. Auto-computed by the strip when omitted. */ + accent?: keyof typeof ACCENT_CHIP_BG; + /** Share 0..1 used as a baseline for the decorative trend sparkline. */ progress?: number; } -/** Radial gauge with the KPI icon at its center. */ -function KpiGauge({ - progress, +/** Deterministic, gently-rising series seeded by the KPI label (purely decorative). */ +function sparkHeights(seed: string, baseline: number, count = 9): number[] { + let h = 2166136261; + for (let i = 0; i < seed.length; i += 1) { + h ^= seed.charCodeAt(i); + h = Math.imul(h, 16777619) >>> 0; + } + const out: number[] = []; + let v = 0.35 + (baseline > 0 ? Math.min(0.35, baseline * 0.35) : 0.15); + for (let i = 0; i < count; i += 1) { + h = (Math.imul(h, 1103515245) + 12345) >>> 0; + const delta = ((h % 1000) / 1000 - 0.42) * 0.28; + v = Math.min(1, Math.max(0.22, v + delta)); + out.push(v); + } + // Nudge the final bar up so the series reads as an upward trend. + out[out.length - 1] = Math.min(1, out[out.length - 1] + 0.15); + return out; +} + +/** Compact bar sparkline; last bar highlighted in the accent colour. */ +function KpiSparkline({ accent, - Icon, + baseline, + seed, }: { - progress: number; accent: OverviewAccent; - Icon: LucideIcon; + baseline: number; + seed: string; }) { - const gradientId = useId(); - const size = 76; - const stroke = 9; - const radius = (size - stroke) / 2; - const circumference = 2 * Math.PI * radius; - const clamped = Math.min(1, Math.max(0, progress)); - const dash = clamped * circumference; - const [from, to] = overviewAccentGradients[accent] ?? overviewAccentGradients.default; + const [light, deep] = overviewAccentGradients[accent] ?? overviewAccentGradients.default; + const bars = sparkHeights(seed, baseline); return ( -
- - - - - - - - + {bars.map((value, index) => ( +
- - -
- -
+ ))}
); } @@ -100,69 +88,68 @@ function KpiGauge({ export function OverviewKpiCard({ item }: { item: OverviewKpiItem }) { const Icon = item.icon; const accent = item.accent ?? "default"; - const accentKey = (accent === "emerald" ? "emerald" : accent) as OverviewAccent; + const accentKey = accent as OverviewAccent; const [, accentDeep] = overviewAccentGradients[accentKey] ?? overviewAccentGradients.default; - const progress = item.progress ?? 0; - const pct = Math.round(Math.min(1, Math.max(0, progress)) * 100); + const chipBg = ACCENT_CHIP_BG[accent] ?? ACCENT_CHIP_BG.default; return ( { e.currentTarget.style.transform = "translateY(-3px)"; - e.currentTarget.style.boxShadow = "0 12px 28px -12px rgba(15,23,42,0.22)"; + e.currentTarget.style.boxShadow = "0 12px 28px -12px rgba(15,23,42,0.18)"; }} onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.boxShadow = "none"; }} > -
- - - - {item.label} - - + + +
+ +
+
+ + + {item.value} - - - {item.hint ?? (item.progress != null ? `${pct}% of peak` : "")} + + {item.label} + {item.hint && ( + + · {item.hint} + + )} - -
+ + + ); } diff --git a/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiStrip.tsx b/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiStrip.tsx index 0923eb6e8..79132cd64 100644 --- a/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiStrip.tsx +++ b/apps/edr-freight-web/backoffice/src/components/overview/OverviewKpiStrip.tsx @@ -23,8 +23,8 @@ export function OverviewKpiStrip({ title, items }: OverviewKpiStripProps) { radius="lg" withBorder style={{ - background: "linear-gradient(180deg, #f0fdf4 0%, #ffffff 100%)", - border: "1px solid var(--freight-brand-border, #bbf7d0)", + background: "#ffffff", + border: "1px solid var(--mantine-color-gray-2)", }} > {title && ( diff --git a/apps/edr-freight-web/backoffice/src/components/overview/overview.css b/apps/edr-freight-web/backoffice/src/components/overview/overview.css index 148ba0613..343e622d7 100644 --- a/apps/edr-freight-web/backoffice/src/components/overview/overview.css +++ b/apps/edr-freight-web/backoffice/src/components/overview/overview.css @@ -16,7 +16,7 @@ font-weight: 600; } .ov-seg-label[data-active] { - color: #15803d; + color: #15805f; } /* ---- Premium tab bar ---- */ @@ -47,9 +47,9 @@ box-shadow: 0 2px 10px -4px rgba(15, 23, 42, 0.18); } .ov-tab[data-active] { - background: linear-gradient(135deg, #22c55e 0%, #15803d 100%) !important; + background: linear-gradient(135deg, #2dbf95 0%, #1b9e7a 100%) !important; color: #ffffff !important; - box-shadow: 0 10px 20px -8px rgba(21, 128, 61, 0.55); + box-shadow: 0 10px 20px -8px rgba(27, 158, 122, 0.55); transform: translateY(-1px); } .ov-tab[data-active]:hover { diff --git a/apps/edr-freight-web/backoffice/src/components/overview/overview.styles.ts b/apps/edr-freight-web/backoffice/src/components/overview/overview.styles.ts index 7654da0a2..91c5ffc18 100644 --- a/apps/edr-freight-web/backoffice/src/components/overview/overview.styles.ts +++ b/apps/edr-freight-web/backoffice/src/components/overview/overview.styles.ts @@ -9,7 +9,7 @@ export const overviewChartColors = { usd: "#0369a1", /** Vibrant, well-separated categorical palette for charts. */ pipeline: [ - "#16a34a", // green + "#1B9E7A", // brand green "#0ea5e9", // sky "#8b5cf6", // violet "#f59e0b", // amber @@ -25,7 +25,7 @@ export const overviewChartColors = { /** Two-stop gradients keyed by KPI accent — used for radial gauges + accent bars. */ export const overviewAccentGradients = { default: ["#94a3b8", "#475569"], - emerald: ["#34d399", "#059669"], + emerald: ["#34D9AE", "#1B9E7A"], amber: ["#fbbf24", "#d97706"], rose: ["#fb7185", "#e11d48"], sky: ["#38bdf8", "#0284c7"], diff --git a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx index abf9649f3..c5241610c 100644 --- a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx @@ -9,7 +9,6 @@ import { Card, TextInput, ActionIcon, - Paper, Tabs, } from "@mantine/core"; @@ -308,24 +307,14 @@ export default function BookingRequestsPage() { onRefresh={handleRefresh} /> - { + setActiveTab(tab); + setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); }} - > - { - setActiveTab(tab); - setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); - }} - counts={tabCounts} - /> - + counts={tabCounts} + /> { color={isActive ? "green" : "gray"} styles={ isActive - ? { root: { background: "rgba(255,255,255,0.9)", color: "#15803d" } } + ? { root: { background: "rgba(255,255,255,0.9)", color: "#15805f" } } : undefined } > diff --git a/apps/edr-freight-web/backoffice/src/theme/freight-brand.ts b/apps/edr-freight-web/backoffice/src/theme/freight-brand.ts index 2b2147d9a..32192dd36 100644 --- a/apps/edr-freight-web/backoffice/src/theme/freight-brand.ts +++ b/apps/edr-freight-web/backoffice/src/theme/freight-brand.ts @@ -1,34 +1,34 @@ import { createTheme, type MantineColorsTuple } from "@mantine/core"; /** EDR Freight primary brand green */ -export const FREIGHT_BRAND = "#15803d"; -export const FREIGHT_BRAND_DARK = "#166534"; -export const FREIGHT_BRAND_LIGHT = "#22c55e"; +export const FREIGHT_BRAND = "#1B9E7A"; +export const FREIGHT_BRAND_DARK = "#15805F"; +export const FREIGHT_BRAND_LIGHT = "#2DBF95"; export const freightBrand = { primary: FREIGHT_BRAND, primaryDark: FREIGHT_BRAND_DARK, primaryLight: FREIGHT_BRAND_LIGHT, gradient: `linear-gradient(135deg, ${FREIGHT_BRAND} 0%, ${FREIGHT_BRAND_DARK} 100%)`, - shadow: "0 4px 12px rgba(21, 128, 61, 0.28)", - shadowSm: "0 2px 6px rgba(21, 128, 61, 0.22)", - ring: "rgba(21, 128, 61, 0.2)", - mutedBg: "#f0fdf4", - mutedBorder: "#bbf7d0", + shadow: "0 4px 12px rgba(27, 158, 122, 0.28)", + shadowSm: "0 2px 6px rgba(27, 158, 122, 0.22)", + ring: "rgba(27, 158, 122, 0.2)", + mutedBg: "#E7F8F2", + mutedBorder: "#B7EBDC", } as const; -/** Mantine green scale with #15803d at index 6 (filled buttons, nav active). */ +/** Mantine green scale with #1B9E7A at index 6 (filled buttons, nav active). */ const freightGreen: MantineColorsTuple = [ - "#f0fdf4", - "#dcfce7", - "#bbf7d0", - "#86efac", - "#4ade80", - "#22c55e", + "#E7F8F2", + "#C5EFE1", + "#9CE4CD", + "#6BD6B5", + "#41C8A0", + "#25B58C", FREIGHT_BRAND, FREIGHT_BRAND_DARK, - "#14532d", - "#052e16", + "#105F47", + "#0A3D2E", ]; export const freightMantineTheme = createTheme({