// List state lives in the URL query (design: Interactions, D-22): search, // sort, dir, page, per_page and filter[]. These helpers convert between // the route query and the typed list query the API client sends. Anything // malformed (a non-numeric page, an unknown direction, an empty filter) is // dropped, so a hand-edited URL never reaches the server half-valid. import type { LocationQuery, LocationQueryRaw } from 'vue-router' import type { ListQuery } from '../api/types' /** A list row's id as the record carries it. */ export type RowId = number | string const FILTER_KEY = /^filter\[(.+)\]$/ const POSITIVE = /^[1-9]\d*$/ function first(value: LocationQuery[string] | undefined): string { const raw = Array.isArray(value) ? value[value.length - 1] : value return typeof raw === 'string' ? raw : '' } function positive(value: string): number | undefined { return POSITIVE.test(value) ? Number(value) : undefined } export function parseListQuery(query: LocationQuery): ListQuery { const out: ListQuery = {} const search = first(query.search).trim() if (search !== '') { out.search = search } const sort = first(query.sort).trim() if (sort !== '') { out.sort = sort const dir = first(query.dir) out.dir = dir === 'desc' ? 'desc' : 'asc' } const page = positive(first(query.page)) if (page !== undefined) { out.page = page } const perPage = positive(first(query.per_page)) if (perPage !== undefined) { out.per_page = perPage } const filter: Record = {} for (const key of Object.keys(query).sort()) { const name = FILTER_KEY.exec(key)?.[1] const value = first(query[key]) if (name && value !== '') { filter[name] = value } } if (Object.keys(filter).length > 0) { out.filter = filter } return out } export function toListQuery(input: ListQuery): LocationQueryRaw { const out: LocationQueryRaw = {} if (input.search) { out.search = input.search } if (input.sort) { out.sort = input.sort out.dir = input.dir === 'desc' ? 'desc' : 'asc' } if (input.page !== undefined && input.page > 1) { out.page = String(input.page) } if (input.per_page !== undefined) { out.per_page = String(input.per_page) } for (const name of Object.keys(input.filter ?? {}).sort()) { const value = input.filter?.[name] if (value !== undefined && value !== '') { out[`filter[${name}]`] = value } } return out } /** A stable key for comparing two list queries. */ export function listQueryKey(input: ListQuery): string { return JSON.stringify(toListQuery(input)) }