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 *')
This commit is contained in:
Francisco Ignácio Fontoura Solla 2026-07-25 15:12:07 -03:00
parent 11d3fcd24c
commit e357c3cc19
51 changed files with 93 additions and 93 deletions

View file

@ -72,7 +72,7 @@ export default async function SupporterDetailPage({ params }: SupporterDetailPag
<CampaignScopeBadge>{campaignRoleLabels[user.role]}</CampaignScopeBadge>
<h1 className="text-2xl font-semibold tracking-tight">{supporter.name}</h1>
<p className="text-muted-foreground">
{supporter.city ?? 'Município não informado'}
{supporter.city ?? 'Cidade não informada'}
{supporter.territory ? ` · ${supporter.territory}` : ''}
</p>
</header>
@ -109,7 +109,7 @@ export default async function SupporterDetailPage({ params }: SupporterDetailPag
<p>
{supporter.municipalityName ? (
<>
Praça:{' '}
Município:{' '}
<Link
href={`/campanha/municipios/${supporter.municipalitySlug}`}
className="font-medium text-primary underline-offset-4 hover:underline"
@ -118,7 +118,7 @@ export default async function SupporterDetailPage({ params }: SupporterDetailPag
</Link>
</>
) : (
<span className="text-muted-foreground">Sem Praça vinculada</span>
<span className="text-muted-foreground">Sem município vinculado</span>
)}
</p>
</CardContent>

View file

@ -31,7 +31,7 @@ export type SupporterFormValues = {
}
const safeActionMessages = [
'Esta pessoa já está cadastrada como apoiador nesta Praça.',
'Esta pessoa já está cadastrada como apoiador neste município.',
'Existe mais de um contato com este celular. Resolva a duplicidade no admin antes de continuar.',
'Somente a coordenação e a assessoria podem gerenciar apoiadores.',
'Somente o Coordenador Geral pode cadastrar apoiadores sem município.',

View file

@ -77,7 +77,7 @@ export default async function SupportersPage({ searchParams }: SupportersPagePro
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold tracking-tight">Apoiadores</h1>
<p className="text-muted-foreground">
Base nominal de apoio com intenção de voto e vínculo opcional a Praças.
Base nominal de apoio com intenção de voto e vínculo opcional a municípios.
</p>
<CampaignScopeBadge>
{isCampaignUnrestricted(user)

View file

@ -49,7 +49,7 @@ export default async function NewDemandPage({ searchParams }: NewDemandPageProps
</Button>
<h1 className="text-2xl font-semibold tracking-tight">Nova demanda</h1>
<p className="text-muted-foreground">
Descreva a necessidade (material, transporte, espaço, apoio…) e a Praça. A assessoria
Descreva a necessidade (material, transporte, espaço, apoio…) e o município. A assessoria
revisa e responde por aqui.
</p>
</header>

View file

@ -73,7 +73,7 @@ const demandColumns: Array<CampaignTableColumn<DemandRowViewModel>> = [
},
{
id: 'municipality',
head: <CampaignTableHead>Praça</CampaignTableHead>,
head: <CampaignTableHead>Município</CampaignTableHead>,
cellClassName: 'text-muted-foreground',
cell: (row) => row.municipalityName,
},

View file

@ -54,7 +54,7 @@ const leadershipColumns: Array<CampaignTableColumn<LeadershipRowViewModel>> = [
},
{
id: 'municipalities',
head: <CampaignTableHead>Praças</CampaignTableHead>,
head: <CampaignTableHead>Municípios</CampaignTableHead>,
cellClassName: 'max-w-64 whitespace-normal text-muted-foreground',
cell: (row) => row.municipalityNames.join(', ') || '—',
},
@ -90,7 +90,7 @@ export default async function LeadershipsPage({ searchParams }: LeadershipsPageP
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold tracking-tight">Lideranças</h1>
<p className="text-muted-foreground">
Uma ficha por pessoa — cada liderança pode atuar em várias Praças e organizações.
Uma ficha por pessoa — cada liderança pode atuar em vários municípios e organizações.
</p>
</div>
<Button asChild className="min-h-11">
@ -126,7 +126,7 @@ export default async function LeadershipsPage({ searchParams }: LeadershipsPageP
<CampaignListEmptyState
icon={SearchXIcon}
title="Nenhuma liderança encontrada"
description="Cadastre a primeira liderança ou ajuste a busca. Você só vê lideranças das suas Praças."
description="Cadastre a primeira liderança ou ajuste a busca. Você só vê lideranças dos seus municípios."
>
<Button asChild className="min-h-11">
<Link href="/campanha/liderancas/nova">

View file

@ -60,7 +60,7 @@ export default async function MunicipalityEditPage({ params }: MunicipalityEditP
</Button>
<h1 className="text-2xl font-semibold tracking-tight">Editar {view.name}</h1>
<p className="text-muted-foreground">
A geografia da Praça é pré-definida e não pode ser alterada. Aqui você edita metas,
A geografia do município é pré-definida e não pode ser alterada. Aqui você edita metas,
votos estimados, inteligência e tendência política.
</p>
</header>

View file

@ -158,7 +158,7 @@ export default async function MunicipalityDetailPage({
{activeTab === 'demands' ? (
<section className="rounded-xl border px-4 py-6">
<p className="text-sm text-muted-foreground">
As demandas desta Praça aparecem em{' '}
As demandas deste município aparecem em{' '}
<Link
href="/campanha/demandas"
className="text-primary underline-offset-4 hover:underline"
@ -314,7 +314,7 @@ const ElectionsTab = async ({
Comparativo entre candidatos
</h2>
<p className="text-sm text-muted-foreground">
Compare a votação de deputados federais nesta Praça através dos anos (fonte TSE, votos
Compare a votação de deputados federais neste município através dos anos (fonte TSE, votos
nominais do 1º turno).
</p>
</div>

View file

@ -54,7 +54,7 @@ const organizationColumns: Array<CampaignTableColumn<OrganizationRowViewModel>>
},
{
id: 'municipalities',
head: <CampaignTableHead>Praças de atuação</CampaignTableHead>,
head: <CampaignTableHead>Municípios de atuação</CampaignTableHead>,
cellClassName: 'max-w-64 whitespace-normal text-muted-foreground',
cell: (row) => row.municipalityNames.join(', ') || '—',
},

View file

@ -131,7 +131,7 @@ export default async function ActionPlanDetailPage({
)}
<dl className="grid gap-2 sm:grid-cols-2">
<div>
<dt className="text-muted-foreground">Praça</dt>
<dt className="text-muted-foreground">Município</dt>
<dd>
{view.municipality ? (
<Link

View file

@ -207,7 +207,7 @@ const createValidatedSupporter = async (payload: Payload, actor: CampaignUser, i
)
} catch (error) {
if (isUniqueSupporterConflict(error)) {
throw new Error('Esta pessoa já está cadastrada como apoiador nesta Praça.')
throw new Error('Esta pessoa já está cadastrada como apoiador neste município.')
}
throw error
}

View file

@ -395,7 +395,7 @@ export const ActionPlan: CollectionConfig = {
name: 'municipality',
type: 'relationship',
relationTo: 'municipality',
label: 'Praça',
label: 'Município',
required: true,
index: true,
},
@ -574,7 +574,7 @@ export const ActionPlan: CollectionConfig = {
},
admin: {
description:
'O que aconteceu, público, aprendizados. Vira base para futuras atividades na mesma Praça.',
'O que aconteceu, público, aprendizados. Vira base para futuras atividades no mesmo município.',
},
},
{

View file

@ -130,7 +130,7 @@ const lockMunicipalitiesBeforeChange: CollectionBeforeChangeHook = async ({
relationshipId(originalDoc?.municipality),
relationshipId(data.municipality ?? originalDoc?.municipality),
])
if (municipalityIDs.length === 0) throw new APIError('Praça da atualização inválida.', 500)
if (municipalityIDs.length === 0) throw new APIError('Município da atualização inválido.', 500)
await acquireMunicipalityUpdateLocks(req, municipalityIDs)
return data
@ -147,7 +147,7 @@ const lockMunicipalityBeforeDelete: CollectionBeforeDeleteHook = async ({ contex
req,
})
const municipalityIDs = uniqueSortedMunicipalityIDs([relationshipId(doc.municipality)])
if (municipalityIDs.length === 0) throw new APIError('Praça da atualização inválida.', 500)
if (municipalityIDs.length === 0) throw new APIError('Município da atualização inválido.', 500)
await acquireMunicipalityUpdateLocks(req, municipalityIDs)
}
@ -164,7 +164,7 @@ const recomputeChangedMunicipalities: CollectionAfterChangeHook = async ({
relationshipId(previousDoc?.municipality),
relationshipId(doc.municipality),
])
if (municipalityIDs.length === 0) throw new APIError('Praça da atualização inválida.', 500)
if (municipalityIDs.length === 0) throw new APIError('Município da atualização inválido.', 500)
await acquireMunicipalityUpdateLocks(req, municipalityIDs)
await recomputeMunicipalityLastUpdateAt(req, municipalityIDs)
@ -175,7 +175,7 @@ const recomputeDeletedMunicipality: CollectionAfterDeleteHook = async ({ context
if (context[DERIVED_MUNICIPALITY_UPDATE_CONTEXT]) return doc
const municipalityIDs = uniqueSortedMunicipalityIDs([relationshipId(doc.municipality)])
if (municipalityIDs.length === 0) throw new APIError('Praça da atualização inválida.', 500)
if (municipalityIDs.length === 0) throw new APIError('Município da atualização inválido.', 500)
await acquireMunicipalityUpdateLocks(req, municipalityIDs)
await recomputeMunicipalityLastUpdateAt(req, municipalityIDs)
@ -185,8 +185,8 @@ const recomputeDeletedMunicipality: CollectionAfterDeleteHook = async ({ context
export const MunicipalityUpdate: CollectionConfig = {
slug: 'municipalityUpdate',
labels: {
singular: 'Atualização da Praça',
plural: 'Atualizações das Praças',
singular: 'Atualização do Município',
plural: 'Atualizações dos Municípios',
},
admin: {
group: 'Campanha',
@ -211,7 +211,7 @@ export const MunicipalityUpdate: CollectionConfig = {
name: 'municipality',
type: 'relationship',
relationTo: 'municipality',
label: 'Praça',
label: 'Município',
required: true,
index: true,
},

View file

@ -79,7 +79,7 @@ export const Supporter: CollectionConfig = {
name: 'municipality',
type: 'relationship',
relationTo: 'municipality',
label: 'Praça',
label: 'Município',
index: true,
access: {
update: canManageCampaignStaffField,

View file

@ -64,7 +64,7 @@ const validatePledgeIntegrity: CollectionBeforeChangeHook = async ({
const leadershipID = relationshipId(data.leadership ?? originalDoc?.leadership)
const municipalityID = relationshipId(data.municipality ?? originalDoc?.municipality)
if (!leadershipID || !municipalityID) {
throw new APIError('Informe a liderança e a Praça do compromisso de votos.', 400)
throw new APIError('Informe a liderança e o município do compromisso de votos.', 400)
}
if (operation === 'update') {
@ -74,7 +74,7 @@ const validatePledgeIntegrity: CollectionBeforeChangeHook = async ({
(data.leadership !== undefined && leadershipID !== previousLeadership) ||
(data.municipality !== undefined && municipalityID !== previousMunicipality)
) {
throw new APIError('A liderança e a Praça do compromisso não podem ser alteradas.', 409)
throw new APIError('A liderança e o município do compromisso não podem ser alterados.', 409)
}
}
@ -90,7 +90,7 @@ const validatePledgeIntegrity: CollectionBeforeChangeHook = async ({
.filter((id): id is number => id !== null)
if (!linkedMunicipalityIDs.includes(municipalityID)) {
throw new APIError('A liderança precisa estar vinculada à Praça para declarar votos nela.', 409)
throw new APIError('A liderança precisa estar vinculada ao município para declarar votos nele.', 409)
}
return data
@ -172,7 +172,7 @@ export const VotePledge: CollectionConfig = {
name: 'municipality',
type: 'relationship',
relationTo: 'municipality',
label: 'Praça',
label: 'Município',
required: true,
index: true,
},

View file

@ -186,14 +186,14 @@ export const ActionPlanFilters = ({
</Field>
) : null}
<Field>
<FieldLabel htmlFor="action-plan-municipality">Praça</FieldLabel>
<FieldLabel htmlFor="action-plan-municipality">Município</FieldLabel>
<NativeSelect
id="action-plan-municipality"
value={values.municipality}
onChange={(event) => updateMunicipality(event.target.value)}
className="w-full **:data-[slot=native-select]:min-h-11 **:data-[slot=native-select]:rounded-[6px]"
>
<NativeSelectOption value="">Todas as Praças</NativeSelectOption>
<NativeSelectOption value="">Todos os municípios</NativeSelectOption>
{municipalityOptions.map((option) => (
<NativeSelectOption key={option.id} value={String(option.id)}>
{option.name}

View file

@ -283,7 +283,7 @@ export const ActionPlanFormFields = ({
<CardContent>
<FieldGroup>
<Field data-invalid={Boolean(errorFor('municipality'))}>
<FieldLabel htmlFor="municipality">Praça *</FieldLabel>
<FieldLabel htmlFor="municipality">Município *</FieldLabel>
<NativeSelect
id="municipality"
name="municipality"
@ -293,7 +293,7 @@ export const ActionPlanFormFields = ({
aria-describedby={errorFor('municipality') ? 'municipality-error' : undefined}
className="w-full **:data-[slot=native-select]:min-h-11"
>
<NativeSelectOption value="">Selecione a Praça</NativeSelectOption>
<NativeSelectOption value="">Selecione o município</NativeSelectOption>
{municipalityOptions.map((option) => (
<NativeSelectOption key={option.id} value={String(option.id)}>
{option.name}

View file

@ -46,28 +46,28 @@ export const CampaignDashboard = ({
<h1 className="text-2xl font-semibold tracking-tight">Olá, {userName}</h1>
<p className="text-muted-foreground">
{view.role === 'advisor'
? 'Quadro das Praças sob sua assessoria.'
: 'Quadro geral da campanha por Praça.'}
? 'Quadro dos municípios sob sua assessoria.'
: 'Quadro geral da campanha por município.'}
</p>
</header>
{mapSlot}
<section
aria-label="Indicadores das Praças"
aria-label="Indicadores dos municípios"
className={`rounded-xl ${campaignPrioritySurfaceClassName}`}
>
<CampaignMetricStrip
metrics={[
{
label: `${voteEstimateScenarioLabels.central} nas Praças`,
label: `${voteEstimateScenarioLabels.central} nos municípios`,
value: formatElectionNumber(view.staffVoteTotalByScenario.central),
detail: formatVoteEstimateEndpointsLabel(view.staffVoteTotalByScenario) ?? undefined,
emphasize: true,
},
{
label: 'Cobertura de assessoria',
value: `${view.withAdvisorCount} de ${view.municipalityCount} Praças`,
value: `${view.withAdvisorCount} de ${view.municipalityCount} municípios`,
progress:
view.municipalityCount > 0
? Math.round((view.withAdvisorCount / view.municipalityCount) * 100)
@ -158,7 +158,7 @@ export const CampaignDashboard = ({
{view.priorityMunicipalities.length ? (
<section aria-labelledby="dashboard-priority-title" className="flex flex-col gap-3">
<h2 id="dashboard-priority-title" className="text-base font-medium">
Praças prioritárias
Municípios prioritários
</h2>
<ul className="flex flex-wrap gap-2">
{view.priorityMunicipalities.map((municipality) => (
@ -177,7 +177,7 @@ export const CampaignDashboard = ({
<div>
<Button asChild className="min-h-11">
<Link href="/campanha/municipios">
Ver todas as Praças
Ver todos os municípios
<ArrowRightIcon data-icon="inline-end" aria-hidden="true" />
</Link>
</Button>

View file

@ -157,7 +157,7 @@ export const LeaderContactForm = ({
defaultValue={values?.municipality ?? ''}
className="w-full **:data-[slot=native-select]:min-h-11 **:data-[slot=native-select]:rounded-[6px]"
>
<NativeSelectOption value="">Selecione a Praça</NativeSelectOption>
<NativeSelectOption value="">Selecione o município</NativeSelectOption>
{municipalityOptions.map((municipality) => (
<NativeSelectOption key={municipality.id} value={String(municipality.id)}>
{municipality.name}

View file

@ -109,7 +109,7 @@ export const LeaderContactsPanel = ({
{municipalityOptions.length === 0 ? (
<p className="rounded-xl border px-4 py-6 text-sm text-muted-foreground">
Você ainda não está vinculada a nenhuma Praça. Fale com a assessoria da campanha.
Você ainda não está vinculada a nenhum município. Fale com a assessoria da campanha.
</p>
) : (
<LeaderContactForm

View file

@ -96,11 +96,11 @@ export const LeadershipForm = ({
<RelationMultiSelect
name="municipalities"
label="Praças em que atua"
label="Municípios em que atua"
options={municipalityOptions}
initialSelectedIDs={initialMunicipalityIDs}
error={fieldError(state.fieldErrors, 'municipalities')}
placeholder="Adicionar Praça…"
placeholder="Adicionar município…"
/>
<RelationMultiSelect

View file

@ -61,11 +61,11 @@ export const LeadershipInternalForm = ({
<RelationMultiSelect
name="municipalities"
label="Praças em que atua"
label="Municípios em que atua"
options={municipalityOptions}
initialSelectedIDs={leadership.municipalityIDs}
error={fieldError(state.fieldErrors, 'municipalities')}
placeholder="Adicionar Praça…"
placeholder="Adicionar município…"
/>
<RelationMultiSelect

View file

@ -7,7 +7,7 @@ const statusPresentation = {
engajado: {
label: 'Engajado',
variant: 'support-engaged',
summary: 'Participa ativamente da campanha nas suas Praças e pode ter acesso ao app.',
summary: 'Participa ativamente da campanha nos seus municípios e pode ter acesso ao app.',
},
a_abordar: {
label: 'A abordar',

View file

@ -87,14 +87,14 @@ export const MapFeatureReadout = ({
) : null}
{navigation?.kind === 'zones' ? (
<p className="text-xs text-muted-foreground">
Toque de novo no mapa ou role até as Praças por zona abaixo.
Toque de novo no mapa ou role até os Municípios por zona abaixo.
</p>
) : null}
</div>
{navigation?.kind === 'navigate' ? (
<Button asChild className="min-h-11 shrink-0">
<Link href={`/campanha/municipios/${navigation.slug}`}>Abrir Praça</Link>
<Link href={`/campanha/municipios/${navigation.slug}`}>Abrir município</Link>
</Button>
) : null}
</div>

View file

@ -138,7 +138,7 @@ export const MunicipalityMapPanel = ({
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
<div className="flex flex-col gap-1">
<h2 id="municipality-map-title" className="text-base font-medium">
Mapa das Praças
Mapa dos Municípios
</h2>
<p className="text-sm text-muted-foreground">
{comparisonActive && comparison
@ -277,7 +277,7 @@ export const MunicipalityMapPanel = ({
{/* zone breakdown below */}
{bundle.zoneBreakdown.length > 0 && !comparisonActive ? (
<div id="municipality-zone-breakdown" className="flex flex-col gap-2">
<h3 className="text-sm font-medium">Praças por zona eleitoral</h3>
<h3 className="text-sm font-medium">Municípios por zona eleitoral</h3>
<p className="text-sm text-muted-foreground">
Zonas não têm polígono oficial — Salvador e Camaçari aparecem agregadas no mapa e
detalhadas aqui.

View file

@ -35,9 +35,9 @@ export const MunicipalityAdvisorsForm = ({
return (
<form action={submitAction} className="flex flex-col gap-4 rounded-xl border p-4">
<div className="flex flex-col gap-1">
<h2 className="text-base font-medium">Assessores da Praça</h2>
<h2 className="text-base font-medium">Assessores do município</h2>
<p className="text-sm text-muted-foreground">
O assessor vê e gerencia somente as Praças que administra.
O assessor vê e gerencia somente os municípios que administra.
</p>
</div>
<input type="hidden" name="municipalityId" value={municipalityID} />

View file

@ -276,7 +276,7 @@ export const MunicipalityGoalAccountCard = ({
</dl>
{!usesMesaEstimate && suggestedGoal <= 0 ? (
<p className="text-xs text-muted-foreground">
Sem votação própria em 2022 aqui, então não há meta sugerida — é praça de expansão:
Sem votação própria em 2022 aqui, então não há meta sugerida — é município de expansão:
cadastre a estimativa da mesa manualmente se decidir investir.
</p>
) : null}

View file

@ -33,10 +33,10 @@ export const MunicipalityLeadershipsPanel = ({
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-col gap-1">
<h2 id="municipality-leaderships-title" className="text-base font-medium">
Lideranças desta Praça
Lideranças deste município
</h2>
<p className="text-sm text-muted-foreground">
Uma liderança pode atuar em várias Praças e organizações.
Uma liderança pode atuar em vários municípios e organizações.
</p>
</div>
<Button asChild className="min-h-11">
@ -74,7 +74,7 @@ export const MunicipalityLeadershipsPanel = ({
</div>
{leadership.municipalityNames.length > 1 ? (
<span className="text-sm text-muted-foreground">
Atua em {leadership.municipalityNames.length} Praças
Atua em {leadership.municipalityNames.length} municípios
</span>
) : null}
</div>
@ -90,7 +90,7 @@ export const MunicipalityLeadershipsPanel = ({
</ul>
) : (
<p className="rounded-xl border px-4 py-6 text-sm text-muted-foreground">
Nenhuma liderança vinculada a esta Praça ainda.
Nenhuma liderança vinculada a este município ainda.
</p>
)}
</section>

View file

@ -104,7 +104,7 @@ export const MunicipalityPledgesPanel = ({
</>
) : (
<p className="text-sm text-muted-foreground">
Nenhuma liderança declarou votos nesta Praça ainda.
Nenhuma liderança declarou votos neste município ainda.
</p>
)}
</section>

View file

@ -27,7 +27,7 @@ export const MunicipalityTabNav = ({
searchParams: MunicipalityDetailSearchParams
}) => (
<nav
aria-label="Seções da Praça"
aria-label="Seções do município"
className="h-14 min-w-0 overflow-x-auto overflow-y-hidden border-b print:hidden"
>
<ul

View file

@ -16,7 +16,7 @@ export const MunicipalityZoneNeighborhoodsCard = ({ municipalitySlug }: { munici
>
<div className="flex flex-col gap-1">
<h2 id="municipality-zone-neighborhoods-title" className="text-base font-medium">
Bairros desta Praça
Bairros deste município
</h2>
<p className="text-sm text-muted-foreground">
{neighborhoods.length}{' '}

View file

@ -80,11 +80,11 @@ export const OrganizationForm = ({
<RelationMultiSelect
name="municipalities"
label="Praças de atuação"
label="Municípios de atuação"
options={municipalityOptions}
initialSelectedIDs={initial?.municipalityIDs ?? []}
error={fieldError(state.fieldErrors, 'municipalities')}
placeholder="Adicionar Praça…"
placeholder="Adicionar município…"
/>
<Field>

View file

@ -71,7 +71,7 @@ const FilterFields = ({
</NativeSelect>
</Field>
<Field>
<FieldLabel htmlFor="supporter-city">Município</FieldLabel>
<FieldLabel htmlFor="supporter-city">Cidade</FieldLabel>
<StrictCombobox
id="supporter-city"
options={municipalityComboboxOptions()}
@ -80,7 +80,7 @@ const FilterFields = ({
/>
</Field>
<Field>
<FieldLabel htmlFor="supporter-municipality">Praça</FieldLabel>
<FieldLabel htmlFor="supporter-municipality">Município</FieldLabel>
<NativeSelect
id="supporter-municipality"
name="municipality"

View file

@ -213,7 +213,7 @@ export const SupporterForm = ({
</Field>
<Field data-invalid={municipalityField.invalid}>
<FieldLabel htmlFor="supporter-municipality">Praça{requireMunicipality ? ' *' : ''}</FieldLabel>
<FieldLabel htmlFor="supporter-municipality">Município{requireMunicipality ? ' *' : ''}</FieldLabel>
<NativeSelect
id="supporter-municipality"
name="municipality"
@ -221,7 +221,7 @@ export const SupporterForm = ({
defaultValue={values?.municipality ?? ''}
className="w-full **:data-[slot=native-select]:min-h-11 **:data-[slot=native-select]:rounded-[6px]"
>
{!requireMunicipality ? <NativeSelectOption value="">Sem Praça</NativeSelectOption> : null}
{!requireMunicipality ? <NativeSelectOption value="">Sem município</NativeSelectOption> : null}
{municipalityOptions.map((municipality) => (
<NativeSelectOption key={municipality.id} value={String(municipality.id)}>
{municipality.name}

View file

@ -52,7 +52,7 @@ const SupporterCard = ({ supporter }: { supporter: SupporterListItemViewModel })
)}
</div>
<p className="text-sm text-muted-foreground">
{supporter.city ?? 'Município não informado'}
{supporter.city ?? 'Cidade não informada'}
</p>
</CardHeader>
<CardContent className="flex flex-col gap-3">
@ -65,7 +65,7 @@ const SupporterCard = ({ supporter }: { supporter: SupporterListItemViewModel })
{supporter.municipalityName}
</Link>
) : (
<span className="text-muted-foreground">Sem Praça vinculada</span>
<span className="text-muted-foreground">Sem município vinculado</span>
)}
</p>
{supporter.phone ? (
@ -99,12 +99,12 @@ const supporterColumns: Array<CampaignTableColumn<SupporterListItemViewModel>> =
},
{
id: 'city',
head: <CampaignTableHead>Município</CampaignTableHead>,
head: <CampaignTableHead>Cidade</CampaignTableHead>,
cell: (supporter) => supporter.city ?? '—',
},
{
id: 'municipality',
head: <CampaignTableHead>Praça</CampaignTableHead>,
head: <CampaignTableHead>Município</CampaignTableHead>,
cellClassName: 'max-w-48 whitespace-normal',
cell: (supporter) =>
supporter.municipalityName ? (
@ -115,7 +115,7 @@ const supporterColumns: Array<CampaignTableColumn<SupporterListItemViewModel>> =
{supporter.municipalityName}
</Link>
) : (
<span className="text-muted-foreground">Sem Praça vinculada</span>
<span className="text-muted-foreground">Sem município vinculado</span>
),
},
{

View file

@ -7,7 +7,7 @@ export const CAMPAIGN_LEADERSHIP_LOGIN_RECOVERY_HINT =
'Conta só com celular? Peça um novo convite ao coordenador — a recuperação por e-mail não funciona sem e-mail cadastrado.'
export const CAMPAIGN_FIRST_ACCESS_HINT =
'Peça um convite no WhatsApp ao assessor da sua Praça.'
'Peça um convite no WhatsApp ao assessor do seu município.'
export const CAMPAIGN_LOGIN_SUBTITLE = 'Use o e-mail ou o celular cadastrado na sua conta.'

View file

@ -33,7 +33,7 @@ export interface MunicipalityCatalogEntry {
/** Display name, e.g. "Feira de Santana" or "Salvador — ZE 3". */
readonly name: string
readonly kind: MunicipalityKind
/** Canonical municipality name (every Praça is single-municipality). */
/** Canonical municipality name (every catalog unit is single-municipality). */
readonly city: string
/** Official Bahia identity territory (TI) of the municipality. */
readonly region: BahiaIdentityTerritory

View file

@ -41,7 +41,7 @@ export const MAX_LEADERSHIP_STATE_DEPUTIES = 20
const municipalitiesArraySchema = z
.array(positiveRelationshipId)
.min(1, 'Vincule a liderança a pelo menos uma Praça.')
.min(1, 'Vincule a liderança a pelo menos um município.')
.max(MAX_LEADERSHIP_MUNICIPALITIES)
.transform((ids) => [...new Set(ids)])

View file

@ -683,7 +683,7 @@ export interface ActionPlan {
}[]
| null;
/**
* O que aconteceu, público, aprendizados. Vira base para futuras atividades na mesma Praça.
* O que aconteceu, público, aprendizados. Vira base para futuras atividades no mesmo município.
*/
resultSummary?: string | null;
resultMedia?: (number | Media)[] | null;

View file

@ -1,5 +1,5 @@
// ---------------------------------------------------------------------------
// Municipalities (Praças)
// Municipalities (Municípios)
// ---------------------------------------------------------------------------
import type { Access, FieldAccess, Payload, PayloadRequest } from 'payload'

View file

@ -16,7 +16,7 @@ export const actionPlanMunicipalitySummary = (
? { id: municipality.id, name: municipality.name, slug: municipality.slug }
: null
/** Short "where" line, e.g. "Praça de Itabuna · Feira do Malhado". */
/** Short "where" line, e.g. "Itabuna · Feira do Malhado". */
export const formatActionPlanLocationLabel = ({
municipalityName,
locality,

View file

@ -165,7 +165,7 @@ export const getCampaignDashboardData = async (
: 'Contato'
return {
pledgeID: pledge.id,
municipalityName: municipality?.name ?? 'Praça',
municipalityName: municipality?.name ?? 'Município',
municipalitySlug: municipality?.slug ?? '',
contactName,
declaredVotes: pledge.declaredVotes,
@ -180,7 +180,7 @@ export const getCampaignDashboardData = async (
const author = authorsById.get(relationshipId(update.author) ?? -1)
return {
id: update.id,
municipalityName: municipality?.name ?? 'Praça',
municipalityName: municipality?.name ?? 'Município',
municipalitySlug: municipality?.slug ?? '',
authorName: String(author?.name ?? 'Usuário'),
createdAt: update.createdAt,

View file

@ -141,7 +141,7 @@ const toDemandRow = (
slug: demand.slug,
kind: demand.kind as CampaignDemandKind,
status: demand.status as CampaignDemandStatus,
municipalityName: municipality?.name ?? 'Praça',
municipalityName: municipality?.name ?? 'Município',
municipalitySlug: municipality?.slug ?? '',
requesterName: leadershipID ? (requesterBy.get(leadershipID) ?? null) : null,
createdAt: demand.createdAt,

View file

@ -106,7 +106,7 @@ const toLeadershipRows = async (
municipalityNames: (doc.municipalities ?? [])
.map(relationshipId)
.filter((id): id is number => id !== null)
.map((id) => municipalityNames.get(id) ?? 'Praça'),
.map((id) => municipalityNames.get(id) ?? 'Município'),
organizationNames: (doc.organizations ?? [])
.map(relationshipId)
.filter((id): id is number => id !== null)

View file

@ -31,7 +31,7 @@ export type CandidateComparisonRow = {
/**
* Vote comparison between Jorge Solla and selected federal-deputy candidates
* inside one municipality geography, across the TSE series years. This is the
* coordination's core analysis tool ("com quem comparar nesta Praça?").
* coordination's core analysis tool ("com quem comparar neste município?").
*/
/** Cross-request cached core — immutable TSE series, busted with `election-tse`. */
const loadMunicipalityCandidateComparisonCached = unstable_cache(

View file

@ -3,9 +3,9 @@ import type { Where } from 'payload'
import { getMunicipalityCatalogEntry, type MunicipalityCatalogEntry } from '@/lib/municipalityCatalog'
/**
* Election-data geography of a Praça: exactly one municipality (TSE cityCode)
* and either all of its official zones (municipality Praça) or the single
* zone (zone Praça). Election collections are keyed by cityCode × zoneNumber,
* Election-data geography of a Município: exactly one municipality (TSE cityCode)
* and either all of its official zones (whole-municipality unit) or the single
* zone (zona unit). Election collections are keyed by cityCode × zoneNumber,
* so the cut at the municipal boundary is exact.
*/
export type MunicipalityElectionGeography = {

View file

@ -190,7 +190,7 @@ export type CandidateVotesByCity = Map<string, number>
/**
* Statewide nominal votes of one federal-deputy candidate in one year, grouped
* by TSE cityCode. Used by the Praças map (choropleth per municipality).
* by TSE cityCode. Used by the municipalities map (choropleth per municipality).
*/
export const loadCandidateVotesByCity = async (
payload: Payload,

View file

@ -222,7 +222,7 @@ export const buildMunicipalityMapBundleFromMunicipalities = async (
}
/**
* Map data for the Praças overview: municipalities matching the list URL filters (and
* Map data for the municipalities overview: municipalities matching the list URL filters (and
* role access) contribute their geography's votes (or 2026 pledge estimates)
* to municipality polygons. When `state.compare` is set, TSE years also carry
* a red↔white↔blue diff (Solla − other candidate).

View file

@ -136,7 +136,7 @@ export const loadOrganizationListPageData = async (
municipalityNames: (doc.municipalities ?? [])
.map(relationshipId)
.filter((id): id is number => id !== null)
.map((id) => municipalityNames.get(id) ?? 'Praça'),
.map((id) => municipalityNames.get(id) ?? 'Município'),
leadershipCount: leadershipCounts.get(doc.id) ?? 0,
})),
totalDocs: result.totalDocs,
@ -221,7 +221,7 @@ export const loadOrganizationDetail = async (
kind: organization.kind as OrganizationKind,
notes: organization.notes ?? null,
municipalityIDs,
municipalityNames: municipalityIDs.map((id) => municipalityNames.get(id) ?? 'Praça'),
municipalityNames: municipalityIDs.map((id) => municipalityNames.get(id) ?? 'Município'),
leaderships: leaderships.docs.map((leadership) => {
const contact = leadership.contact
return {

View file

@ -119,7 +119,7 @@ export const resolveSupporterListUrl = (
})
export const getSupporterScopeLabel = (total: number): string =>
`${total} ${total === 1 ? 'apoiador nas suas Praças' : 'apoiadores nas suas Praças'}`
`${total} ${total === 1 ? 'apoiador nos seus municípios' : 'apoiadores nos seus municípios'}`
/** Staff roles with supporter-area access — matches collection-level supporter access. */
export const canAccessSupporterArea = (role: CampaignUser['role']): boolean =>

View file

@ -30,7 +30,7 @@ test.describe('Planos de ação — registro-fundação', () => {
await page.getByLabel('Título *').fill(planTitle)
await page.getByLabel('Tipo de ação *').selectOption('caminhada')
await page.getByLabel('Origem da ação').selectOption('obrigacao_politica')
await page.getByLabel('Praça *').selectOption(String(municipality.id))
await page.getByLabel('Município *').selectOption(String(municipality.id))
await page.getByRole('button', { name: 'Adicionar demanda' }).click()
await page.getByLabel('Título da demanda 1').fill(transportDemand)