mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 08:53:11 +00:00
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:
parent
11d3fcd24c
commit
e357c3cc19
51 changed files with 93 additions and 93 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(', ') || '—',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}{' '}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)])
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// Municipalities (Praças)
|
||||
// Municipalities (Municípios)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import type { Access, FieldAccess, Payload, PayloadRequest } from 'payload'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue