From 8aaccf3f367cfe72041d543a7eae394b4d0e1f94 Mon Sep 17 00:00:00 2001 From: marshalyordanos Date: Mon, 20 Jul 2026 22:10:48 +0300 Subject: [PATCH 1/3] Enhance BookingRequestsPage layout and styling --- .../pages/bookings/BookingRequestsPage.tsx | 67 ++++++++------- .../pages/bookings/booking-requests-table.css | 84 +++++++++++++++++++ 2 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 apps/edr-freight-web/backoffice/src/pages/bookings/booking-requests-table.css 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 8ca532819..1345d2855 100644 --- a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx @@ -58,6 +58,8 @@ import { type ColumnDef, } from "@edr/ui-common"; +import "./booking-requests-table.css"; + /** Booking kind: one-time vs general-contract bookings. Now a filter, not a tab. */ type BookingKind = "ONE_TIME" | "GENERAL_CONTRACT"; @@ -282,6 +284,8 @@ export default function BookingRequestsPage() { const columns: ColumnDef[] = [ { id: "booking", + size: 60, + minSize: 40, header: () => Booking, cell: ({ row }) => { const b = row.original; @@ -303,6 +307,8 @@ export default function BookingRequestsPage() { }, { id: "contract", + size: 60, + minSize: 40, header: () => Contract, cell: ({ row }) => { const ref = row.original.contractReference; @@ -329,6 +335,8 @@ export default function BookingRequestsPage() { }, { id: "bookingKind", + size: 60, + minSize: 40, header: () => Type, cell: ({ row }) => { const isGeneral = row.original.bookingKind === "GENERAL_CONTRACT"; @@ -346,6 +354,8 @@ export default function BookingRequestsPage() { }, { id: "route", + size: 60, + minSize: 40, header: () => Route, cell: ({ row }) => { const b = row.original; @@ -376,8 +386,8 @@ export default function BookingRequestsPage() { }, { id: "status", - size: 200, - minSize: 180, + size: 60, + minSize: 40, header: () => Status, cell: ({ row }) => (
@@ -388,13 +398,11 @@ export default function BookingRequestsPage() { />
), - meta: { - headerClassName: "min-w-[11rem]", - cellClassName: "min-w-[11rem]", - }, }, { id: "scheduled", + size: 60, + minSize: 40, header: () => Scheduled, cell: ({ row }) => ( @@ -405,6 +413,8 @@ export default function BookingRequestsPage() { }, { id: "priority", + size: 60, + minSize: 40, header: () => Priority, cell: ({ row }) => ( @@ -412,7 +422,8 @@ export default function BookingRequestsPage() { }, { id: "actions", - size: 140, + size: 60, + minSize: 40, cell: ({ row }) => ( ) : ( - - - + )} diff --git a/apps/edr-freight-web/backoffice/src/pages/bookings/booking-requests-table.css b/apps/edr-freight-web/backoffice/src/pages/bookings/booking-requests-table.css new file mode 100644 index 000000000..d5bb8df5d --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/booking-requests-table.css @@ -0,0 +1,84 @@ +/* + * Scoped to .edr-booking-requests-table — the DataTable container div on the + * backoffice booking-requests list only; no other DataTable is affected. + * Mirrors the portal's /bookings table (bookings-table.css): horizontal + * scroll on the container, a sticky header row, and a sticky/shadowed + * action column — with a compact 40–60px column width band (content + * beyond that is clipped with an ellipsis) instead of the portal's + * content-sized columns. + */ +.edr-booking-requests-table { + overflow-x: auto; +} + +/* + * width: max-content — the table is exactly as wide as its columns need, + * never squeezed to fit the viewport; the container scrolls instead. + * min-width: 100% keeps it filling the card when content is narrow. + */ +.edr-booking-requests-table table { + table-layout: auto; + width: max-content; + min-width: 100%; +} + +/* Compact column band: 40px floor, 60px ceiling, ellipsis past that. */ +.edr-booking-requests-table th, +.edr-booking-requests-table td:not([colspan]) { + min-width: 40px; + max-width: 60px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* + * Full-width rows (loading skeleton / error / empty state) span every + * column via colspan — leave their sizing and wrapping alone. + */ +.edr-booking-requests-table td[colspan] { + max-width: none; + white-space: normal; +} + +/* Sticky header row. */ +.edr-booking-requests-table thead th { + position: sticky; + top: 0; + z-index: 1; +} + +/* + * Fixed, sticky action column. Overrides the inline width DataTable stamps + * from tanstack's column size (`size: 140` on the actions column) — hence + * !important. `:not([colspan])` keeps the full-width error/empty rows out. + */ +.edr-booking-requests-table th:last-child, +.edr-booking-requests-table td:last-child:not([colspan]) { + width: 60px !important; + min-width: 60px; + max-width: 60px; + position: sticky; + right: 0; + box-shadow: -12px 0 16px -6px rgba(16, 32, 47, 0.3); +} + +/* + * Sticky cells sit above the scrolling ones, so they need their own opaque + * background or the columns underneath show through. + */ +.edr-booking-requests-table td:last-child:not([colspan]) { + background: #f5f8fb; + z-index: 2; +} + +/* Row hover uses the tailwind `hover:bg-accent` class on the . */ +.edr-booking-requests-table tbody tr:hover td:last-child:not([colspan]) { + background: var(--accent, #f4fbf8); +} + +/* Header cell is sticky on both axes — it must outrank the body's sticky column. */ +.edr-booking-requests-table th:last-child { + background: #f4f7fa; + z-index: 3; +} From 8e74604a07db647dbb0de2a3b027c4d1e054dacf Mon Sep 17 00:00:00 2001 From: marshalyordanos Date: Mon, 20 Jul 2026 22:35:01 +0300 Subject: [PATCH 2/3] Fix CSS formatting for bookings table class to ensure proper styling and layout. --- .../pages/bookings/BookingRequestsPage.tsx | 143 ++++++++---------- .../src/pages/bookings/bookings-table.css | 2 +- 2 files changed, 65 insertions(+), 80 deletions(-) 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 1345d2855..3eb775e95 100644 --- a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestsPage.tsx @@ -7,6 +7,7 @@ import { MultiSelect, Select, Stack, + Tabs, Text, TextInput, } from "@mantine/core"; @@ -32,6 +33,7 @@ import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { BookingActionsMenu } from "@/components/bookings/BookingActionsMenu"; +import { BookingApprovalProgressCell } from "@/components/bookings/BookingApprovalProgressCell"; import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge"; import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge"; // BookingStatusTabs / Operations* queues removed — replaced by booking-kind tabs. @@ -58,12 +60,10 @@ import { type ColumnDef, } from "@edr/ui-common"; -import "./booking-requests-table.css"; +/** The two booking-kind tabs: one-time vs general-contract bookings. */ +type BookingKindTab = "ONE_TIME" | "GENERAL_CONTRACT"; -/** Booking kind: one-time vs general-contract bookings. Now a filter, not a tab. */ -type BookingKind = "ONE_TIME" | "GENERAL_CONTRACT"; - -const BOOKING_KIND_OPTIONS: { value: BookingKind; label: string }[] = [ +const BOOKING_KIND_TABS: { value: BookingKindTab; label: string }[] = [ { value: "ONE_TIME", label: "One-time booking" }, { value: "GENERAL_CONTRACT", label: "General booking" }, ]; @@ -128,9 +128,9 @@ export default function BookingRequestsPage() { const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [query, setQuery] = useState(""); const [debouncedQuery] = useDebouncedValue(query, 300); - // Booking kind is a filter now — one list holds both kinds (null = "all"). - const [kindFilter, setKindFilter] = useState(null); - // Filter controls (empty/null = "all"). + // Booking-kind tabs (one-time vs general contract) replace the old status tabs. + const [kindTab, setKindTab] = useState("ONE_TIME"); + // Per-tab filter controls (empty/null = "all"). const [statusFilter, setStatusFilter] = useState([]); const [directionFilter, setDirectionFilter] = useState(null); const [freightTypeFilter, setFreightTypeFilter] = useState(null); @@ -158,9 +158,9 @@ export default function BookingRequestsPage() { pageSize: pagination.pageSize, sortBy: "createdAt", sortOrder: "DESC", - // React Query cache key per kind selection ("ALL" when unfiltered). - tab: kindFilter ?? "ALL", - ...(kindFilter ? { bookingType: kindFilter } : {}), + // React Query cache key per kind tab. + tab: kindTab, + bookingType: kindTab, // Server-side free-text search (booking ref, customer, contract ref). ...(debouncedQuery.trim() ? { search: debouncedQuery.trim() } : {}), ...(statusFilter.length ? { statuses: statusFilter.join(",") } : {}), @@ -182,7 +182,7 @@ export default function BookingRequestsPage() { }, [ pagination.pageIndex, pagination.pageSize, - kindFilter, + kindTab, debouncedQuery, statusFilter, directionFilter, @@ -226,7 +226,6 @@ export default function BookingRequestsPage() { }, [setPagination, pagination.pageSize]); const activeFilterCount = - (kindFilter ? 1 : 0) + (statusFilter.length ? 1 : 0) + (directionFilter ? 1 : 0) + (freightTypeFilter ? 1 : 0) + @@ -238,7 +237,6 @@ export default function BookingRequestsPage() { (scheduledFrom || scheduledTo ? 1 : 0); const clearFilters = useCallback(() => { - setKindFilter(null); setStatusFilter([]); setDirectionFilter(null); setFreightTypeFilter(null); @@ -284,8 +282,6 @@ export default function BookingRequestsPage() { const columns: ColumnDef[] = [ { id: "booking", - size: 60, - minSize: 40, header: () => Booking, cell: ({ row }) => { const b = row.original; @@ -307,8 +303,6 @@ export default function BookingRequestsPage() { }, { id: "contract", - size: 60, - minSize: 40, header: () => Contract, cell: ({ row }) => { const ref = row.original.contractReference; @@ -333,29 +327,8 @@ export default function BookingRequestsPage() { ); }, }, - { - id: "bookingKind", - size: 60, - minSize: 40, - header: () => Type, - cell: ({ row }) => { - const isGeneral = row.original.bookingKind === "GENERAL_CONTRACT"; - return ( -
- - {isGeneral ? "General" : "One-time"} - -
- ); - }, - }, { id: "route", - size: 60, - minSize: 40, header: () => Route, cell: ({ row }) => { const b = row.original; @@ -386,8 +359,8 @@ export default function BookingRequestsPage() { }, { id: "status", - size: 60, - minSize: 40, + size: 200, + minSize: 180, header: () => Status, cell: ({ row }) => (
@@ -398,11 +371,20 @@ export default function BookingRequestsPage() { />
), + meta: { + headerClassName: "min-w-[11rem]", + cellClassName: "min-w-[11rem]", + }, + }, + { + id: "approval", + header: () => ( + Approval + ), + cell: ({ row }) => , }, { id: "scheduled", - size: 60, - minSize: 40, header: () => Scheduled, cell: ({ row }) => ( @@ -413,8 +395,6 @@ export default function BookingRequestsPage() { }, { id: "priority", - size: 60, - minSize: 40, header: () => Priority, cell: ({ row }) => ( @@ -422,8 +402,7 @@ export default function BookingRequestsPage() { }, { id: "actions", - size: 60, - minSize: 40, + size: 140, cell: ({ row }) => ( */} + { + setKindTab((value as BookingKindTab) ?? "ONE_TIME"); + setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); + }} + > + + {BOOKING_KIND_TABS.map((t) => ( + + {t.label} + + ))} + + + @@ -540,18 +535,6 @@ export default function BookingRequestsPage() { - { + setKindFilter((v as BookingKind | null) ?? null); + resetPage(); + }} + clearable + radius="lg" + style={{ minWidth: 190 }} + />