// Shared helpers for parsing and sorting web list query state. function parsePageNumber(value) { const pageNumber = Math.floor(Number(value) || 1); return Math.max(1, pageNumber); } function getSearchQuery(req) { return String(req && req.query && req.query.search || '').trim(); } function getSortQuery(req) { return String(req && req.query && req.query.sort || '').trim(); } function getSortDirectionQuery(req) { return String(req && req.query && req.query.direction || '').trim().toLowerCase() === 'desc' ? 'desc' : 'asc'; } function normalizeSortDirection(value) { return String(value || '').trim().toLowerCase() === 'desc' ? 'desc' : 'asc'; } function getComparableSortValue(rawValue) { const value = String(rawValue || '').trim(); if (!value) { return { type: 'empty', value: '' }; } const numericValue = Number(value.replace(/,/g, '')); if (!Number.isNaN(numericValue)) { return { type: 'number', value: numericValue }; } const dateValue = Date.parse(value); if (!Number.isNaN(dateValue)) { return { type: 'date', value: dateValue }; } return { type: 'string', value: value.toLowerCase() }; } function compareSortValues(leftValue, rightValue) { if (leftValue.type === 'empty' && rightValue.type === 'empty') { return 0; } if (leftValue.type === 'empty') { return 1; } if (rightValue.type === 'empty') { return -1; } if (leftValue.type === rightValue.type) { if (leftValue.value < rightValue.value) { return -1; } if (leftValue.value > rightValue.value) { return 1; } return 0; } return String(leftValue.value).localeCompare(String(rightValue.value), undefined, { numeric: true, sensitivity: 'base' }); } function sortRows(rows, accessor, sortDirection) { const multiplier = normalizeSortDirection(sortDirection) === 'desc' ? -1 : 1; return (Array.isArray(rows) ? rows.slice() : []).sort(function (leftRow, rightRow) { return compareSortValues(getComparableSortValue(accessor(leftRow)), getComparableSortValue(accessor(rightRow))) * multiplier; }); } function createSearchMatcher(searchTerm, fields) { const query = String(searchTerm || '').trim().toLowerCase(); if (!query) { return function () { return true; }; } const normalizedFields = Array.isArray(fields) ? fields : []; return function (item) { const haystack = normalizedFields.map(function (field) { if (typeof field === 'function') { return field(item); } return item && field ? item[field] : ''; }).map(function (value) { return String(value || '').trim().toLowerCase(); }).join(' '); return haystack.indexOf(query) !== -1; }; } module.exports = { parsePageNumber: parsePageNumber, getSearchQuery: getSearchQuery, getSortQuery: getSortQuery, normalizeSortDirection: normalizeSortDirection, getSortDirectionQuery: getSortDirectionQuery, getComparableSortValue: getComparableSortValue, compareSortValues: compareSortValues, sortRows: sortRows, createSearchMatcher: createSearchMatcher };