mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 08:53:11 +00:00
Bring B15/B16-style URL sort/filter to /campanha/liderancas on the B33 mold, with shared CampaignTableHead filter slot and B18 exhaustive enum canonicalize from /simplify.
245 lines
8.1 KiB
TypeScript
245 lines
8.1 KiB
TypeScript
/**
|
||
* Leadership list URL contract: state, param parse/canonicalize, Payload
|
||
* `where`, sort and hrefs. Own module (same shape as `stateDeputyListUrl`) —
|
||
* does not import municipality/territory list modules.
|
||
*/
|
||
import type { Where } from 'payload'
|
||
|
||
import {
|
||
leadershipSectors,
|
||
leadershipSupportStatuses,
|
||
type SupportStatus,
|
||
} from '@/lib/schemas/leadership'
|
||
import {
|
||
allParamValues,
|
||
buildListHref,
|
||
firstValue,
|
||
normalizedText,
|
||
parseExhaustiveEnumParam,
|
||
resolveListUrl,
|
||
strictDecimalInteger,
|
||
type RawSearchParams,
|
||
} from '@/utilities/campaignListUrl'
|
||
|
||
export const leadershipPageSize = 25
|
||
|
||
export type LeadershipListSortKey = 'name' | 'supportStatus' | 'sector' | 'updatedAt'
|
||
export type LeadershipListSortDirection = 'asc' | 'desc'
|
||
|
||
/** Acesso ao app — exclusive filter (com | sem), same shape as municipality coverage. */
|
||
export type LeadershipListAccessFilter = 'com' | 'sem'
|
||
|
||
type LeadershipSector = (typeof leadershipSectors)[number]
|
||
|
||
export type LeadershipListState = {
|
||
page: number
|
||
q?: string
|
||
statuses?: SupportStatus[]
|
||
sectors?: LeadershipSector[]
|
||
municipalities?: number[]
|
||
access?: LeadershipListAccessFilter
|
||
sort?: LeadershipListSortKey
|
||
dir?: LeadershipListSortDirection
|
||
}
|
||
|
||
export type LeadershipListSearchParams = RawSearchParams
|
||
|
||
const leadershipListParamNames = [
|
||
'q',
|
||
'status',
|
||
'sector',
|
||
'municipality',
|
||
'access',
|
||
'sort',
|
||
'dir',
|
||
'page',
|
||
] as const
|
||
const leadershipListParamNameSet = new Set<string>(leadershipListParamNames)
|
||
|
||
const DEFAULT_LEADERSHIP_LIST_SORT_KEY: LeadershipListSortKey = 'updatedAt'
|
||
const DEFAULT_LEADERSHIP_LIST_SORT_DIR: LeadershipListSortDirection = 'desc'
|
||
|
||
export const leadershipListSortLabels: Record<LeadershipListSortKey, string> = {
|
||
name: 'Nome',
|
||
supportStatus: 'Status',
|
||
sector: 'Setor',
|
||
updatedAt: 'Última atualização',
|
||
}
|
||
|
||
const leadershipListSortKeySet = new Set<string>(Object.keys(leadershipListSortLabels))
|
||
const leadershipListSortDirSet = new Set<LeadershipListSortDirection>(['asc', 'desc'])
|
||
const supportStatusSet = new Set<string>(leadershipSupportStatuses)
|
||
const sectorSet = new Set<string>(leadershipSectors)
|
||
const accessFilterSet = new Set<LeadershipListAccessFilter>(['com', 'sem'])
|
||
|
||
/** Temporal = newest first; categorical/name = A–Z. */
|
||
export const defaultLeadershipListSortDir = (
|
||
key: LeadershipListSortKey,
|
||
): LeadershipListSortDirection => (key === 'updatedAt' ? 'desc' : 'asc')
|
||
|
||
export const resolveLeadershipListSort = (
|
||
state: LeadershipListState,
|
||
): { sort: LeadershipListSortKey; dir: LeadershipListSortDirection } => {
|
||
const sort = state.sort ?? DEFAULT_LEADERSHIP_LIST_SORT_KEY
|
||
return { sort, dir: state.dir ?? defaultLeadershipListSortDir(sort) }
|
||
}
|
||
|
||
/** Dotted path for Contact name join; `-` prefix for descending. */
|
||
export const resolveLeadershipListPayloadSort = (
|
||
sort: LeadershipListSortKey,
|
||
dir: LeadershipListSortDirection,
|
||
): string => {
|
||
const path = sort === 'name' ? 'contact.name' : sort
|
||
return dir === 'desc' ? `-${path}` : path
|
||
}
|
||
|
||
export const formatLeadershipListSortSummary = (
|
||
sort: LeadershipListSortKey,
|
||
dir: LeadershipListSortDirection,
|
||
): string => {
|
||
const label = leadershipListSortLabels[sort]
|
||
if (sort === 'updatedAt') {
|
||
return dir === 'desc'
|
||
? `Ordenado por ${label} (mais recente)`
|
||
: `Ordenado por ${label} (mais antigo)`
|
||
}
|
||
return dir === 'asc' ? `Ordenado por ${label} (A–Z)` : `Ordenado por ${label} (Z–A)`
|
||
}
|
||
|
||
export const leadershipListStateToRawParams = (
|
||
state: LeadershipListState,
|
||
page = state.page,
|
||
): LeadershipListSearchParams => ({
|
||
page: String(page),
|
||
q: state.q,
|
||
status: state.statuses,
|
||
sector: state.sectors,
|
||
municipality: state.municipalities?.map(String),
|
||
access: state.access,
|
||
sort: state.sort,
|
||
dir: state.dir,
|
||
})
|
||
|
||
export const parseLeadershipListParams = (
|
||
params: LeadershipListSearchParams,
|
||
): LeadershipListState => {
|
||
const rawPage = strictDecimalInteger(firstValue(params.page))
|
||
const q = normalizedText(firstValue(params.q))
|
||
const statuses = parseExhaustiveEnumParam<SupportStatus>(params.status, supportStatusSet)
|
||
const sectors = parseExhaustiveEnumParam<LeadershipSector>(params.sector, sectorSet)
|
||
const municipalities = allParamValues(params.municipality)
|
||
.map((token) => strictDecimalInteger(token))
|
||
.filter((id): id is number => typeof id === 'number' && id > 0)
|
||
const rawAccess = firstValue(params.access)
|
||
const access =
|
||
rawAccess && accessFilterSet.has(rawAccess as LeadershipListAccessFilter)
|
||
? (rawAccess as LeadershipListAccessFilter)
|
||
: undefined
|
||
const rawSort = firstValue(params.sort)
|
||
const sort =
|
||
rawSort && leadershipListSortKeySet.has(rawSort)
|
||
? (rawSort as LeadershipListSortKey)
|
||
: undefined
|
||
const rawDir = firstValue(params.dir)
|
||
const dir =
|
||
rawDir && leadershipListSortDirSet.has(rawDir as LeadershipListSortDirection)
|
||
? (rawDir as LeadershipListSortDirection)
|
||
: undefined
|
||
|
||
return {
|
||
page: rawPage ?? 1,
|
||
...(q ? { q } : {}),
|
||
...(statuses.length ? { statuses } : {}),
|
||
...(sectors.length ? { sectors } : {}),
|
||
...(municipalities.length ? { municipalities } : {}),
|
||
...(access ? { access } : {}),
|
||
...(sort ? { sort } : {}),
|
||
...(dir ? { dir } : {}),
|
||
}
|
||
}
|
||
|
||
export const buildLeadershipListWhere = (state: LeadershipListState): Where => {
|
||
const filters: Where[] = []
|
||
if (state.q) filters.push({ 'contact.name': { contains: state.q } })
|
||
if (state.statuses?.length) filters.push({ supportStatus: { in: state.statuses } })
|
||
if (state.sectors?.length) filters.push({ sector: { in: state.sectors } })
|
||
if (state.municipalities?.length) {
|
||
filters.push({ municipalities: { in: state.municipalities } })
|
||
}
|
||
if (state.access) {
|
||
filters.push({ user: { exists: state.access === 'com' } })
|
||
}
|
||
return filters.length ? { and: filters } : {}
|
||
}
|
||
|
||
/** Expects already-canonical state (from parse or a rule-preserving toggle). */
|
||
export const serializeCanonicalLeadershipListSearchParams = (
|
||
canonicalState: LeadershipListState,
|
||
): URLSearchParams => {
|
||
const params = new URLSearchParams()
|
||
const resolvedSort = canonicalState.sort ?? DEFAULT_LEADERSHIP_LIST_SORT_KEY
|
||
const resolvedDir = canonicalState.dir ?? defaultLeadershipListSortDir(resolvedSort)
|
||
const isSortDefault =
|
||
resolvedSort === DEFAULT_LEADERSHIP_LIST_SORT_KEY &&
|
||
resolvedDir === DEFAULT_LEADERSHIP_LIST_SORT_DIR
|
||
|
||
if (canonicalState.q) params.set('q', canonicalState.q)
|
||
for (const status of canonicalState.statuses ?? []) params.append('status', status)
|
||
for (const sector of canonicalState.sectors ?? []) params.append('sector', sector)
|
||
for (const municipality of canonicalState.municipalities ?? []) {
|
||
params.append('municipality', String(municipality))
|
||
}
|
||
if (canonicalState.access) params.set('access', canonicalState.access)
|
||
if (!isSortDefault) {
|
||
params.set('sort', resolvedSort)
|
||
if (resolvedDir !== defaultLeadershipListSortDir(resolvedSort)) {
|
||
params.set('dir', resolvedDir)
|
||
}
|
||
}
|
||
if (canonicalState.page > 1) params.set('page', String(canonicalState.page))
|
||
|
||
return params
|
||
}
|
||
|
||
const buildLeadershipListSearchParams = (
|
||
state: LeadershipListState,
|
||
page = state.page,
|
||
): URLSearchParams =>
|
||
serializeCanonicalLeadershipListSearchParams(
|
||
parseLeadershipListParams(leadershipListStateToRawParams(state, page)),
|
||
)
|
||
|
||
export const buildLeadershipListHref = (state: LeadershipListState, page: number): string =>
|
||
buildListHref(state, buildLeadershipListSearchParams, '/campanha/liderancas', page)
|
||
|
||
export const buildLeadershipSortHref = (
|
||
state: LeadershipListState,
|
||
nextKey: LeadershipListSortKey,
|
||
): string => {
|
||
const current = resolveLeadershipListSort(state)
|
||
const dir =
|
||
current.sort === nextKey
|
||
? current.dir === 'asc'
|
||
? 'desc'
|
||
: 'asc'
|
||
: defaultLeadershipListSortDir(nextKey)
|
||
|
||
return buildLeadershipListHref({ ...state, sort: nextKey, dir, page: 1 }, 1)
|
||
}
|
||
|
||
export const resolveLeadershipListUrl = (
|
||
params: LeadershipListSearchParams,
|
||
totalPages?: number,
|
||
): {
|
||
state: LeadershipListState
|
||
href: string
|
||
redirectHref?: string
|
||
} =>
|
||
resolveListUrl({
|
||
params,
|
||
paramNameSet: leadershipListParamNameSet,
|
||
parse: parseLeadershipListParams,
|
||
buildSearchParams: buildLeadershipListSearchParams,
|
||
basePath: '/campanha/liderancas',
|
||
totalPages,
|
||
})
|