/** * Gridify query-string builder. * * The backend list endpoints accept Gridify query params: `filter`, `orderBy`, * `page`, `pageSize`. This module turns typed UI filter state into a Gridify * `filter` string and assembles the query string. * * Gridify filter syntax (subset we use): * field==value equals * field=*value* contains (append /i for case-insensitive) * a==1,b==2 AND (comma) * a==1|b==2 OR (pipe) * Special characters in values (comma, pipe, parens, =, <, >, !, asterisk, * backslash) must be escaped with a leading backslash. */ export interface GridifyQuery { /** Gridify filter expression, already composed. */ filter?: string /** Gridify orderBy expression, e.g. "name" or "dateOfBirth desc". */ orderBy?: string /** 1-based page number. */ page?: number pageSize?: number } /** Escape a value for use inside a Gridify filter expression. */ export function escapeGridifyValue(value: string): string { return value.replace(/([,|()=<>!*\\])/g, '\\$1') } export type GridifyOp = '==' | '!=' | '>' | '<' | '>=' | '<=' | 'contains' export interface FilterCondition { field: string op: GridifyOp value: string | number | boolean /** Case-insensitive match (appends /i). Only meaningful for string ops. */ caseInsensitive?: boolean } /** Build a single Gridify condition, e.g. a name-contains or `status==Active`. */ export function condition(c: FilterCondition): string { const escaped = escapeGridifyValue(String(c.value)) if (c.op === 'contains') { // Gridify's "contains" operator is `=*` — the value follows directly and is // matched as a substring. Do NOT wrap the value in asterisks (SQL-LIKE habit): // a trailing `*` is a literal character to Gridify, so `name=*a*` searches for // the literal "a*" and matches nothing. Correct form: `name=*a`. const suffix = c.caseInsensitive === false ? '' : '/i' return `${c.field}=*${escaped}${suffix}` } // Gridify's equals operator is a single '='; callers use '==' semantically. const op = c.op === '==' ? '=' : c.op const suffix = c.caseInsensitive ? '/i' : '' return `${c.field}${op}${escaped}${suffix}` } /** Join conditions with AND (comma). Falsy/empty conditions are dropped. */ export function andFilter(...conditions: Array): string { return conditions.filter((c): c is string => Boolean(c)).join(',') } /** Assemble the URL query string ("?...") from a GridifyQuery. */ export function toQueryString(q: GridifyQuery): string { const params = new URLSearchParams() if (q.filter) params.set('filter', q.filter) if (q.orderBy) params.set('orderBy', q.orderBy) if (q.page != null) params.set('page', String(q.page)) if (q.pageSize != null) params.set('pageSize', String(q.pageSize)) const s = params.toString() return s ? `?${s}` : '' }