| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState";
- function isNonEmptyString(value) {
- return typeof value === "string" && value.trim().length > 0;
- }
- /**
- * Build the apiClient.search(...) input from URL state + current user context.
- *
- * Why this exists:
- * - Search UI state is URL-driven and shareable.
- * - Cursor is intentionally kept out of the URL by default (client state only).
- * - Role/scoping rules must be enforced consistently (branch users are always single-branch).
- *
- * Return shape:
- * - input: object for apiClient.search(...) or null (no search yet / not ready)
- * - error: ApiClientError or null (local validation / fast-fail)
- *
- * UX policy for MULTI without branches:
- * - Treat it as "not ready" (input=null, error=null) instead of an error.
- * The UI should show a friendly hint (select at least one branch).
- *
- * @param {{
- * urlState: {
- * q: string|null,
- * scope: "single"|"multi"|"all",
- * branch: string|null,
- * branches: string[],
- * from: string|null,
- * to: string|null
- * },
- * routeBranch: string,
- * user: { role: string, branchId: string|null }|null,
- * cursor?: string|null,
- * limit?: number
- * }} args
- * @returns {{ input: any|null, error: any|null }}
- */
- export function buildSearchApiInput({
- urlState,
- routeBranch,
- user,
- cursor = null,
- limit = 100,
- }) {
- const q = isNonEmptyString(urlState?.q) ? urlState.q.trim() : null;
- // No query => no search request. UI should show an "idle" empty state.
- if (!q) return { input: null, error: null };
- const input = { q, limit };
- // Keep from/to as pass-through for RHL-025 (future).
- if (isNonEmptyString(urlState?.from)) input.from = urlState.from.trim();
- if (isNonEmptyString(urlState?.to)) input.to = urlState.to.trim();
- if (isNonEmptyString(cursor)) input.cursor = cursor.trim();
- const role = user?.role;
- // Branch users: always restricted to the current route branch.
- if (role === "branch") {
- input.branch = routeBranch;
- return { input, error: null };
- }
- // Admin/dev: respect scope rules from URL state.
- if (role === "admin" || role === "dev") {
- if (urlState.scope === SEARCH_SCOPE.ALL) {
- input.scope = "all";
- return { input, error: null };
- }
- if (urlState.scope === SEARCH_SCOPE.MULTI) {
- const branches = Array.isArray(urlState.branches)
- ? urlState.branches
- : [];
- // UX: missing branches is not an error, it's "not ready".
- if (branches.length === 0) {
- return { input: null, error: null };
- }
- input.scope = "multi";
- input.branches = branches;
- return { input, error: null };
- }
- // SINGLE
- input.branch = routeBranch;
- return { input, error: null };
- }
- // Unknown role: fail-safe to single-branch using the route context.
- input.branch = routeBranch;
- return { input, error: null };
- }
|