fix(boss-dashboard): filter to active reps only + fix PB date-filter bug

The reps list seeded a row for every `users` record (admin, test
accounts, drivers, duplicate entries), not just the 6 real sales reps.
Also, window filters built with toISOString() used a "T"-separated
date PB's filter engine doesn't match against `created`, so every
window silently returned zero rows — the whole dashboard read 0
regardless of real activity. Same fix pattern already used in
order-delivery-note-link-service.ts / order-order-duplicate-link-service.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-08-31 22:14:40 +02:00
parent 40993ce8f8
commit f05fc5d9cf
3 changed files with 66 additions and 9 deletions

View file

@ -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({

View file

@ -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<string> = 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";

View file

@ -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",