diff --git a/e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-detail.png b/e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-detail.png
index 1beb0a15..1993c2c7 100644
Binary files a/e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-detail.png and b/e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-detail.png differ
diff --git a/src/app/(protected)/vendors/_components/vendor-detail-drawer.tsx b/src/app/(protected)/vendors/_components/vendor-detail-drawer.tsx
index fdb627be..b1a24c3c 100644
--- a/src/app/(protected)/vendors/_components/vendor-detail-drawer.tsx
+++ b/src/app/(protected)/vendors/_components/vendor-detail-drawer.tsx
@@ -17,6 +17,7 @@ import {
Switch,
} from "@mui/material";
import { VendorRosterConflictAlert } from "./vendor-roster-conflict-alert";
+import { VendorStatusBadge } from "./vendor-status-badge";
import { VendorRosterFormFields } from "./vendor-roster-form-fields";
import { VendorRosterLoadErrorAlert } from "./vendor-roster-load-error";
import { useVendorRosterForm } from "./use-vendor-roster-form";
@@ -81,6 +82,13 @@ function DrawerHeader({
technician?: VendorRosterTechnician;
onClose: () => void;
}) {
+ const title = technician?.contactName || vendor?.contactName || roster?.name || "Vendor company";
+ const company = roster?.name || vendor?.companyName || "Vendor details";
+ // The company is a subtitle under the technician's name. When there is no
+ // technician the title already falls back to the company, and repeating it
+ // would render the same line twice.
+ const subtitle = company === title ? null : company;
+
return (
- {technician?.contactName || vendor?.contactName || roster?.name || "Vendor company"}
-
-
- {roster?.name || vendor?.companyName || "Vendor details"}
+ {title}
+ {subtitle !== null && (
+
+ {subtitle}
+
+ )}
@@ -118,17 +128,24 @@ function CompanySection({ roster }: { roster: VendorCompanyRoster }) {
- {Boolean(roster.googleMapsUrl) && (
-
-
- Open in Google Maps
-
- )}
+
+
+ Google Maps
+
+ {roster.googleMapsUrl ? (
+
+
+ Open in Google Maps
+
+ ) : (
+ —
+ )}
+
);
}
@@ -154,23 +171,14 @@ function TechnicianSection({ technician }: { technician: VendorRosterTechnician
)}
-
+
{technician.totalJobs ?? 0}
Total Jobs
-
-
- Status
-
-
-
+
diff --git a/src/app/(protected)/vendors/_components/vendor-status-badge.tsx b/src/app/(protected)/vendors/_components/vendor-status-badge.tsx
new file mode 100644
index 00000000..7fee8970
--- /dev/null
+++ b/src/app/(protected)/vendors/_components/vendor-status-badge.tsx
@@ -0,0 +1,20 @@
+import { Box, Stack } from "@mui/material";
+import { Text } from "@/components/ui/text";
+
+/** Dot plus text, so the status never reads by colour alone. */
+export function VendorStatusBadge({ isActive }: { isActive: boolean }) {
+ return (
+
+
+ {isActive ? "Active" : "Inactive"}
+
+ );
+}
diff --git a/src/app/(protected)/vendors/_components/vendors-table.tsx b/src/app/(protected)/vendors/_components/vendors-table.tsx
index db588cff..e524630c 100644
--- a/src/app/(protected)/vendors/_components/vendors-table.tsx
+++ b/src/app/(protected)/vendors/_components/vendors-table.tsx
@@ -18,6 +18,7 @@ import {
TableRow,
Tooltip,
} from "@mui/material";
+import { VendorStatusBadge } from "./vendor-status-badge";
import { Text } from "@/components/ui/text";
import type { VendorListItem } from "@/domain/vendors/types/vendor";
@@ -50,23 +51,6 @@ function stopPropagation(event: MouseEvent): void {
event.stopPropagation();
}
-function VendorStatus({ isActive }: { isActive: boolean }) {
- return (
-
-
- {isActive ? "Active" : "Inactive"}
-
- );
-}
-
interface VendorTableRowProps {
row: VendorListItem;
onOpenDetail: (row: VendorListItem) => void;
@@ -177,7 +161,9 @@ function VendorTableRow({ row, onOpenDetail, onOpenEdit }: VendorTableRowProps)
{row.totalJobs ?? 0}
-
+
+
+
{
});
});
+describe("VendorDetailDrawer design parity", () => {
+ it("renders the company once when there is no technician name to head the panel", () => {
+ useVendorCompanyRoster.mockReturnValue(rosterWith([]));
+
+ renderWithProviders(
+ ,
+ { route: "/vendors", withAuth: false },
+ );
+
+ const heading = screen.getByRole("heading", { level: 2, name: "Gateway Plumbing" });
+ expect(heading).toBeInTheDocument();
+ // The header block holds the title alone — no subtitle repeating the company.
+ expect(heading.parentElement?.children).toHaveLength(1);
+ });
+
+ it("keeps the company subtitle under a technician name", () => {
+ useVendorCompanyRoster.mockReturnValue(
+ rosterWith([{ id: 1, contactName: "Adam", phone: "", isActive: true, totalJobs: 5 }]),
+ );
+
+ renderWithProviders(, {
+ route: "/vendors",
+ withAuth: false,
+ });
+
+ expect(screen.getByRole("heading", { level: 2, name: "Adam" })).toBeInTheDocument();
+ expect(screen.getAllByText("Gateway Plumbing").length).toBeGreaterThan(0);
+ });
+
+ it("labels the Google Maps field below Address even when no URL is stored", () => {
+ useVendorCompanyRoster.mockReturnValue(
+ rosterWith([{ id: 1, contactName: "Adam", phone: "", isActive: true, totalJobs: 5 }]),
+ );
+
+ renderWithProviders(, {
+ route: "/vendors",
+ withAuth: false,
+ });
+
+ expect(screen.getByText("Google Maps")).toBeInTheDocument();
+ expect(screen.queryByRole("link", { name: /Open in Google Maps/ })).not.toBeInTheDocument();
+ });
+
+ it("links out to Google Maps when a URL is stored", () => {
+ const roster = rosterWith([
+ { id: 1, contactName: "Adam", phone: "", isActive: true, totalJobs: 5 },
+ ]);
+ roster.data.googleMapsUrl = "https://maps.google.com/?q=1+Industrial+Pkwy";
+ useVendorCompanyRoster.mockReturnValue(roster);
+
+ renderWithProviders(, {
+ route: "/vendors",
+ withAuth: false,
+ });
+
+ expect(screen.getByRole("link", { name: /Open in Google Maps/ })).toHaveAttribute(
+ "href",
+ "https://maps.google.com/?q=1+Industrial+Pkwy",
+ );
+ });
+
+ it("puts the status opposite Total Jobs and drops the redundant Status label", () => {
+ useVendorCompanyRoster.mockReturnValue(
+ rosterWith([{ id: 1, contactName: "Adam", phone: "", isActive: true, totalJobs: 5 }]),
+ );
+
+ renderWithProviders(, {
+ route: "/vendors",
+ withAuth: false,
+ });
+
+ const row = screen.getByText("Total Jobs").closest("div")?.parentElement;
+ expect(row).not.toBeNull();
+ expect(getComputedStyle(row as Element).justifyContent).toBe("space-between");
+ expect(row).toHaveTextContent("Active");
+ expect(screen.queryByText("Status")).not.toBeInTheDocument();
+ });
+});
+
describe("VendorDetailDrawer deactivation prompt", () => {
const inactiveVendor: VendorListItem = { ...vendor, isActive: false };