| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- export const SEARCH_RESULTS_SORT = Object.freeze({
- RELEVANCE: "relevance",
- DATE_DESC: "date_desc",
- BRANCH_ASC: "branch_asc",
- });
- function pad2(value) {
- return String(value || "").padStart(2, "0");
- }
- /**
- * Extract a comparable branch number from ids like "NL01", "NL200".
- *
- * Why this exists:
- * - A lexicographic compare would sort "NL10" before "NL2" (wrong).
- * - We want deterministic ordering that matches how humans read branch ids.
- *
- * @param {any} branchId
- * @returns {number|null}
- */
- function toBranchNumber(branchId) {
- const raw = String(branchId || "").trim();
- const match = /^NL(\d+)$/i.exec(raw);
- if (!match) return null;
- const n = Number(match[1]);
- return Number.isInteger(n) ? n : null;
- }
- /**
- * Compare two branch ids.
- *
- * Policy:
- * - If both look like NL<number>, compare numerically (NL2 < NL10).
- * - If only one looks like NL<number>, prefer the valid one first.
- * - Otherwise fall back to a stable lexicographic compare.
- *
- * @param {any} a
- * @param {any} b
- * @returns {number}
- */
- function compareBranchIds(a, b) {
- const aa = String(a || "");
- const bb = String(b || "");
- const na = toBranchNumber(aa);
- const nb = toBranchNumber(bb);
- if (na !== null && nb !== null) return na - nb;
- if (na !== null && nb === null) return -1;
- if (na === null && nb !== null) return 1;
- return aa.localeCompare(bb, "en");
- }
- /**
- * Build an ISO-like date key (YYYY-MM-DD) from a search item.
- *
- * Note:
- * - The backend guarantees year/month/day for search items.
- * - We still keep this defensive to avoid runtime crashes on malformed items.
- *
- * @param {any} item
- * @returns {string}
- */
- export function toSearchItemIsoDateKey(item) {
- const y = String(item?.year || "");
- const m = pad2(item?.month);
- const d = pad2(item?.day);
- return `${y}-${m}-${d}`;
- }
- /**
- * Format the search item date as German UI string: DD.MM.YYYY
- *
- * Important:
- * - This is user-facing output (German).
- * - We still keep it pure/testable and independent from UI frameworks.
- *
- * @param {any} item
- * @returns {string}
- */
- export function formatSearchItemDateDe(item) {
- const y = String(item?.year || "");
- const m = pad2(item?.month);
- const d = pad2(item?.day);
- return `${d}.${m}.${y}`;
- }
- /**
- * Sort search items according to the selected sort mode.
- *
- * UX/API policy:
- * - RELEVANCE:
- * Keep backend order (assumed relevance-ranked). We still return a shallow copy
- * to avoid accidental mutations by callers.
- *
- * - DATE_DESC:
- * Newest date first.
- * Tie-breakers: branch (stable, numeric for NLxx), then filename asc.
- *
- * - BRANCH_ASC:
- * Branch ascending (NL01..NLxx), then newest date first, then filename asc.
- * This keeps multi/all searches easy to scan without introducing UI grouping headers.
- *
- * @param {any[]} items
- * @param {"relevance"|"date_desc"|"branch_asc"|string} sortMode
- * @returns {any[]}
- */
- export function sortSearchItems(items, sortMode) {
- const arr = Array.isArray(items) ? [...items] : [];
- if (sortMode === SEARCH_RESULTS_SORT.RELEVANCE) return arr;
- if (sortMode === SEARCH_RESULTS_SORT.DATE_DESC) {
- return arr.sort((a, b) => {
- const da = toSearchItemIsoDateKey(a);
- const db = toSearchItemIsoDateKey(b);
- // Newest first
- if (da !== db) return da < db ? 1 : -1;
- // Stable tie-breakers
- const ba = a?.branch;
- const bb = b?.branch;
- const branchCmp = compareBranchIds(ba, bb);
- if (branchCmp !== 0) return branchCmp;
- const fa = String(a?.filename || "");
- const fb = String(b?.filename || "");
- return fa.localeCompare(fb, "de");
- });
- }
- if (sortMode === SEARCH_RESULTS_SORT.BRANCH_ASC) {
- return arr.sort((a, b) => {
- const ba = a?.branch;
- const bb = b?.branch;
- const branchCmp = compareBranchIds(ba, bb);
- if (branchCmp !== 0) return branchCmp;
- // Within the same branch: newest date first
- const da = toSearchItemIsoDateKey(a);
- const db = toSearchItemIsoDateKey(b);
- if (da !== db) return da < db ? 1 : -1;
- const fa = String(a?.filename || "");
- const fb = String(b?.filename || "");
- return fa.localeCompare(fb, "de");
- });
- }
- // Unknown sort mode => fail-safe to backend order.
- return arr;
- }
|