From a48d77aff8afb13d32004023920ae64d844efa18 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Thu, 27 Aug 2026 09:29:41 +0000 Subject: [PATCH] feat(backoffice): show each role's eTrade business on the customer detail page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reviewer approving a role had no way to see which business it claims to operate as, so there was nothing to check the uploaded licence document against. The Role profiles table now carries a column with the trade name, the licensed activity (does it actually cover this role?), the licence number (the only unambiguous handle — trade names repeat across a TIN's licences) and the renewal date. A role with nothing attached reads as a yellow "Not attached" rather than a blank: it is a review finding. Yellow, not red, because a co-operative or investment-licence company legitimately has none. Two fixes alongside it: - The profile reference was already rendered but is minted only on approval, so every pending role drew an empty line. It now says so. - `TableCard` gained an optional header section, so padding sits per section and the table runs edge to edge. The header stays outside the scroll region — inside, a title slides away from its own table. --- .../src/components/customers/TableCard.tsx | 29 ++++-- .../pages/customers/CustomerDetailPage.tsx | 89 ++++++++++++++----- .../backoffice/src/types/customer.ts | 11 +++ 3 files changed, 104 insertions(+), 25 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/components/customers/TableCard.tsx b/apps/edr-freight-web/backoffice/src/components/customers/TableCard.tsx index b67181da5..14aa8e80e 100644 --- a/apps/edr-freight-web/backoffice/src/components/customers/TableCard.tsx +++ b/apps/edr-freight-web/backoffice/src/components/customers/TableCard.tsx @@ -3,6 +3,12 @@ import type { ReactNode } from "react"; export interface TableCardProps { children: ReactNode; + /** + * Optional heading row (title, chips, actions). Rendered in its own padded + * section above the table and OUTSIDE the scroll region — a header inside it + * would slide away from its own table on a narrow viewport. + */ + header?: ReactNode; /** * Minimum width (px) the table is forced to occupy. The Mantine `Table` is * always `width: 100%`, so without a floor it can never overflow its @@ -14,14 +20,27 @@ export interface TableCardProps { } /** - * Flush card shell for a `DataTable`: a borderless, padding-less card whose - * single child is a horizontally scrollable region. Pair with the table's - * `containerClassName="border-0 shadow-none bg-transparent"` so every table on - * the customer pages reads identically (same surface, same scroll behaviour). + * Flush card shell for a `DataTable`: a padding-less card whose table region + * runs edge to edge. Padding is applied per section rather than to the card, so + * the optional {@link TableCardProps.header} is inset like any other card + * content while the table's own rows and header cells reach both edges. + * + * Pair with the table's `containerClassName="border-0 shadow-none bg-transparent"` + * so every table on the customer pages reads identically (same surface, same + * scroll behaviour). */ -export function TableCard({ children, minWidth = 860 }: TableCardProps) { +export function TableCard({ + children, + minWidth = 860, + header, +}: TableCardProps) { return ( + {header && ( + + {header} + + )} {children} diff --git a/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx index a81f07c8b..ec89279a2 100644 --- a/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx @@ -240,12 +240,61 @@ export default function CustomerDetailPage() {
- - {row.original.reference} - + {/* The profile reference (EX-A00001). Minted only when a reviewer + approves the role, so an unapproved one has none — say so + rather than rendering an empty line that reads as a bug. */} + {row.original.reference ? ( + + {row.original.reference} + + ) : ( + + Ref. issued on approval + + )}
), }, + { + id: "etradeBusiness", + header: "eTrade business", + cell: ({ row }) => { + const business = row.original.etradeBusiness; + // Not attached is a review finding, not a blank: the role names no + // business, so there is nothing to check the uploaded licence + // against. Companies with no eTrade record legitimately show this, + // which is why it reads as a warning rather than an error. + if (!business) { + return ( + + Not attached + + ); + } + return ( + + + {business.tradeName || "(no trade name on this licence)"} + + {business.activity && ( + + {business.activity} + + )} + {/* The licence number is what the reviewer matches against the + uploaded document — trade names repeat across licences. */} + + {business.licenceNumber} + + {business.renewedTo && ( + + Renewed to {business.renewedTo} + + )} + + ); + }, + }, { id: "licenseFiles", header: "License documents", @@ -981,29 +1030,29 @@ export default function CustomerDetailPage() {
- - + {/* Padding sits on the header section, not the card, so the + table runs edge to edge. minWidth carries the eTrade + business column; the region scrolls rather than squashing + the other columns. */} + Role profiles - {/* Narrower than the old full-width layout — the table - shares the row with the people column now. */} - - - - - - - + } + > + + diff --git a/apps/edr-freight-web/backoffice/src/types/customer.ts b/apps/edr-freight-web/backoffice/src/types/customer.ts index 3c2427a1b..e45112ecd 100644 --- a/apps/edr-freight-web/backoffice/src/types/customer.ts +++ b/apps/edr-freight-web/backoffice/src/types/customer.ts @@ -8,6 +8,8 @@ * API so the data layer can be swapped to live endpoints with no UI changes. */ +import type { ETradeBusinessOption } from "@edr/types"; + /** Mirrors backend `CompanyType`. */ export type CompanyType = | "customer" @@ -65,6 +67,15 @@ export interface CompanyProfile { /** Business-license documents uploaded for this profile. */ licenseFiles?: LicenseFile[]; attributes?: Record | null; + /** + * Which of the TIN's eTrade business licences this role operates as. + * + * A TIN routinely holds a dozen licences split by activity, so "exporter" and + * "freight forwarder" are usually two different businesses under one company. + * Null when the customer has not attached one, or when the company registered + * without eTrade at all (co-operative / investment licence). + */ + etradeBusiness?: ETradeBusinessOption | null; /** Reviewer note when the role is rejected. */ reviewNote?: string | null; createdAt: string;