teqo-backup/src/lib/schemas/municipality.ts
Francisco Ignácio Fontoura Solla 01e52834e2 B19: Gerenciar assessores (/campanha/assessores)
Lista+detalhe única de edição para coordinator/candidate: nome, e-mail e
celular com auto-save debounced (edit-where-you-see-it); carteira de
municípios por chips com agregação de território de identidade,
adição/remoção com UI otimista; criação inline no topo da tabela; ação de
WhatsApp por assessor; reset de senha via e-mail (bloqueado para
placeholders @planilha.invalid). Alinha access de update/phone/atribuição
de município a isCampaignUnrestricted (coordinator + candidate).

Sem migration. Testes int (campaignAdvisorManagement) + unit (schema e
portfólio de municípios) cobrindo access, conflito de e-mail único, cap de
assessores por município e agregação por território/ZE.
2026-07-25 02:40:47 -03:00

58 lines
2.2 KiB
TypeScript

import { z } from 'zod'
import { positiveRelationshipId, trimmedNullableText } from '@/lib/schemas/primitives'
import { voteEstimateScenarioFieldsSchema } from '@/lib/schemas/votePledge'
export const politicalTrendStatuses = ['favoravel', 'neutra', 'desfavoravel'] as const
export type PoliticalTrendStatusValue = (typeof politicalTrendStatuses)[number]
const stateDeputiesArraySchema = z
.array(positiveRelationshipId)
.max(20)
.transform((ids) => [...new Set(ids)])
export const municipalityStrategyUpdateSchema = z.object({
municipality: positiveRelationshipId,
priority: z.enum(['alta', 'normal']).optional(),
strengths: z.array(z.string().trim().min(1).max(1000)).max(20).optional(),
risks: z.array(z.string().trim().min(1).max(1000)).max(20).optional(),
stateDeputies: stateDeputiesArraySchema.optional(),
dobradinhaNotes: trimmedNullableText(4000),
nextSteps: trimmedNullableText(4000),
budgetNotes: trimmedNullableText(4000),
})
export const municipalityPoliticalTrendSchema = z.object({
municipality: positiveRelationshipId,
status: z.enum(politicalTrendStatuses).nullable(),
note: trimmedNullableText(2000),
})
export const parsePoliticalTrendStatusFormValue = (
raw: string | undefined,
): PoliticalTrendStatusValue | null => {
if (!raw) return null
return politicalTrendStatuses.includes(raw as PoliticalTrendStatusValue)
? (raw as PoliticalTrendStatusValue)
: null
}
export const MAX_ADVISORS_PER_MUNICIPALITY = 10
export const municipalityAdvisorsAssignmentSchema = z.object({
municipality: positiveRelationshipId,
advisors: z
.array(positiveRelationshipId)
.max(MAX_ADVISORS_PER_MUNICIPALITY)
.transform((ids) => [...new Set(ids)]),
})
export const municipalityExpectedVotesSchema = z.object({
municipality: positiveRelationshipId,
expectedVotes: voteEstimateScenarioFieldsSchema,
})
export type MunicipalityStrategyUpdateInput = z.input<typeof municipalityStrategyUpdateSchema>
export type MunicipalityPoliticalTrendInput = z.input<typeof municipalityPoliticalTrendSchema>
export type MunicipalityAdvisorsAssignmentInput = z.input<typeof municipalityAdvisorsAssignmentSchema>
export type MunicipalityExpectedVotesInput = z.input<typeof municipalityExpectedVotesSchema>