diff --git a/docs/work-orders/pending-be-contract.md b/docs/work-orders/pending-be-contract.md
new file mode 100644
index 00000000..a3a92f53
--- /dev/null
+++ b/docs/work-orders/pending-be-contract.md
@@ -0,0 +1,58 @@
+# Pending BE contract (WO design parity gaps)
+
+Local checklist for FE branches off `dev`. Confirm with backend before shipping PR3/PR5 to production. Until confirmed, FE may use typed clients + mocks.
+
+## Board row / PATCH
+
+| Field | Needed by | Notes |
+| -------------------------------------------------------- | ------------- | ---------------------------------------------------------------------- |
+| `completedDate` on GET `/board` and `/board/search` rows | PR1, PR4 | PATCH `field: "completedDate"` expected; confirm GET includes ISO date |
+| `severity` (`1`–`5` \| `null`) on row, create, PATCH | PR2, PR5 | Required for Emergency/Reactive; PATCH `field: "severity"` |
+| `upliftSummary` on row | PR3, PR4, PR5 | `{ hasUplift, pendingCount, primaryStatus? }` |
+| `hasPendingUplift` on row/detail | PR4 | Prefer server 422 on Completed / completion-doc when pending |
+| `additionalContacts[]` | PR6 | `{ name, phone, notes? }[]` on create/detail/PATCH |
+| `isAddOn` | PR5 (filter) | Indicator, not a WO type (SH-126) |
+
+## Proposed WO-scoped uplift endpoints (confirm vs NTE `uplifts` queue)
+
+| Method | Path | Body |
+| ------ | ------------------------------------------- | --------------------------------- |
+| GET | `workorders/{id}/uplifts` | — |
+| POST | `workorders/{id}/uplifts` | `{ amount, notes, attachments? }` |
+| POST | `workorders/{id}/uplifts/{upliftId}/cancel` | — |
+| POST | `workorders/{id}/uplifts/{upliftId}/revoke` | `{ reason? }` |
+
+Statuses (design): `pending` \| `approved` \| `auto_approved` \| `rejected` \| `cancelled` \| `revoked`.
+
+## Search query params (`GET workorders/board/search`)
+
+| Param | Meaning |
+| ------------------------------ | ------------------------- |
+| `severities` | multi `1`–`5` |
+| `rescheduled` / `carriedOver` | boolean (≥2 counters) |
+| `addOn` | boolean (`isAddOn`) |
+| `flagColors` | multi `#RRGGBB` |
+| `internalOnly` | WO# starts with `SH` |
+| `avetaOnly` | if `avetaRequired` exists |
+| `hasUplift` + `upliftStatuses` | uplift filters |
+
+Already documented in `board-search-api.md`: datePreset, sites, types, overdue, dispatchers, statuses, pmTypes, vendorIds, docStatuses.
+
+## Comments
+
+| Field | Needed by |
+| ---------------------------------------- | --------- |
+| POST `{ text, mentions?: string[] }` | PR9 |
+| Response includes mentions for highlight | PR9 |
+
+## Vendor
+
+| Item | Needed by |
+| -------------------------------------------------------------- | --------- |
+| Create technician under company (confirm existing vendors API) | PR7 |
+
+## Optional
+
+| Item | Needed by |
+| ----------------------------------- | -------------------------------- |
+| 422 on duplicate WO number (global) | PR8 (FE dialog works without it) |
diff --git a/e2e/vendors/vendors.spec.ts b/e2e/vendors/vendors.spec.ts
index 8bbfdfb8..fb46f610 100644
--- a/e2e/vendors/vendors.spec.ts
+++ b/e2e/vendors/vendors.spec.ts
@@ -310,7 +310,7 @@ test.describe("Vendor directory prototype parity", () => {
await page.goto("/vendors");
await expect(page.getByRole("heading", { name: "Vendors / Technicians" })).toBeVisible();
- await expect(page.getByText("3 vendor entries · 2 active")).toBeVisible();
+ await expect(page.getByText("3 technicians · 2 active")).toBeVisible();
await expect(page.getByRole("tab", { name: "Active (2)" })).toHaveAttribute(
"aria-selected",
"true",
diff --git a/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx b/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx
index e3e7cfa5..a6ee3797 100644
--- a/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx
+++ b/src/app/(protected)/vendors/_components/vendors-list-toolbar.tsx
@@ -1,4 +1,3 @@
-import FilterListIcon from "@mui/icons-material/FilterList";
import {
Button,
Chip,
@@ -8,6 +7,9 @@ import {
TextField,
Typography,
} from "@mui/material";
+import { SlidersHorizontal } from "lucide-react";
+import { PageHeader } from "@/components/ui/page-header";
+import { ToolbarButton } from "@/components/ui/toolbar-button";
interface VendorsListToolbarProps {
globalCount: number;
@@ -33,52 +35,34 @@ export function VendorsListToolbar({
onAddVendor,
}: VendorsListToolbarProps) {
return (
-
-
-
- Vendors / Technicians
-
-
- {globalCount} vendor entries · {activeCount} active
-
- {isFetching && !isLoading && }
-
-
-
- onSearchChange(e.target.value)}
- placeholder="Search name, company, trade..."
- className="min-w-[260px]"
- />
- }
- aria-label="Open vendor filters"
- >
- Filters
- {appliedFilterCount > 0 && (
-
- )}
-
-
-
-
+
+ onSearchChange(e.target.value)}
+ placeholder="Search name, company, trade..."
+ className="min-w-[260px]"
+ />
+ }
+ onClick={onOpenFilters}
+ aria-label="Open vendor filters"
+ badge={appliedFilterCount}
+ >
+ Filters
+
+
+ {Boolean(isFetching && !isLoading) && }
+ >
+ }
+ />
);
}
diff --git a/src/app/(protected)/vendors/_components/vendors-table.tsx b/src/app/(protected)/vendors/_components/vendors-table.tsx
index 802fff8a..5296c5f6 100644
--- a/src/app/(protected)/vendors/_components/vendors-table.tsx
+++ b/src/app/(protected)/vendors/_components/vendors-table.tsx
@@ -134,7 +134,7 @@ function VendorTableRow({ row, onOpenDetail, onOpenDeactivation }: VendorTableRo
"—"
)}
- {row.totalJobs ?? 0}
+ {row.totalJobs ?? 0}
(
{column}