The order list and order sprint pages were rendering the rep column with a raw PocketBase record id, which the operator could not recognize. The data layer now resolves the rep through the user's name: - New `resolveRepDisplayName` helper (`src/lib/order-rep.ts`) takes the customer record's `expand.rep`, the `customer.rep` relation id, the legacy `orders.rep` text snapshot, and an admin-resolved user map, and returns a name (or empty when only an unresolved PB id is present). - The orders service now fetches the user list alongside the order page/counts query and resolves the rep on the way out, so the client receives a `repName` per row. New endpoints `/api/v1/orders/list` and `/api/v1/orders/[id]/review` expose the read model; the page and sprint calls go through them. - The list card and the sprint table + mobile card render the resolved name. An opaque PB id is never displayed. Migrates `/orders` off `pb-helpers.ts` per the data-layer discipline, and adds a shared browser-session helper for `Authorization: Bearer <PB token>` validation in route handlers that serve the dashboard UI (vs. the agent API). Co-authored-by: Orca <help@stably.ai>
56 lines
2 KiB
TypeScript
56 lines
2 KiB
TypeScript
import type { User } from "@/lib/types";
|
|
|
|
const POCKETBASE_RECORD_ID = /^[a-z0-9]{15}$/i;
|
|
|
|
type RepUser = Pick<User, "id" | "name" | "email">;
|
|
|
|
export interface ResolveRepDisplayNameOptions {
|
|
/** Expanded `customer.rep` record, when PocketBase returned it. */
|
|
expandedCustomerRep?: Partial<RepUser> | null;
|
|
/** Raw relation id stored in `customer.rep`. */
|
|
customerRepId?: string | null;
|
|
/** Legacy/free-text snapshot stored in `orders.rep`. */
|
|
orderRep?: string | null;
|
|
/** Admin-resolved users, keyed by PocketBase user id. */
|
|
usersById?: ReadonlyMap<string, RepUser>;
|
|
}
|
|
|
|
function userLabel(user?: Partial<RepUser> | null): string {
|
|
return (user?.name || user?.email || "").trim();
|
|
}
|
|
|
|
/**
|
|
* Resolves the sales rep shown on order surfaces without ever leaking an
|
|
* opaque PocketBase record id into the UI.
|
|
*
|
|
* Customer ownership is authoritative, so its expanded/user-map value wins.
|
|
* `orders.rep` remains the fallback because company-only and older orders can
|
|
* carry a manually entered name there. Some older rows stored a user id in
|
|
* that text field; those ids are resolved through `usersById`, or hidden when
|
|
* the related user no longer exists.
|
|
*/
|
|
export function resolveRepDisplayName({
|
|
expandedCustomerRep,
|
|
customerRepId,
|
|
orderRep,
|
|
usersById,
|
|
}: ResolveRepDisplayNameOptions): string {
|
|
const expandedLabel = userLabel(expandedCustomerRep);
|
|
if (expandedLabel) return expandedLabel;
|
|
|
|
const customerRef = (customerRepId || "").trim();
|
|
if (customerRef) {
|
|
const customerUserLabel = userLabel(usersById?.get(customerRef));
|
|
if (customerUserLabel) return customerUserLabel;
|
|
}
|
|
|
|
const orderValue = (orderRep || "").trim();
|
|
if (!orderValue) return "";
|
|
|
|
const orderUserLabel = userLabel(usersById?.get(orderValue));
|
|
if (orderUserLabel) return orderUserLabel;
|
|
|
|
// A free-text name is safe to show. An unresolved PB id is not useful to
|
|
// an operator, so suppress it instead of regressing to the old ID display.
|
|
return POCKETBASE_RECORD_ID.test(orderValue) ? "" : orderValue;
|
|
}
|