diff --git a/src/app/api/v1/boss-dashboard/route.test.ts b/src/app/api/v1/boss-dashboard/route.test.ts index bb5bbfa..0bd5105 100644 --- a/src/app/api/v1/boss-dashboard/route.test.ts +++ b/src/app/api/v1/boss-dashboard/route.test.ts @@ -7,7 +7,7 @@ vi.mock("@/lib/server/boss-dashboard-service", () => ({ getBossDashboardData: (...args: unknown[]) => getBossDashboardData(...args), })); -import { GET } from "./route"; +import { ACTIVE_REP_IDS, GET } from "./route"; // --- Helpers -------------------------------------------------------------- @@ -120,8 +120,8 @@ describe("GET /api/v1/boss-dashboard", () => { orderTotals: { total: 7, delivered: 3, invoiced: 1 }, reps: [ { - id: "u1", - name: "Alice", + id: "krpyec9lblra0qz", // Gary — one of the active reps + name: "Gary", isUnknown: false, quotesLoaded: 4, quotesDoneFor: 2, @@ -144,7 +144,7 @@ describe("GET /api/v1/boss-dashboard", () => { expect(body.rangeEnd).toBe("2026-08-29T00:00:00.000Z"); expect(body.orderTotals).toEqual({ total: 7, delivered: 3, invoiced: 1 }); expect(body.reps).toHaveLength(1); - expect(body.reps[0].name).toBe("Alice"); + expect(body.reps[0].name).toBe("Gary"); // viewerName merged from the auth-refresh response. expect(body.viewerName).toBe("Dave"); @@ -204,6 +204,31 @@ describe("GET /api/v1/boss-dashboard", () => { expect((await res.json()).error).toBe("PB down"); }); + it("filters reps down to the active allow-list, dropping everyone else", async () => { + mockAuthRefreshOk(); + const [firstActiveId] = ACTIVE_REP_IDS; + getBossDashboardData.mockResolvedValueOnce({ + window: "today", + rangeStart: "2026-08-28T00:00:00.000Z", + rangeEnd: "2026-08-29T00:00:00.000Z", + orderTotals: { total: 0, delivered: 0, invoiced: 0 }, + reps: [ + { id: firstActiveId, name: "Active Rep", isUnknown: false, quotesLoaded: 1, quotesDoneFor: 0, quotesDoneBy: 0, ordersPlaced: 0 }, + { id: "not-in-allow-list", name: "Admin", isUnknown: false, quotesLoaded: 5, quotesDoneFor: 0, quotesDoneBy: 0, ordersPlaced: 0 }, + { id: "", name: "(unknown)", isUnknown: true, quotesLoaded: 2, quotesDoneFor: 0, quotesDoneBy: 0, ordersPlaced: 0 }, + ], + drivers: [], + }); + + const res = await GET( + authedRequest("http://localhost/api/v1/boss-dashboard?window=today"), + ); + const body = await res.json(); + + expect(body.reps).toHaveLength(1); + expect(body.reps[0].id).toBe(firstActiveId); + }); + it("forwards the 'week' window to the service", async () => { mockAuthRefreshOk(); getBossDashboardData.mockResolvedValueOnce({ diff --git a/src/app/api/v1/boss-dashboard/route.ts b/src/app/api/v1/boss-dashboard/route.ts index 81122ee..736d4d1 100644 --- a/src/app/api/v1/boss-dashboard/route.ts +++ b/src/app/api/v1/boss-dashboard/route.ts @@ -13,6 +13,23 @@ export const dynamic = "force-dynamic"; const VALID_WINDOWS: BossWindow[] = ["today", "week", "month"]; +/** + * The boss only wants his active sales reps on this view. `users` also + * holds admin/test/driver/picker accounts, which the service seeds as + * zero-activity rep rows same as everyone else — filter them out here + * rather than in the service, so the tested aggregation math stays + * generic. Keyed by id (not name) so a display-name edit in PB admin + * doesn't silently drop someone off the list. + */ +export const ACTIVE_REP_IDS: ReadonlySet = new Set([ + "krpyec9lblra0qz", // Gary + "54uj39i4juqkh9x", // Tiaan + "wvfr6q8ppjnkkrs", // Chris + "j3sgntvcqpc7gt5", // Conar + "63zd0ys47pvs9w2", // Karin + "hlu4yn214ceu11b", // Pieter +]); + async function assertBrowserSession(req: NextRequest) { const auth = req.headers.get("authorization"); if (!auth) { @@ -51,7 +68,12 @@ export async function GET(req: NextRequest) { } const data = await getBossDashboardData({ window: raw as BossWindow }); - return NextResponse.json({ ...data, viewerName: user?.record?.name ?? null }); + const reps = data.reps.filter((r) => ACTIVE_REP_IDS.has(r.id)); + return NextResponse.json({ + ...data, + reps, + viewerName: user?.record?.name ?? null, + }); } catch (error) { const message = error instanceof Error ? error.message : "Failed to load dashboard"; diff --git a/src/lib/server/boss-dashboard-service.ts b/src/lib/server/boss-dashboard-service.ts index e9bfccd..7365388 100644 --- a/src/lib/server/boss-dashboard-service.ts +++ b/src/lib/server/boss-dashboard-service.ts @@ -48,6 +48,16 @@ export function windowRange(window: BossWindow, now: Date = new Date()): WindowR return { rangeStart: start.toISOString(), rangeEnd: end.toISOString() }; } +/** + * PB filter clauses compare `created` (stored as "YYYY-MM-DD HH:mm:ss.sssZ") + * against the literal string given — a "T"-separated ISO string like + * `toISOString()` produces does not match and silently returns zero rows. + * Same fix as `order-delivery-note-link-service.ts` / `order-order-duplicate-link-service.ts`. + */ +function toPbFilterDate(iso: string): string { + return iso.replace("T", " "); +} + // --------------------------------------------------------------------------- // Boss dashboard aggregation // --------------------------------------------------------------------------- @@ -106,8 +116,8 @@ export async function getBossDashboardData({ const todayRange = windowRange("today", now); const pb = await getAdminPocketBase(); - const rangeFilter = `created >= "${rangeStart}" && created < "${rangeEnd}"`; - const todayRangeFilter = `created >= "${todayRange.rangeStart}" && created < "${todayRange.rangeEnd}"`; + const rangeFilter = `created >= "${toPbFilterDate(rangeStart)}" && created < "${toPbFilterDate(rangeEnd)}"`; + const todayRangeFilter = `created >= "${toPbFilterDate(todayRange.rangeStart)}" && created < "${toPbFilterDate(todayRange.rangeEnd)}"`; const [users, customers, orders, quotes, todayItems, orderItemsInWindow] = await Promise.all([ pb.collection("users").getFullList({ fields: "id,name" }), @@ -368,7 +378,7 @@ export async function getBossRepActivity({ if (kind === "quotes") { // Quotes owned by this rep = claimed_by = rep.id const result = await pb.collection("quotes").getList(page, PAGE_SIZE, { - filter: `claimed_by = "${rep}" && created >= "${rangeStart}" && created < "${rangeEnd}"`, + filter: `claimed_by = "${rep}" && created >= "${toPbFilterDate(rangeStart)}" && created < "${toPbFilterDate(rangeEnd)}"`, sort: "-created", fields: "id,quote_number,customer,status,created", expand: "customer", @@ -391,7 +401,7 @@ export async function getBossRepActivity({ // kind === "orders" const result = await pb.collection("orders").getList(page, PAGE_SIZE, { - filter: `rep = "${rep}" && created >= "${rangeStart}" && created < "${rangeEnd}"`, + filter: `rep = "${rep}" && created >= "${toPbFilterDate(rangeStart)}" && created < "${toPbFilterDate(rangeEnd)}"`, sort: "-created", fields: "id,order_number,customer,item_status,created", expand: "customer",