From fca5487f6d743b54189e84d532df12a5e76979b8 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Thu, 6 Aug 2026 23:30:23 -0300 Subject: [PATCH] fix(vendors): enforce prototype visual geometry --- e2e/vendors/vendors.spec.ts | 19 +++- .../_components/vendors-list-toolbar.tsx | 20 +++- .../vendors/_components/vendors-table.tsx | 96 +++++++++++++++---- src/app/(protected)/vendors/index.tsx | 4 +- src/components/ui/page-header.tsx | 9 +- 5 files changed, 123 insertions(+), 25 deletions(-) diff --git a/e2e/vendors/vendors.spec.ts b/e2e/vendors/vendors.spec.ts index 0ce999d2..e5c8bcf3 100644 --- a/e2e/vendors/vendors.spec.ts +++ b/e2e/vendors/vendors.spec.ts @@ -306,10 +306,17 @@ test.describe("Vendor directory prototype parity", () => { test("covers status tabs, global counts, search, facets, table fields, and filtered footer", async ({ page, }) => { + await page.setViewportSize({ width: 1440, height: 1000 }); const state = await mockVendorApi(page); await page.goto("/vendors"); - await expect(page.getByRole("heading", { name: "Vendors / Technicians" })).toBeVisible(); + const heading = page.getByRole("heading", { name: "Vendors / Technicians" }); + await expect(heading).toBeVisible(); + await expect(heading).toHaveCSS("font-family", /Montserrat/); + await expect(heading).toHaveCSS("font-size", "30px"); + await expect(heading).toHaveCSS("font-weight", "800"); + await expect(heading).toHaveCSS("line-height", "33px"); + await expect(heading).toHaveCSS("letter-spacing", "-0.6px"); await expect(page.getByText("3 technicians · 2 active")).toBeVisible(); await expect(page.getByRole("tab", { name: "Active (2)" })).toHaveAttribute( "aria-selected", @@ -327,6 +334,16 @@ test.describe("Vendor directory prototype parity", () => { await expect(page.getByRole("columnheader", { name: heading })).toBeVisible(); } await expect(page.getByRole("columnheader", { name: "Status" })).toBeVisible(); + const activeTabBox = await page.getByRole("tab", { name: "Active (2)" }).boundingBox(); + expect(activeTabBox).toMatchObject({ x: 256, y: 159.5, height: 36 }); + const tableContainerBox = await page.locator("table").locator("..").boundingBox(); + expect(tableContainerBox).toMatchObject({ x: 260, y: 212.5, width: 1164 }); + expect(tableContainerBox?.height).toBeCloseTo(738, 0); + expect((await page.locator("table").boundingBox())?.width).toBe(1550); + expect((await page.getByRole("columnheader", { name: "Company" }).boundingBox())?.height).toBe( + 47, + ); + expect((await page.locator("tbody tr").first().boundingBox())?.height).toBe(52); await expect(page.getByText("Gateway Plumbing")).toBeVisible(); await expect(page.getByText("+1")).toBeVisible(); await expect(page.getByText("2 active technicians")).toBeVisible(); diff --git a/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx b/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx index ff5352ec..a8203ecb 100644 --- a/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx +++ b/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx @@ -31,6 +31,7 @@ export function VendorsListToolbar({ return ( } + icon={} onClick={onOpenFilters} aria-label="Open vendor filters" badge={appliedFilterCount} + className="w-[88.609375px] text-[13px]" > Filters - } onClick={onAddVendor}> + } + onClick={onAddVendor} + className="w-[115.546875px] border-0 px-3 text-[13px]" + > Add Vendor {isFetching && !isLoading && ( @@ -131,14 +139,18 @@ export function VendorsListFooter({ sx={{ alignItems: { xs: "stretch", sm: "center" }, justifyContent: "space-between", + minHeight: 33.5, + mx: -2, + px: 2, + backgroundColor: "var(--color-bg-surface)", }} > - + {totalCount} {isActive ? "active" : "inactive"}{" "} {totalCount === 1 ? "technician" : "technicians"} {isFiltered && " · filtered"} - + {activeCount} active · {inactiveCount} inactive {totalCount > pageSize && ( diff --git a/src/app/(protected)/vendors/_components/vendors-table.tsx b/src/app/(protected)/vendors/_components/vendors-table.tsx index 1e47a58a..db588cff 100644 --- a/src/app/(protected)/vendors/_components/vendors-table.tsx +++ b/src/app/(protected)/vendors/_components/vendors-table.tsx @@ -4,7 +4,7 @@ import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import { Avatar, - Chip, + Box, CircularProgress, IconButton, Link, @@ -50,6 +50,23 @@ function stopPropagation(event: MouseEvent): void { event.stopPropagation(); } +function VendorStatus({ isActive }: { isActive: boolean }) { + return ( + + + {isActive ? "Active" : "Inactive"} + + ); +} + interface VendorTableRowProps { row: VendorListItem; onOpenDetail: (row: VendorListItem) => void; @@ -82,12 +99,12 @@ function VendorTableRow({ row, onOpenDetail, onOpenEdit }: VendorTableRowProps) onKeyDown={handleKeyDown} > - + {row.companyName || "—"} - + {getInitials(row.contactName)} - {row.contactName || "—"} + + {row.contactName || "—"} + - {locationText} - + {locationText} + {primaryTrade === "—" ? ( "—" ) : ( {primaryTrade} - {extraTrades > 0 && } + {extraTrades > 0 && ( + + +{extraTrades} + + )} )} - + {row.phone ? ( - + {row.phone} ) : ( @@ -125,7 +163,12 @@ function VendorTableRow({ row, onOpenDetail, onOpenEdit }: VendorTableRowProps) {row.email ? ( - + {row.email} ) : ( @@ -134,11 +177,7 @@ function VendorTableRow({ row, onOpenDetail, onOpenEdit }: VendorTableRowProps) {row.totalJobs ?? 0} - + + + + + + + + + + + + {TABLE_COLUMNS.map((column) => ( diff --git a/src/app/(protected)/vendors/index.tsx b/src/app/(protected)/vendors/index.tsx index c81ed17f..48447ad6 100644 --- a/src/app/(protected)/vendors/index.tsx +++ b/src/app/(protected)/vendors/index.tsx @@ -59,6 +59,7 @@ function VendorStatusTabs({ flexShrink: 0, borderBottom: (theme) => `1px solid ${theme.palette.divider}`, backgroundColor: "var(--color-bg-surface)", + mx: -2, px: 1.5, "& .MuiTab-root": { minHeight: 36, @@ -68,6 +69,7 @@ function VendorStatusTabs({ fontWeight: 500, textTransform: "none", }, + "& .MuiTab-root:first-of-type": { width: 102.84375 }, "& .Mui-selected": { fontWeight: 600 }, "& .MuiTabs-indicator": { height: 2 }, }} @@ -139,7 +141,7 @@ export default function VendorsListPage() { const appliedChips = describeChips(filters.appliedFilters, jobBucketLabels); return ( - + {title} - + {subtitle}