teqo/src/collections/Supporter.ts
Francisco Ignácio Fontoura Solla e357c3cc19 Pass 2 W4f: varredura de copy Praca -> Municipio
- ~70 strings visiveis + labels de admin (headers de coluna, APIErrors, empty states, dashboard, mapa, formularios, filtros) com concordancia de genero corrigida
- apoiadores desambiguado: cidade do contato vira 'Cidade'; a unidade operacional assume 'Municipio' (filtro, coluna, ficha)
- comentarios de codigo atualizados; payload-types regenerado
- fora por decisao D6: textos provisorios de consent do Onda 0 (lote juridico) e migrations congeladas
- e2e do plano de acao acompanha o label ('Municipio *')
2026-07-25 15:12:07 -03:00

229 lines
5.9 KiB
TypeScript

import type { CollectionConfig } from 'payload'
import { APIError } from 'payload'
import {
canCreateSupporter,
canDeleteSupporter,
canManageCampaignStaffField,
canManageSupporter,
canReadCampaignStaffField,
canReadSupporter,
canSetAdministrativeLeadershipField,
} from '@/utilities/campaignAccess'
import { relationshipId } from '@/utilities/relationship'
export const Supporter: CollectionConfig = {
slug: 'supporter',
labels: {
singular: 'Apoiador',
plural: 'Apoiadores',
},
admin: {
group: 'Campanha',
useAsTitle: 'contact',
defaultColumns: ['contact', 'municipality', 'voteIntention', 'source', 'updatedAt'],
},
access: {
create: canCreateSupporter,
read: canReadSupporter,
update: canManageSupporter,
delete: canDeleteSupporter,
},
hooks: {
beforeChange: [
async ({ data, operation, originalDoc, req }) => {
if (operation === 'create' && req.user?.collection === 'campaignUser') {
data.createdBy = req.user.id
}
const contactID = relationshipId(data.contact ?? originalDoc?.contact)
const municipalityID = relationshipId(data.municipality ?? originalDoc?.municipality)
if (!contactID || !municipalityID) return data
const existingLeadership = await req.payload.find({
collection: 'leadership',
where: {
and: [{ contact: { equals: contactID } }, { municipalities: { in: [municipalityID] } }],
},
depth: 0,
limit: 1,
pagination: false,
overrideAccess: true,
req,
})
if (existingLeadership.totalDocs > 0) {
throw new APIError(
'Este contato já é liderança neste município e não pode ser cadastrado como apoiador.',
409,
)
}
return data
},
],
},
fields: [
{
name: 'contact',
type: 'relationship',
relationTo: 'contact',
label: 'Contato',
required: true,
index: true,
access: {
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'municipality',
type: 'relationship',
relationTo: 'municipality',
label: 'Município',
index: true,
access: {
update: canManageCampaignStaffField,
},
},
{
name: 'voteIntention',
type: 'select',
label: 'Intenção de voto',
index: true,
access: {
create: canManageCampaignStaffField,
read: canReadCampaignStaffField,
update: canManageCampaignStaffField,
},
options: [
{ label: 'Certo', value: 'certo' },
{ label: 'Tende a certo', value: 'tende_a_certo' },
{ label: 'Indeciso', value: 'indeciso' },
{ label: 'Outro', value: 'outro' },
],
},
{
name: 'consent',
type: 'relationship',
relationTo: 'consent',
label: 'Consentimento de cadastro',
access: {
create: canSetAdministrativeLeadershipField,
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'consentContentHash',
type: 'text',
label: 'Hash do consentimento de cadastro',
admin: {
readOnly: true,
},
access: {
create: canSetAdministrativeLeadershipField,
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'consentedAt',
type: 'date',
label: 'Consentimento de cadastro em',
admin: {
readOnly: true,
},
access: {
create: canSetAdministrativeLeadershipField,
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'voteIntentionConsent',
type: 'relationship',
relationTo: 'consent',
label: 'Consentimento de intenção de voto',
access: {
create: canSetAdministrativeLeadershipField,
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'voteIntentionConsentContentHash',
type: 'text',
label: 'Hash do consentimento de intenção',
admin: {
readOnly: true,
},
access: {
create: canSetAdministrativeLeadershipField,
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'voteIntentionConsentedAt',
type: 'date',
label: 'Consentimento de intenção em',
admin: {
readOnly: true,
},
access: {
create: canSetAdministrativeLeadershipField,
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'source',
type: 'select',
label: 'Origem',
required: true,
defaultValue: 'manual',
index: true,
options: [
{ label: 'Importação CSV', value: 'import_csv' },
{ label: 'Cadastro manual', value: 'manual' },
{ label: 'Liderança', value: 'lideranca' },
{ label: 'Convite', value: 'convite' },
{ label: 'Evento', value: 'evento' },
],
access: {
update: canSetAdministrativeLeadershipField,
},
},
{
name: 'consentNote',
type: 'textarea',
label: 'Registro de consentimento externo',
maxLength: 2000,
access: {
create: canManageCampaignStaffField,
read: canReadCampaignStaffField,
update: canManageCampaignStaffField,
},
},
{
name: 'notes',
type: 'textarea',
label: 'Observações internas',
maxLength: 3000,
access: {
create: canManageCampaignStaffField,
read: canReadCampaignStaffField,
update: canManageCampaignStaffField,
},
},
{
name: 'createdBy',
type: 'relationship',
relationTo: 'campaignUser',
label: 'Criado por',
index: true,
admin: {
readOnly: true,
},
access: {
create: canSetAdministrativeLeadershipField,
read: canReadCampaignStaffField,
update: canSetAdministrativeLeadershipField,
},
},
],
}