From cd3633e2b4532d966b9c37077028c7949ec85349 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Tue, 25 Aug 2026 13:33:53 -0300 Subject: [PATCH] fix(vendors): show free-text address in the list Location column The Location column rendered City/State only, so a vendor whose location lives in the free-text address field showed an em dash while the detail drawer, which already composes address with city, state and zip, showed the value. Carry address through the list item type and mapper, which previously dropped it, and fall back to it in the Location cell when City/State are blank. Structured City/State still wins when both are present. --- .../vendors/_components/vendors-table.tsx | 5 ++- src/domain/vendors/mappers/vendor-mapper.ts | 1 + src/domain/vendors/types/vendor.ts | 1 + .../vendors/vendor-detail-drawer.test.tsx | 1 + .../(protected)/vendors/vendors-list.test.tsx | 43 +++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/app/(protected)/vendors/_components/vendors-table.tsx b/src/app/(protected)/vendors/_components/vendors-table.tsx index e524630c..57e8a243 100644 --- a/src/app/(protected)/vendors/_components/vendors-table.tsx +++ b/src/app/(protected)/vendors/_components/vendors-table.tsx @@ -61,7 +61,10 @@ function VendorTableRow({ row, onOpenDetail, onOpenEdit }: VendorTableRowProps) const trades = splitTrades(row.tradeSpecialties); const primaryTrade = trades[0] ?? "—"; const extraTrades = trades.length > 1 ? trades.length - 1 : 0; - const locationText = [row.city, row.state].filter(Boolean).join(", ") || "—"; + // Vendors captured through the roster form keep their location in the free-text + // address field, so fall back to it the way the detail drawer already does. + const locationText = + [row.city, row.state].filter(Boolean).join(", ").trim() || (row.address ?? "").trim() || "—"; const rowKey = String(row.id ?? `company-${row.companyId ?? ""}`); const handleKeyDown = (event: KeyboardEvent) => { diff --git a/src/domain/vendors/mappers/vendor-mapper.ts b/src/domain/vendors/mappers/vendor-mapper.ts index 33a31223..321e4542 100644 --- a/src/domain/vendors/mappers/vendor-mapper.ts +++ b/src/domain/vendors/mappers/vendor-mapper.ts @@ -119,6 +119,7 @@ export function mapVendorListItem(raw: unknown): VendorListItem { googleMapsUrl: readString(item, "googleMapsUrl", "GoogleMapsUrl"), notes: readString(item, "notes", "Notes"), totalJobs: readNumber(item, "totalJobs", "TotalJobs"), + address: readString(item, "address", "Address"), city: readString(item, "city", "City"), state: readString(item, "state", "State"), tradeSpecialties: readString(item, "tradeSpecialties", "TradeSpecialties"), diff --git a/src/domain/vendors/types/vendor.ts b/src/domain/vendors/types/vendor.ts index d4140afa..74e6519e 100644 --- a/src/domain/vendors/types/vendor.ts +++ b/src/domain/vendors/types/vendor.ts @@ -13,6 +13,7 @@ export interface VendorListItem { googleMapsUrl: string; notes: string; totalJobs: number; + address: string; city: string; state: string; tradeSpecialties: string; diff --git a/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx b/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx index a882b91f..cf38b5bb 100644 --- a/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx +++ b/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx @@ -33,6 +33,7 @@ const vendor: VendorListItem = { googleMapsUrl: "", notes: "", totalJobs: 0, + address: "", city: "", state: "", tradeSpecialties: "", diff --git a/src/test/app/(protected)/vendors/vendors-list.test.tsx b/src/test/app/(protected)/vendors/vendors-list.test.tsx index ae5a18d3..ec0b1389 100644 --- a/src/test/app/(protected)/vendors/vendors-list.test.tsx +++ b/src/test/app/(protected)/vendors/vendors-list.test.tsx @@ -40,6 +40,7 @@ const activeVendor: VendorListItem = { googleMapsUrl: "", notes: "", totalJobs: 0, + address: "", city: "St. Louis", state: "MO", tradeSpecialties: "Backflow Preventers", @@ -152,6 +153,48 @@ describe("VendorsListPage", () => { expect(screen.queryByText(/3 active technicians/)).not.toBeInTheDocument(); }); + it("falls back to the free-text address when city and state are blank", () => { + setupDefaults(); + const addressOnly = { + ...activeVendor, + id: 900, + companyName: "Vinewood LLC", + address: "Santa Monica, CA", + city: "", + state: "", + }; + + useVendorsList.mockImplementation((params: { isActive?: boolean; pageSize?: number }) => { + if (params.pageSize === 1) return result([], params.isActive ? 1 : 0); + return result([addressOnly], 1); + }); + + renderWithProviders(, { route: "/vendors", withAuth: false }); + + expect(screen.getByText("Santa Monica, CA")).toBeInTheDocument(); + }); + + it("prefers structured city and state over the free-text address", () => { + setupDefaults(); + const both = { + ...activeVendor, + id: 901, + address: "Santa Monica, CA", + city: "St. Louis", + state: "MO", + }; + + useVendorsList.mockImplementation((params: { isActive?: boolean; pageSize?: number }) => { + if (params.pageSize === 1) return result([], params.isActive ? 1 : 0); + return result([both], 1); + }); + + renderWithProviders(, { route: "/vendors", withAuth: false }); + + expect(screen.getByText("St. Louis, MO")).toBeInTheDocument(); + expect(screen.queryByText("Santa Monica, CA")).not.toBeInTheDocument(); + }); + it("opens the detail drawer when a row is clicked or activated by keyboard", async () => { setupDefaults(); useVendorCompanyRoster.mockReturnValue({