mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
204 lines
6.1 KiB
TypeScript
204 lines
6.1 KiB
TypeScript
import 'server-only'
|
|
|
|
import type { Payload } from 'payload'
|
|
|
|
import type { RelationOption } from '@/components/campaign/shared/RelationMultiSelect'
|
|
import { populatedContactName } from '@/lib/relationship'
|
|
import type { CampaignUser } from '@/payload-types'
|
|
import { eligibleCampaignStaffWhere, getWritableMunicipalityIds } from '@/utilities/campaignAccess'
|
|
|
|
/** Municipalities the actor may operate on (coordinator: all 435; advisor: administered). */
|
|
export const loadMunicipalityOptions = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
): Promise<RelationOption[]> => {
|
|
const result = await payload.find({
|
|
collection: 'municipality',
|
|
depth: 0,
|
|
limit: 0,
|
|
pagination: false,
|
|
sort: 'name',
|
|
select: { name: true },
|
|
where: {},
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
return result.docs.map((municipality) => ({ id: municipality.id, name: municipality.name }))
|
|
}
|
|
|
|
/**
|
|
* C144 — WRITE-scoped variant for the write forms: only municipalities the
|
|
* actor may write under the Edição axis (`getWritableMunicipalityIds`).
|
|
* `null` (unrestricted staff or `editing === 'tudo'`) delegates to the read
|
|
* loader (same query — the whole catalog); `[]` (`somente_leitura`) returns no
|
|
* options without a fetch; a carteira resolves to exactly the portfolio ids.
|
|
* Read surfaces keep `loadMunicipalityOptions`.
|
|
*/
|
|
export const loadWritableMunicipalityOptions = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
): Promise<RelationOption[]> => {
|
|
const writableIds = await getWritableMunicipalityIds(payload, user)
|
|
if (writableIds === null) return loadMunicipalityOptions(payload, user)
|
|
if (writableIds.length === 0) return []
|
|
|
|
const result = await payload.find({
|
|
collection: 'municipality',
|
|
depth: 0,
|
|
limit: 0,
|
|
pagination: false,
|
|
sort: 'name',
|
|
select: { name: true },
|
|
where: { id: { in: writableIds } },
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
return result.docs.map((municipality) => ({ id: municipality.id, name: municipality.name }))
|
|
}
|
|
|
|
export const loadOrganizationOptions = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
): Promise<RelationOption[]> => {
|
|
const result = await payload.find({
|
|
collection: 'organization',
|
|
depth: 0,
|
|
limit: 0,
|
|
pagination: false,
|
|
sort: 'name',
|
|
select: { name: true },
|
|
where: {},
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
return result.docs.map((organization) => ({ id: organization.id, name: organization.name }))
|
|
}
|
|
|
|
/**
|
|
* `name` folds in the party for select/typeahead disambiguation (e.g.
|
|
* `RelationMultiSelect`); `plainName`/`party`/`slug` ride along so cell-level
|
|
* consumers (B31's "Dobradinhas" column) can build a `StateDeputySummary`
|
|
* chip for an optimistic add and link to `/campanha/dobradinhas/<id>`
|
|
* without a second catalog fetch.
|
|
*/
|
|
export type StateDeputyRelationOption = RelationOption & {
|
|
plainName: string
|
|
party: string | null
|
|
slug: string
|
|
}
|
|
|
|
export const loadStateDeputyOptions = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
): Promise<StateDeputyRelationOption[]> => {
|
|
const result = await payload.find({
|
|
collection: 'stateDeputy',
|
|
depth: 1,
|
|
limit: 0,
|
|
pagination: false,
|
|
sort: 'contact.name',
|
|
select: { contact: true, party: true, slug: true },
|
|
where: {},
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
return result.docs.map((stateDeputy) => {
|
|
const plainName = populatedContactName(stateDeputy.contact)
|
|
return {
|
|
id: stateDeputy.id,
|
|
name: stateDeputy.party ? `${plainName} (${stateDeputy.party})` : plainName,
|
|
plainName,
|
|
party: stateDeputy.party ?? null,
|
|
slug: stateDeputy.slug,
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Leadership catalog for typeahead cells (B36). Access of `canReadLeadership`
|
|
* already scopes an advisor to leaderships of administered municipalities —
|
|
* no manual intersection with `getAccessibleLeadershipIds`.
|
|
*/
|
|
export const loadLeadershipOptions = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
): Promise<RelationOption[]> => {
|
|
const result = await payload.find({
|
|
collection: 'leadership',
|
|
depth: 1,
|
|
limit: 0,
|
|
pagination: false,
|
|
select: { contact: true },
|
|
where: {},
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
return result.docs
|
|
.map((leadership) => ({
|
|
id: leadership.id,
|
|
name: populatedContactName(leadership.contact),
|
|
}))
|
|
.sort((left, right) => left.name.localeCompare(right.name, 'pt-BR'))
|
|
}
|
|
|
|
export type CampaignUserSummary = {
|
|
id: number
|
|
name: string
|
|
}
|
|
|
|
/**
|
|
* Names for a set of assigned advisor ids (B156) — the same catalog read the
|
|
* municipios list does with `loadAdvisorSummaries`, but without the phone and
|
|
* in the shared relation-options module (the dobradinhas cells need id+name
|
|
* only). Honours `canReadCampaignUsers` and the eligibility filter, so a
|
|
* stale id that fell out of staff is dropped — same fail-closed contract as
|
|
* `loadAdvisorSummaries`.
|
|
*/
|
|
export const loadCampaignUserSummaries = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
ids: number[],
|
|
): Promise<CampaignUserSummary[]> => {
|
|
if (ids.length === 0) return []
|
|
|
|
const result = await payload.find({
|
|
collection: 'campaignUser',
|
|
where: {
|
|
and: [{ id: { in: ids } }, eligibleCampaignStaffWhere],
|
|
},
|
|
depth: 0,
|
|
pagination: false,
|
|
select: { name: true },
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
const byId = new Map(result.docs.map((doc) => [doc.id, { id: doc.id, name: doc.name }]))
|
|
|
|
return ids.flatMap((id) => {
|
|
const summary = byId.get(id)
|
|
return summary ? [summary] : []
|
|
})
|
|
}
|
|
|
|
/**
|
|
* The addable staff catalog for an advisor-relation cell (B156): every
|
|
* eligible account (coordinator/advisor/candidate), sorted by name. The cell
|
|
* resolves optimistic adds through it, same role as `getEligibleAdvisorOptions`
|
|
* on the municipios list.
|
|
*/
|
|
export const loadEligibleAdvisorOptions = async (
|
|
payload: Payload,
|
|
user: CampaignUser,
|
|
): Promise<RelationOption[]> => {
|
|
const result = await payload.find({
|
|
collection: 'campaignUser',
|
|
depth: 0,
|
|
pagination: false,
|
|
sort: 'name',
|
|
where: eligibleCampaignStaffWhere,
|
|
select: { name: true },
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
return result.docs.map(({ id, name }) => ({ id, name }))
|
|
}
|