import type { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto"; const MAX_LIMIT = 100; const DEFAULT_LIMIT_WHEN_PAGE_SET = 20; export type ListItemGetters = { publicId: (row: T) => string | undefined; createdAt: (row: T) => Date | string | number | undefined; requestNo?: (row: T) => string | undefined; status: (row: T) => string | undefined; /** Extra strings to match against `search` (e.g. claimRequestId, vehicle). */ searchExtras?: (row: T) => string[]; }; function normalizeSearch(raw?: string): string | undefined { const t = raw?.trim().toLowerCase(); return t || undefined; } function toTime(v: Date | string | number | undefined): number { if (v == null) return 0; const n = v instanceof Date ? v.getTime() : new Date(v as string | number).getTime(); return Number.isFinite(n) ? n : 0; } /** * Filter, sort, and optionally paginate in-memory list results. * Pagination applies only when `query.page` or `query.limit` is set. */ export function applyListQueryV2( rows: T[], getters: ListItemGetters, query: ListQueryV2Dto, ): { list: T[]; total: number; page?: number; limit?: number; totalPages?: number; } { const search = normalizeSearch(query.search); let filtered = rows; if (search) { filtered = rows.filter((row) => { const chunks: string[] = []; const pid = getters.publicId(row); if (pid) chunks.push(pid); const rno = getters.requestNo?.(row); if (rno) chunks.push(rno); const st = getters.status(row); if (st) chunks.push(st); const extras = getters.searchExtras?.(row) ?? []; for (const e of extras) { if (e) chunks.push(e); } const hay = chunks.join(" ").toLowerCase(); return hay.includes(search); }); } const sortBy = query.sortBy ?? "createdAt"; let sortOrder = query.sortOrder; if (!sortOrder) { sortOrder = sortBy === "publicId" || sortBy === "requestNo" ? "asc" : "desc"; } const dir = sortOrder === "asc" ? 1 : -1; const sorted = [...filtered].sort((a, b) => { switch (sortBy) { case "publicId": { const va = String(getters.publicId(a) ?? ""); const vb = String(getters.publicId(b) ?? ""); return dir * va.localeCompare(vb, "en", { numeric: true, sensitivity: "base" }); } case "requestNo": { const va = String( getters.requestNo?.(a) ?? getters.publicId(a) ?? "", ); const vb = String( getters.requestNo?.(b) ?? getters.publicId(b) ?? "", ); return dir * va.localeCompare(vb, "en", { numeric: true, sensitivity: "base" }); } case "status": { const va = String(getters.status(a) ?? ""); const vb = String(getters.status(b) ?? ""); return dir * va.localeCompare(vb, "en", { sensitivity: "base" }); } case "createdAt": default: { const ta = toTime(getters.createdAt(a)); const tb = toTime(getters.createdAt(b)); return dir * (ta - tb); } } }); const total = sorted.length; const wantsPage = query.page != null; const wantsLimit = query.limit != null; if (!wantsPage && !wantsLimit) { return { list: sorted, total }; } const limit = Math.min( MAX_LIMIT, Math.max(1, query.limit ?? (wantsPage ? DEFAULT_LIMIT_WHEN_PAGE_SET : MAX_LIMIT)), ); const page = Math.max(1, query.page ?? 1); const skip = (page - 1) * limit; const list = sorted.slice(skip, skip + limit); const totalPages = Math.max(1, Math.ceil(total / limit)); return { list, total, page, limit, totalPages }; }