The "Municípios" column was dead text; it is now chips that navigate to the município, with a territory chip when a leadership covers a whole TI, and it is editable in place. What picks inline vs. Drawer is the `pointer:` media query, not viewport width: on a fine pointer the editor is always mounted (the "×" sits at the chip's corner, revealed on hover/focus) and on a coarse pointer a tap anywhere in the cell opens a Drawer with 44 px targets. `AdvisorMunicipalityCell` is promoted to `shared/MunicipalityPortfolioCell` and /campanha/assessores migrates onto it in the same change, so the second call site is born with the first. One batch action only; the single-id advisor twin is deleted with its int tests migrated. The anti-goal on always-mounted list inputs is amended for chip-based relations on fine pointers, not reopened. Also fixes a latent bug: `CampaignTableHead`'s `description` seam broke any RSC caller, because its two helpers lived in a 'use client' module.
78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
import { MAX_ADVISORS_PER_MUNICIPALITY } from '@/lib/schemas/municipality'
|
|
import {
|
|
brazilianMobile,
|
|
optionalPersistedEmail,
|
|
positiveRelationshipId,
|
|
trimmedOptionalText,
|
|
} from '@/lib/schemas/primitives'
|
|
|
|
const PLANILHA_INVALID_EMAIL_SUFFIX = '@planilha.invalid'
|
|
|
|
export const isPlanilhaPlaceholderEmail = (email: string | null | undefined): boolean =>
|
|
typeof email === 'string' && email.trim().toLowerCase().endsWith(PLANILHA_INVALID_EMAIL_SUFFIX)
|
|
|
|
/** Stable seed-style placeholder used when the e-mail field is cleared in the list UI. */
|
|
export const planilhaPlaceholderEmailForAdvisor = (advisorId: number): string =>
|
|
`assessor-${advisorId}${PLANILHA_INVALID_EMAIL_SUFFIX}`
|
|
|
|
const advisorPhoneInput = z.union([brazilianMobile, z.literal(''), z.null()]).optional()
|
|
|
|
const optionalAdvisorPhone = advisorPhoneInput.transform((value) =>
|
|
value === '' || value === null || value === undefined ? undefined : value,
|
|
)
|
|
|
|
const nullableAdvisorPhone = advisorPhoneInput.transform((value) =>
|
|
value === '' || value === undefined ? null : value,
|
|
)
|
|
|
|
export const PLACEHOLDER_RESET_MESSAGE =
|
|
'Troque o e-mail placeholder da planilha por um e-mail real antes de enviar o link de senha.'
|
|
|
|
export const ADVISOR_UNRESTRICTED_MESSAGE =
|
|
'Somente a coordenação geral ou o candidato gerencia assessores.'
|
|
|
|
export const ADVISOR_EMAIL_CONFLICT_MESSAGE = 'Já existe uma conta com este e-mail.'
|
|
|
|
export const ADVISOR_ACTION_SAFE_MESSAGES = [
|
|
ADVISOR_UNRESTRICTED_MESSAGE,
|
|
ADVISOR_EMAIL_CONFLICT_MESSAGE,
|
|
PLACEHOLDER_RESET_MESSAGE,
|
|
'Só é possível gerenciar contas com papel de Assessor nesta tela.',
|
|
'Use Meu perfil para alterar a própria conta.',
|
|
`Cada município aceita no máximo ${MAX_ADVISORS_PER_MUNICIPALITY} assessores.`,
|
|
] as const
|
|
|
|
export const advisorCreateSchema = z.object({
|
|
name: z.string().trim().min(2).max(160),
|
|
email: z.email('E-mail inválido.'),
|
|
phone: optionalAdvisorPhone,
|
|
})
|
|
|
|
export const advisorProfileUpdateSchema = z
|
|
.object({
|
|
id: positiveRelationshipId,
|
|
name: trimmedOptionalText(160),
|
|
email: optionalPersistedEmail,
|
|
phone: nullableAdvisorPhone,
|
|
})
|
|
.refine(
|
|
(value) => value.name !== undefined || value.email !== undefined || value.phone !== undefined,
|
|
{ message: 'Informe ao menos um campo para atualizar.' },
|
|
)
|
|
|
|
export const advisorMunicipalitiesBatchSchema = z.object({
|
|
advisorId: positiveRelationshipId,
|
|
municipalityIds: z.array(positiveRelationshipId).min(1).max(435),
|
|
assigned: z.boolean(),
|
|
})
|
|
|
|
export const advisorPasswordResetSchema = z.object({
|
|
advisorId: positiveRelationshipId,
|
|
})
|
|
|
|
export type AdvisorCreateInput = z.input<typeof advisorCreateSchema>
|
|
export type AdvisorProfileUpdateInput = z.input<typeof advisorProfileUpdateSchema>
|
|
export type AdvisorMunicipalitiesBatchInput = z.input<typeof advisorMunicipalitiesBatchSchema>
|
|
export type AdvisorPasswordResetInput = z.input<typeof advisorPasswordResetSchema>
|