Deliver C8 F4 shared plaza staff form actions and close simplify debts.
Unify list and /editar server actions in plazaStaffFormActions.ts so B9 inline edits and the edit page share one implementation without behavior changes.
This commit is contained in:
parent
e497fc3982
commit
c0bf440c76
11 changed files with 61 additions and 118 deletions
|
|
@ -9,11 +9,12 @@ alwaysApply: false
|
|||
|
||||
- **B6 entregue em código (2026-07-21; deploy pendente com remodelagem):** `BahiaMap` setStyle incremental — layer GeoJSON estável entre troca de ano/métrica/escala; `pathByKeyRef` + restyle O(2) no hover/select; `fitBounds` só em `mode`/`highlightKeys`; helpers puros em `bahiaMapStyle.ts` + unit `bahiaMapStyle.unit.spec.ts`. Plano: [`escala-dry-pos-b3.md`](../../../docs/plans/escala-dry-pos-b3.md). Fecha débitos de perf de **B10** e **B11**.
|
||||
- **B8 F1 entregue em código (2026-07-21; deploy pendente com remodelagem):** catálogo estático `plazaZoneNeighborhoods` (21 Praças-zona SSA/CMS; Salvador = TRE-BA RA 02/2017; Camaçari = locais de votação + curadoria) + fixture `plaza-zone-neighborhoods.official.json` + int `plazaZoneNeighborhoods.int.spec.ts`; card **Bairros desta Praça** na aba Visão geral de `/campanha/pracas/[slug]` quando `kind === 'zona'` (staff e líder). Plano: [`poligonos-pracas-zona.md`](../../../docs/plans/poligonos-pracas-zona.md). **F2 pendente** (dissolve IBGE → TopoJSON + mapa).
|
||||
- **A9+ entregue em código (2026-07-21; deploy pendente com remodelagem):** loader compartilhado da lista — `loadPlazaListPageBundle` (1× `aggregatePledgesByPlaza`), `buildPlazaMapBundleFromPlazas`, `plazaRevalidation.ts` + hidden `plazaSlug`; int `plazaPageData.int.spec.ts`. Plano: [`escala-dry-pos-a9.md`](../../../docs/plans/escala-dry-pos-a9.md). Débito restante: twin `listFormActions` ↔ `/editar` → **C8** F4.
|
||||
- **C8 F4 entregue em código (2026-07-21; deploy pendente com remodelagem):** DRY plaza staff form actions — `plazaStaffFormActions.ts` (votos estimados, tendência, assessores) compartilhado entre lista e `/editar`; `editar/formActions.ts` mantém só `updatePlazaStrategy`. Plano: [`escala-dry-pos-c6.md`](../../../docs/plans/escala-dry-pos-c6.md).
|
||||
- **A9+ entregue em código (2026-07-21; deploy pendente com remodelagem):** loader compartilhado da lista — `loadPlazaListPageBundle` (1× `aggregatePledgesByPlaza`), `buildPlazaMapBundleFromPlazas`, `plazaRevalidation.ts` + hidden `plazaSlug`; int `plazaPageData.int.spec.ts`. Plano: [`escala-dry-pos-a9.md`](../../../docs/plans/escala-dry-pos-a9.md).
|
||||
- **B11 entregue em código (2026-07-21; deploy pendente com remodelagem):** escala **% dos válidos** no Mapa das Praças — seletor `Total (votos)` / `% dos válidos` (default %); domínio 0–100% fixo (`scaleMax` em `BahiaMap`); `validVotesByYear` no bundle; 2026 usa válidos 2022; compare desliga %; readout em % + votos absolutos; `Promise.all` nos loads TSE. Plano: [`escala-percentual-mapa-pracas.md`](../../../docs/plans/escala-percentual-mapa-pracas.md).
|
||||
- **Fill-in filtros-auto entregue em código (2026-07-21; deploy pendente com remodelagem):** debounce 1s + Enter imediato na busca de `/campanha/pracas`; remove Buscar; pending UI alinhado a `ActionPlanFilters`; `shouldUpdatePlazaSearchUrl` + no-op `buildPlazaFiltersKey`. Plano: [`filtros-auto-pracas.md`](../../../docs/plans/filtros-auto-pracas.md). Débitos pós-`/simplify` (sync back/forward, shell pending) → gatilhos no plano.
|
||||
- **B10 entregue em código (2026-07-21; deploy pendente com remodelagem):** hover/tap no Mapa das Praças — destaque + `MapFeatureReadout`; desktop click navega; mobile 2º tap; SSA/CMS N>1 foca `zoneBreakdown`; `plazasByIbgeCode` / `resolvePlazaMapNavigation`. Plano: [`hover-mapa-pracas.md`](../../../docs/plans/hover-mapa-pracas.md).
|
||||
- **B9 entregue em código (2026-07-21; deploy pendente com remodelagem):** edição rápida na lista — Assessores / Tendência / `expectedVotes` via `PlazaList*Control` + `listFormActions`; sem migration. Plano: [`edicao-rapida-lista-pracas.md`](../../../docs/plans/edicao-rapida-lista-pracas.md). Débitos `/simplify` → **C8** F4 (prep revalidate/parsing no A9+).
|
||||
- **B9 entregue em código (2026-07-21; deploy pendente com remodelagem):** edição rápida na lista — Assessores / Tendência / `expectedVotes` via `PlazaList*Control` + `plazaStaffFormActions`; sem migration. Plano: [`edicao-rapida-lista-pracas.md`](../../../docs/plans/edicao-rapida-lista-pracas.md).
|
||||
- **A9 entregue em código (2026-07-21; deploy pendente com remodelagem):** `plaza.expectedVotes` staff-only, fallback `expectedVotes ?? effectiveTotal` em mapa 2026/overview/dashboard, UI `/editar` + leitura lista/detalhe; migration `20260721_133444_add_plaza_expected_votes`. Plano: [`estimativa-votos-praca.md`](../../../docs/plans/estimativa-votos-praca.md).
|
||||
- **REMODELAGEM ENTREGUE EM CÓDIGO (2026-07-21; deploy pendente — migração `20260721_020109_remodel_plazas` é destrutiva para a vertical campanha, revisar SQL antes do build de produção). Suítes verdes: unit 187, int 306 (incl. assimetria declarado×estimado, access por papel, workflow de demandas), e2e 9. Critique/polish visual fino registrado como R6 no roadmap.** Feedback da coordenação invalidou o modelo de núcleo abstrato. Decisões travadas: 436 Praças pré-definidas (415 municípios + 19 zonas de Salvador + 2 de Camaçari; zonas compartilhadas cortadas na divisa municipal); código `plaza`; roles renomeados para `coordinator`/`advisor`/`leader` (labels "Coordenador Geral"/"Assessor"/"Liderança"); assessor vê só as Praças que administra; liderança = 1 registro por pessoa com N Praças e N organizações; votos por liderança×Praça (`declaredVotes` visível à liderança, `estimatedVotes` staff-only); demandas com workflow assessor→coordenador geral + comprovantes staff-only; tendência política manual (staff); comparativo multi-candidato/multi-ano por Praça; mapa divergente vermelho↔branco↔azul. Reset dos dados de campanha em produção (site público intocado). Plano-mestre: [`docs/plans/remodelagem-pracas.md`](../../../docs/plans/remodelagem-pracas.md). As entradas abaixo descrevem a era Núcleos — padrões de infra (locks, transações, consent por chave, shells, mapa, dados eleitorais) continuam válidos; superfícies e modelo de Núcleo foram substituídos.
|
||||
- **MVP Ciclo 1 entregue e enviado em `main` (2026-07-18):** ondas 1–8 (auth/RBAC, domínio+UI de Núcleo/Liderança/Inteligência/Atualizações/Convites, dashboard, hardening) + refactors de composição Next.js, primitivos compartilhados, locks PostgreSQL, fixtures E2E e migration consolidada `20260718_010733_consolidate_campaign_schema`.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Impeccable: B — encaixe em `PlazaList` / cards mobile em `/campanha/pracas` (s
|
|||
Appetite: ~1–1,5 dia eng; 3 affordances na linha + reuso de actions (A9 + plaza); sem migration própria
|
||||
Responsável: —
|
||||
|
||||
_Revisão 2026-07-21 (pós-implementação + `/simplify`): `PlazaList*Control` + `listFormActions`; coluna Tendência; `PlazaAdvisorAvatarStack`. Débitos → **A9+** F2 / **C8** F4 (ver seção Simplify abaixo)._
|
||||
_Revisão 2026-07-21 (pós-implementação + `/simplify`): `PlazaList*Control` + `plazaStaffFormActions`; coluna Tendência; `PlazaAdvisorAvatarStack`. Débitos pós-B9 absorvidos em **A9+** F2 / **C8** F4 (entregues)._
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ Componentes:
|
|||
|
||||
Limpezas aplicadas no cleanup da sessão (não reabrir): `politicalTrend.note` via hidden field (sem `findByID` extra); `politicalTrendBadgeVariant` em `plazaUi.ts`; `PlazaAdvisorAvatarStack`; tipos `PoliticalTrendStatus`; `max={1_000_000}` no input; higiene de testes.
|
||||
|
||||
Débitos absorvidos noutros planos: twin `listFormActions` ↔ `/editar` → **C8 F4** ([escala-dry-pos-c6.md](escala-dry-pos-c6.md)). ~~`revalidatePath` full-page pós-save~~ → **A9+ F2 entregue** ([escala-dry-pos-a9.md](escala-dry-pos-a9.md): `plazaRevalidation.ts` + `plazaSlug`).
|
||||
Débitos absorvidos noutros planos: ~~twin `listFormActions` ↔ `/editar`~~ → **C8 F4 entregue** ([escala-dry-pos-c6.md](escala-dry-pos-c6.md): `plazaStaffFormActions.ts`). ~~`revalidatePath` full-page pós-save~~ → **A9+ F2 entregue** ([escala-dry-pos-a9.md](escala-dry-pos-a9.md): `plazaRevalidation.ts` + `plazaSlug`).
|
||||
|
||||
**Explicitamente fora (triage):** lazy-load `getEligibleAdvisorOptions`; `PopoverAnchor` não usado; helper `parsePoliticalTrendStatus`; unit tests dos `PlazaList*Control`.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ O revisor de **performance** marcou como **maior que simplify** os três problem
|
|||
|
||||
**F1 — Loader:** `loadPlazaListPageBundle` em `plazaPageData.ts`; `buildPlazaMapBundleFromPlazas` + `scopePlazasFromDocs` em `plazaMapData.ts`; `page.tsx` com uma única chamada; removidos `loadPlazaListPageData` / `loadPlazaListOverviewData` do export público.
|
||||
|
||||
**F2 — Revalidate:** `plazaRevalidation.ts` (`revalidatePlazaListPaths` com validação de slug); hidden `plazaSlug` em `PlazaList*Control`, `PlazaStrategyForm`, `PlazaAdvisorsForm`; `listFormActions` + `editar/formActions` atualizados.
|
||||
**F2 — Revalidate:** `plazaRevalidation.ts` (`revalidatePlazaListPaths` com validação de slug); hidden `plazaSlug` em `PlazaList*Control`, `PlazaStrategyForm`, `PlazaAdvisorsForm`; `plazaStaffFormActions` + `editar/formActions` (via **C8 F4**).
|
||||
|
||||
**Helpers compartilhados (prep C8 F4):** `optionalPlazaSlugFromForm`, `parsePoliticalTrendStatusFormValue`.
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ O revisor de **performance** marcou como **maior que simplify** os três problem
|
|||
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-B7):** testes int extras em `plazaMapData` (`region`/`leader`/`compare`) — cobertura opcional no próximo toque no loader; split do teste advisor por `kind` (legibilidade).
|
||||
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-B9):** ~75 hooks `useActionState` por página (3 controles × 25 linhas × 2 views) — gatilho: reclamação de perf ou **R6**; merge `listFormActions` ↔ `/editar` → **C8 F4**; hook popover compartilhado / layout responsivo único / lazy advisor options → gatilhos no plano B9.
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-B9):** ~75 hooks `useActionState` por página (3 controles × 25 linhas × 2 views) — gatilho: reclamação de perf ou **R6**; ~~merge `listFormActions` ↔ `/editar` → **C8 F4**~~ (entregue 2026-07-21); hook popover compartilhado / layout responsivo único / lazy advisor options → gatilhos no plano B9.
|
||||
|
||||
## Simplify pós-implementação (2026-07-21)
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ Passagem `/simplify` sobre o diff A9+ (F1+F2) após rebase em `main` com B11.
|
|||
|
||||
**F2 como entregue:** `plazaRevalidation.ts` + hidden `plazaSlug` nos forms; lista e `/editar` chamam `revalidatePlazaListPaths({ slug })` — revalida o detalhe de forma estreita quando o slug está presente; a lista continua com `revalidatePath` full-page (paridade com filtros `?trend=` e KPIs). **Rejeitado neste fill-in:** cache por tag / matriz `scope` por tipo de save.
|
||||
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-implementação A9+):** `scope: 'detail'` só em save de tendência (a lista precisa refletir filtro `?trend=`); cache/revalidate por tag (rabbit hole); mover `plazaListFilteredSelect` → `plazaViewModels` (gatilho: 3º consumidor); paralelizar pledge aggregate + loads TSE no mesmo tick do bundle (gatilho: profiling TTFB); unificar os dois `find` de Praças (decisão travada em “Questões em aberto”); twin completo `listFormActions` ↔ `/editar` → **C8 F4** (prep parcial — ver [escala-dry-pos-c6.md](escala-dry-pos-c6.md) Fase 4).
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-implementação A9+):** `scope: 'detail'` só em save de tendência (a lista precisa refletir filtro `?trend=`); cache/revalidate por tag (rabbit hole); mover `plazaListFilteredSelect` → `plazaViewModels` (gatilho: 3º consumidor); paralelizar pledge aggregate + loads TSE no mesmo tick do bundle (gatilho: profiling TTFB); unificar os dois `find` de Praças (decisão travada em “Questões em aberto”); ~~twin completo `listFormActions` ↔ `/editar` → **C8 F4**~~ (entregue 2026-07-21 — ver [escala-dry-pos-c6.md](escala-dry-pos-c6.md) Fase 4).
|
||||
|
||||
## Objetivos
|
||||
|
||||
|
|
@ -97,9 +97,9 @@ flowchart TD
|
|||
|
||||
### Fase 2 — Revalidação escopada pós-B9
|
||||
|
||||
- Avaliar `revalidatePath` estreito (só segmento necessário) ou tag de cache dedicada à lista vs full-page `revalidatePath('/campanha/pracas', 'page')` em `listFormActions.ts`.
|
||||
- Avaliar `revalidatePath` estreito (só segmento necessário) ou tag de cache dedicada à lista vs full-page `revalidatePath('/campanha/pracas', 'page')` em `plazaStaffFormActions.ts` — gatilho: N saves inline + profiling de rerender inaceitável (ver Adiado com gatilho).
|
||||
- Critério de aceite: após N saves inline seguidos, overview/mapa/lista permanecem corretos sem rerodar loaders caros quando o dado alterado não afeta KPI agregado (ex.: tendência não muda métrica 2026 — validar caso a caso).
|
||||
- Coordenar com **C8 F4** se extrair `revalidatePlazaListPaths` compartilhado entre lista e `/editar`.
|
||||
- ~~Coordenar com **C8 F4** se extrair `revalidatePlazaListPaths` compartilhado entre lista e `/editar`.~~ Entregue 2026-07-21.
|
||||
- Testes: int leve ou e2e smoke de save inline + paridade visual de KPIs (manual ok se appetite apertar).
|
||||
|
||||
**Migration:** nenhuma.
|
||||
|
|
@ -137,10 +137,10 @@ flowchart TD
|
|||
- `docs/plans/estimativa-votos-praca.md` — plano pai A9
|
||||
- `docs/plans/escala-dry-pos-e1.md` — precedente E6 F1 (lista+overview núcleos)
|
||||
- `src/app/(campaign)/campanha/(app)/pracas/page.tsx` — consome `loadPlazaListPageBundle`
|
||||
- `src/app/(campaign)/campanha/(app)/pracas/listFormActions.ts` — `revalidatePlazaListPaths` com `plazaSlug`
|
||||
- `src/app/(campaign)/campanha/(app)/pracas/plazaStaffFormActions.ts` — `revalidatePlazaListPaths` com `plazaSlug`
|
||||
- `src/utilities/plazaPageData.ts` — `loadPlazaListPageBundle`
|
||||
- `src/utilities/plazaMapData.ts` — `buildPlazaMapBundleFromPlazas`, `scopePlazasFromDocs`
|
||||
- `src/utilities/plazaRevalidation.ts` — helper compartilhado (prep C8 F4)
|
||||
- `src/utilities/plazaRevalidation.ts` — helper compartilhado (C8 F4)
|
||||
- `tests/int/plazaPageData.int.spec.ts` — paridade overview/mapa/escopo
|
||||
- `src/utilities/plazaMapData.ts` — `loadPlazaMapBundle`, métrica 2026
|
||||
- `src/utilities/votePledgeData.ts` — `aggregatePledgesByPlaza`, `rollupPlazaStaffVotes`
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ flowchart TD
|
|||
### Fase 4 — DRY de forms
|
||||
|
||||
- Migrar os `formActions.ts` restantes para `mapCampaignFormActionError` + `campaignFormFields` (hoje o ladder `FormDataBoundaryError` → `ZodError` → `validationFieldErrors` → safe-message está inlined em vários arquivos):
|
||||
- `src/app/(campaign)/campanha/(app)/pracas/listFormActions.ts` + `pracas/[slug]/editar/formActions.ts` — **twin paths pós-B9** (mesmo parsing `formData`, mesmas actions de domínio). **Prep A9+ (2026-07-21):** `revalidatePlazaListPaths` (`plazaRevalidation.ts`), `optionalPlazaSlugFromForm`, `parsePoliticalTrendStatusFormValue` — feitos; **falta** merge dos wrappers de server action (4 actions × 2 superfícies).
|
||||
- ~~`src/app/(campaign)/campanha/(app)/pracas/listFormActions.ts` + `pracas/[slug]/editar/formActions.ts`~~ — **entregue 2026-07-21 (C8 F4):** `plazaStaffFormActions.ts` (3 actions compartilhadas); `editar/formActions.ts` mantém só `updatePlazaStrategyFormAction`; lista e `/editar` importam o módulo compartilhado diretamente (Next.js não permite re-export em `'use server'`).
|
||||
- `src/app/(campaign)/campanha/(app)/pracas/[slug]/updateFormActions.ts`, `pledgeFormActions.ts` (se ainda não no mapper)
|
||||
- `src/app/(campaign)/campanha/convite/[token]/formActions.ts`
|
||||
- `src/app/(campaign)/campanha/(app)/apoiadores/[id]/formActions.ts`
|
||||
|
|
@ -134,10 +134,24 @@ Os débitos que os revisores marcaram como importantes e maiores que cleanup for
|
|||
|
||||
## Simplify (2026-07-21 pós-B9)
|
||||
|
||||
`/simplify` da entrega **B9** ([edicao-rapida-lista-pracas.md](edicao-rapida-lista-pracas.md)) marcou como **maior que cleanup** o twin `listFormActions.ts` ↔ `editar/formActions.ts` — absorvido na **Fase 4** acima. B9 já adota `mapCampaignFormActionError`; o débito é DRY de parsing/revalidate entre lista e `/editar`, não introdução do mapper.
|
||||
~~`/simplify` da entrega **B9** marcou como **maior que cleanup** o twin `listFormActions.ts` ↔ `editar/formActions.ts`~~ — **entregue 2026-07-21 (C8 F4):** `plazaStaffFormActions.ts` unifica os 3 wrappers compartilhados; `editar/formActions.ts` mantém só `updatePlazaStrategyFormAction`.
|
||||
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-B9):** hook `usePlazaListPopoverForm` (gatilho: 4º inline editor); `PlazaAdvisorCheckboxList` (gatilho: 3º uso ou refactor do form `/editar`); layout responsivo único mobile+desktop na lista (gatilho: page size >25 ou profiling de hydration); lazy-load de `getEligibleAdvisorOptions` (staff set pequeno); `PopoverAnchor` export não usado (scaffold shadcn); ~~helper `parsePoliticalTrendStatus`~~ → entregue como `parsePoliticalTrendStatusFormValue` no prep A9+; unit tests dos `PlazaList*Control` (int access cobre domínio); E2E save inline (gatilho: smoke pós-merge B9).
|
||||
|
||||
## Simplify (2026-07-21 pós-A9+)
|
||||
|
||||
Triage `capture-review-debts` após `/simplify` do fill-in A9+: prep da **Fase 4** nos twins de Praças — `plazaRevalidation.ts`, `optionalPlazaSlugFromForm`, `parsePoliticalTrendStatusFormValue` (lista + `/editar`). Débito restante na Fase 4: unificar os wrappers de server action duplicados; não reabrir revalidate/parsing.
|
||||
~~Débito restante na Fase 4: unificar os wrappers de server action duplicados~~ — **entregue 2026-07-21 (C8 F4):** ver seção Simplify pós-B9.
|
||||
|
||||
## Simplify (2026-07-21 pós-C8 F4)
|
||||
|
||||
Passagem `/simplify` + `capture-review-debts` após fill-in **C8 F4** (plaza twin DRY).
|
||||
|
||||
**Já resolvido (não reabrir):** `plazaStaffFormActions.ts`; `plazaStaffEditSafeMessages`; tipo `PlazaStaffFormAction` em `PlazaList.tsx`; import direto em `editar/page.tsx` (Next.js não permite re-export em `'use server'`); hygiene de refs em planos-pai A9/B9/estimativa.
|
||||
|
||||
**Explicitamente fora (triage `capture-review-debts` 2026-07-21 pós-C8 F4):**
|
||||
|
||||
- **Runner genérico `runCampaignFormAction`** — gatilho: 5+ wrappers idênticos ou 4º cluster de forms na mesma superfície (hoje ~18 arquivos com try/catch inline).
|
||||
- **`revalidatePlazaListPaths` com `scope` por tipo de save** — gatilho: N saves inline + profiling de rerender inaceitável (ver [escala-dry-pos-a9.md](escala-dry-pos-a9.md) Adiado com gatilho).
|
||||
- **`pledgeFormActions` / `updateFormActions` → `revalidatePlazaListPaths`** — gatilho: forms ganham `plazaSlug` ou unificação de revalidate no detalhe da Praça (permanecem em Fase 4 remanescente abaixo).
|
||||
- **`reloadCampaignActor` por submit em `actions/plaza.ts`** — gatilho: profiling mostra latência dominante em saves staff inline.
|
||||
- **Fundir `updatePlazaStrategyFormAction` em `plazaStaffFormActions.ts`** — parse/`safeMessages` distintos; superfície só `/editar`.
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ Componentes:
|
|||
|
||||
## Pós-entrega (`/simplify`)
|
||||
|
||||
Fill-in **A9+** entregue 2026-07-21 — [escala-dry-pos-a9.md](escala-dry-pos-a9.md) (`loadPlazaListPageBundle`, 1× `aggregatePledgesByPlaza`, `plazaRevalidation.ts`). Débito restante: twin `listFormActions` ↔ `/editar` → **C8** F4.
|
||||
Fill-in **A9+** entregue 2026-07-21 — [escala-dry-pos-a9.md](escala-dry-pos-a9.md) (`loadPlazaListPageBundle`, 1× `aggregatePledgesByPlaza`, `plazaRevalidation.ts`). Fill-in **C8 F4** entregue 2026-07-21 — [escala-dry-pos-c6.md](escala-dry-pos-c6.md) (`plazaStaffFormActions.ts`).
|
||||
|
||||
**Já resolvido no simplify (não reabrir):** form `expectedVotes`; `rollupPlazaStaffVotes` / `sumStaffPledgeEffectiveTotal`; `StaffPlazaVotesDisplay`; remoção de `hasStaffVoteData`; testes unit do rollup.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Roadmap — Teqo
|
||||
|
||||
Atualizado em: 2026-07-21 (B6 + B8 F1 entregues em código; débitos B8 F1 pós-/simplify absorvidos no plano F2 prep; B12 registrado — aproximar mapa ao filtro; A9+/B7/B9–B11/filtros-auto entregues; débitos B9 → C8 F4)
|
||||
Atualizado em: 2026-07-21 (B6 + B8 F1 + fill-in **C8** F4 entregues; B12 registrado; A9+/B7/B9–B11/filtros-auto entregues)
|
||||
|
||||
Registro canônico dos **próximos** planos e débitos. Histórico de entregas: resumo abaixo + planos em [`docs/plans/`](plans/) + notebook [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc).
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ flowchart TD
|
|||
C2prod --> C5["C5 GOTV (validar)"]
|
||||
```
|
||||
|
||||
Paralelizáveis agora: **B12** (viewport do mapa; B7 já entregue), fill-ins da lista (**C8** F4, …). ~~**A9** / **A9+** / **B9** / **B7** / **B10** / **B11** / **B6** / **filtros-auto**~~ entregues 2026-07-21.
|
||||
Paralelizáveis agora: **B12** (viewport do mapa; B7 já entregue), fill-ins (O0+, RS+, …). ~~**A9** / **A9+** / **B9** / **B7** / **B10** / **B11** / **B6** / **filtros-auto** / **C8** F4~~ entregues 2026-07-21.
|
||||
|
||||
### Sequência por janela
|
||||
|
||||
|
|
@ -108,14 +108,15 @@ Paralelizáveis agora: **B12** (viewport do mapa; B7 já entregue), fill-ins da
|
|||
- **Plataforma** — local Postgres + guards, migrations baselined, posts/tags do site público com cache `posts`, Onda 0 textos provisórios + `/privacidade`.
|
||||
- **Site público (2026-07-21)** — **Pixel do Meta nos abaixo-assinados** (`tracking.facebookPixelId` no admin `petition`, `PageView`/`Lead` na página pública via `MetaPixel` + `trackMetaLead`; migration `20260721_133531_add_petition_facebook_pixel_id`) — [plano](plans/pixel-meta-abaixo-assinado.md).
|
||||
- **A9 (2026-07-21)** — **Estimativa de votos da Praça** (`plaza.expectedVotes` staff-only; fallback `expectedVotes ?? effectiveTotal` em mapa 2026/overview/dashboard; UI `/editar` + leitura lista/detalhe; migration `20260721_133444_add_plaza_expected_votes`) — [plano](plans/estimativa-votos-praca.md).
|
||||
- **A9+ (2026-07-21)** — **Loader compartilhado da lista de Praças** (`loadPlazaListPageBundle`: 1× `aggregatePledgesByPlaza` + `buildPlazaMapBundleFromPlazas`; `plazaRevalidation.ts` + `plazaSlug` para revalidate estreita no detalhe; int `plazaPageData.int.spec.ts`; sem migration) — [plano](plans/escala-dry-pos-a9.md). Débito restante pós-`/simplify`: twin completo `listFormActions` ↔ `/editar` → **C8** F4.
|
||||
- **A9+ (2026-07-21)** — **Loader compartilhado da lista de Praças** (`loadPlazaListPageBundle`: 1× `aggregatePledgesByPlaza` + `buildPlazaMapBundleFromPlazas`; `plazaRevalidation.ts` + `plazaSlug` para revalidate estreita no detalhe; int `plazaPageData.int.spec.ts`; sem migration) — [plano](plans/escala-dry-pos-a9.md).
|
||||
- **B7 (2026-07-21)** — **Mapa das Praças filtrado pela lista** (`buildPlazaListWhere` em `loadPlazaMapBundle`; `rawSearchParams` na página; empty → omitir painel; int `plazaMapData.int.spec.ts`) — [plano](plans/mapa-pracas-filtrado.md).
|
||||
- **B9 (2026-07-21)** — **Edição rápida na lista de Praças** (Assessores / Tendência / `expectedVotes` via Popovers em `PlazaList*Control`; `listFormActions`; sem migration) — [plano](plans/edicao-rapida-lista-pracas.md). Débitos `/simplify`: **C8** F4 (DRY formActions; prep revalidate/parsing feito no A9+).
|
||||
- **B9 (2026-07-21)** — **Edição rápida na lista de Praças** (Assessores / Tendência / `expectedVotes` via Popovers em `PlazaList*Control`; `plazaStaffFormActions`; sem migration) — [plano](plans/edicao-rapida-lista-pracas.md).
|
||||
- **B10 (2026-07-21)** — **Hover/tap no Mapa das Praças** (destaque + `MapFeatureReadout`; desktop click navega; mobile 2º tap; SSA/CMS N>1 → `zoneBreakdown`; `plazasByIbgeCode` / `resolvePlazaMapNavigation`) — [plano](plans/hover-mapa-pracas.md).
|
||||
- **B11 (2026-07-21)** — **Escala % dos válidos no Mapa das Praças** (`validVotesByYear` no bundle; seletor `Total (votos)` / `% dos válidos`; domínio fixo 0–100%; 2026 usa válidos 2022; compare desliga %; readout em %) — [plano](plans/escala-percentual-mapa-pracas.md).
|
||||
- **B6 (2026-07-21)** — **`BahiaMap` setStyle incremental** (layer GeoJSON estável entre troca de ano/métrica/escala; `pathByKeyRef` + restyle O(2) no hover/select; `fitBounds` só em `mode`/`highlightKeys`; helpers em `bahiaMapStyle.ts`) — [plano](plans/escala-dry-pos-b3.md).
|
||||
- **B8 F1 (2026-07-21)** — **Catálogo bairros das Praças-zona** (`plazaZoneNeighborhoods`: Salvador TRE-BA RA 02/2017 + Camaçari curado; fixture+int; card **Bairros desta Praça** no overview de Praças `kind=zona`; sem migration) — [plano](plans/poligonos-pracas-zona.md). **F2 pendente** (F2 prep catálogo ~½ dia + TopoJSON + mapa). Débitos pós-`/simplify`: hidratação via `plazaCatalog`, canônico JSON→TS, teste ordem slugs → F2 prep no plano (S6 CSS compartilhado adiado até F2/R6).
|
||||
- **Fill-in filtros-auto (2026-07-21)** — **Filtros auto-aplicados na lista de Praças** (`PlazaFilters`: debounce 1s no `q`, Enter imediato, selects imediatos, remove Buscar; `useTransition` + pending a11y; `shouldUpdatePlazaSearchUrl` + no-op via `buildPlazaFiltersKey`; sem migration) — [plano](plans/filtros-auto-pracas.md). Débitos pós-`/simplify`: sync back/forward `search`↔`state.q` e shell pending compartilhado — gatilhos no plano.
|
||||
- **Fill-in C8 F4 (2026-07-21)** — **DRY plaza staff form actions** (`plazaStaffFormActions.ts`: votos estimados, tendência e assessores compartilhados entre lista e `/editar`; `editar/formActions.ts` mantém só `updatePlazaStrategy`; sem migration) — [plano](plans/escala-dry-pos-c6.md).
|
||||
|
||||
## Supersedidos pela remodelagem (2026-07-20)
|
||||
|
||||
|
|
@ -142,7 +143,7 @@ Paralelizáveis agora: **B12** (viewport do mapa; B7 já entregue), fill-ins da
|
|||
- **C5** operação dia D / GOTV _(validar com produto)_ · design [`Dia-D-GOTV`](design-refs/latest/Dia-D-GOTV.png) · depende de C2 dados reais
|
||||
- **D2** push + sino in-app · soft: chave `campanha-notificacoes-push` (Onda 0) · [plano](plans/notifications.md)
|
||||
- **R6** critique/polish visual da vertical remodelada (ciclo /impeccable completo por superfície; smoke visual coordenador feito em 2026-07-21) · gatilho: antes de 16/08 · (opcional: mover filtros acima do mapa)
|
||||
- **Fill-ins:** **C8** F4 DRY `listFormActions`↔`/editar` ([plano](plans/escala-dry-pos-c6.md)) · O0+ ([plano](plans/escala-dry-pos-onda0.md)) · RS+ auth read leve + shells de senha ([plano](plans/escala-dry-pos-reset-senha-perfil.md)) · higiene PascalCase
|
||||
- **Fill-ins:** O0+ ([plano](plans/escala-dry-pos-onda0.md)) · RS+ auth read leve + shells de senha ([plano](plans/escala-dry-pos-reset-senha-perfil.md)) · higiene PascalCase
|
||||
|
||||
## Bloqueadores atuais
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,20 @@
|
|||
'use server'
|
||||
|
||||
import {
|
||||
assignPlazaAdvisors,
|
||||
setPlazaExpectedVotes,
|
||||
setPlazaPoliticalTrend,
|
||||
updatePlazaStrategy,
|
||||
} from '@/app/(campaign)/campanha/actions/plaza'
|
||||
import { updatePlazaStrategy } from '@/app/(campaign)/campanha/actions/plaza'
|
||||
import {
|
||||
nullableFormText,
|
||||
optionalFormText,
|
||||
optionalIntegerFormValue,
|
||||
optionalPlazaSlugFromForm,
|
||||
repeatedFormTexts,
|
||||
repeatedRelationshipFormValues,
|
||||
requiredRelationshipFormValue,
|
||||
} from '@/lib/formData'
|
||||
import { parsePoliticalTrendStatusFormValue } from '@/lib/schemas/plaza'
|
||||
import {
|
||||
mapCampaignFormActionError,
|
||||
type CampaignFormActionState,
|
||||
} from '@/utilities/campaignFormActionError'
|
||||
import { revalidatePlazaListPaths } from '@/utilities/plazaRevalidation'
|
||||
import { plazaStaffEditSafeMessages } from '../../plazaStaffFormActions'
|
||||
|
||||
export const updatePlazaStrategyFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
|
|
@ -50,81 +44,10 @@ export const updatePlazaStrategyFormAction = async (
|
|||
error,
|
||||
safeMessages: [
|
||||
'As metas devem seguir a ordem Bom ≥ Regular ≥ Mínimo quando informadas.',
|
||||
'Somente a coordenação e a assessoria podem editar a Praça.',
|
||||
...plazaStaffEditSafeMessages,
|
||||
],
|
||||
genericMessage:
|
||||
'Não foi possível salvar a estratégia. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setPlazaExpectedVotesFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
try {
|
||||
const plaza = requiredRelationshipFormValue(formData, 'plazaId')
|
||||
const expectedVotes =
|
||||
optionalIntegerFormValue(formData, 'expectedVotes', {
|
||||
minimum: 0,
|
||||
maximum: 1_000_000,
|
||||
}) ?? null
|
||||
|
||||
await setPlazaExpectedVotes({ plaza, expectedVotes })
|
||||
revalidatePlazaListPaths({ slug: optionalPlazaSlugFromForm(formData) })
|
||||
return { status: 'success', message: 'Votos estimados atualizados.' }
|
||||
} catch (error) {
|
||||
return mapCampaignFormActionError({
|
||||
error,
|
||||
safeMessages: ['Somente a coordenação e a assessoria podem editar a Praça.'],
|
||||
genericMessage:
|
||||
'Não foi possível salvar os votos estimados. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setPlazaPoliticalTrendFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
try {
|
||||
const plaza = requiredRelationshipFormValue(formData, 'plazaId')
|
||||
const status = parsePoliticalTrendStatusFormValue(optionalFormText(formData, 'trendStatus'))
|
||||
|
||||
await setPlazaPoliticalTrend({
|
||||
plaza,
|
||||
status,
|
||||
note: nullableFormText(formData, 'trendNote'),
|
||||
})
|
||||
revalidatePlazaListPaths({ slug: optionalPlazaSlugFromForm(formData) })
|
||||
return { status: 'success', message: 'Tendência política registrada.' }
|
||||
} catch (error) {
|
||||
return mapCampaignFormActionError({
|
||||
error,
|
||||
safeMessages: ['Somente a coordenação e a assessoria podem editar a Praça.'],
|
||||
genericMessage:
|
||||
'Não foi possível registrar a tendência. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const assignPlazaAdvisorsFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
try {
|
||||
const plaza = requiredRelationshipFormValue(formData, 'plazaId')
|
||||
const advisors = repeatedRelationshipFormValues(formData, 'advisors')
|
||||
|
||||
await assignPlazaAdvisors({ plaza, advisors })
|
||||
revalidatePlazaListPaths({ slug: optionalPlazaSlugFromForm(formData) })
|
||||
return { status: 'success', message: 'Assessores atualizados.' }
|
||||
} catch (error) {
|
||||
return mapCampaignFormActionError({
|
||||
error,
|
||||
safeMessages: ['Somente o Coordenador Geral designa assessores.'],
|
||||
genericMessage:
|
||||
'Não foi possível atualizar os assessores. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import {
|
|||
assignPlazaAdvisorsFormAction,
|
||||
setPlazaExpectedVotesFormAction,
|
||||
setPlazaPoliticalTrendFormAction,
|
||||
updatePlazaStrategyFormAction,
|
||||
} from './formActions'
|
||||
} from '../../plazaStaffFormActions'
|
||||
import { updatePlazaStrategyFormAction } from './formActions'
|
||||
|
||||
type PlazaEditPageProps = {
|
||||
params: Promise<{ slug: string }>
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ import {
|
|||
} from '@/utilities/plazaUi'
|
||||
import { loadAdvisorSummaries, getEligibleAdvisorOptions } from '@/utilities/plazaViewModels'
|
||||
import {
|
||||
assignPlazaAdvisorsListFormAction,
|
||||
setPlazaExpectedVotesListFormAction,
|
||||
setPlazaPoliticalTrendListFormAction,
|
||||
} from './listFormActions'
|
||||
assignPlazaAdvisorsFormAction,
|
||||
setPlazaExpectedVotesFormAction,
|
||||
setPlazaPoliticalTrendFormAction,
|
||||
} from './plazaStaffFormActions'
|
||||
|
||||
type PlazasPageProps = {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>
|
||||
|
|
@ -104,9 +104,9 @@ export default async function PlazasPage({ searchParams }: PlazasPageProps) {
|
|||
isStaffView={isStaffView}
|
||||
isCoordinator={isCoordinator}
|
||||
advisorOptions={advisorOptions}
|
||||
expectedVotesFormAction={setPlazaExpectedVotesListFormAction}
|
||||
trendFormAction={setPlazaPoliticalTrendListFormAction}
|
||||
advisorsFormAction={assignPlazaAdvisorsListFormAction}
|
||||
expectedVotesFormAction={setPlazaExpectedVotesFormAction}
|
||||
trendFormAction={setPlazaPoliticalTrendFormAction}
|
||||
advisorsFormAction={assignPlazaAdvisorsFormAction}
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ import {
|
|||
} from '@/utilities/campaignFormActionError'
|
||||
import { revalidatePlazaListPaths } from '@/utilities/plazaRevalidation'
|
||||
|
||||
export const setPlazaExpectedVotesListFormAction = async (
|
||||
export const plazaStaffEditSafeMessages = [
|
||||
'Somente a coordenação e a assessoria podem editar a Praça.',
|
||||
] as const
|
||||
|
||||
export const setPlazaExpectedVotesFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
|
|
@ -38,14 +42,14 @@ export const setPlazaExpectedVotesListFormAction = async (
|
|||
} catch (error) {
|
||||
return mapCampaignFormActionError({
|
||||
error,
|
||||
safeMessages: ['Somente a coordenação e a assessoria podem editar a Praça.'],
|
||||
safeMessages: plazaStaffEditSafeMessages,
|
||||
genericMessage:
|
||||
'Não foi possível salvar os votos estimados. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setPlazaPoliticalTrendListFormAction = async (
|
||||
export const setPlazaPoliticalTrendFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
|
|
@ -63,14 +67,14 @@ export const setPlazaPoliticalTrendListFormAction = async (
|
|||
} catch (error) {
|
||||
return mapCampaignFormActionError({
|
||||
error,
|
||||
safeMessages: ['Somente a coordenação e a assessoria podem editar a Praça.'],
|
||||
safeMessages: plazaStaffEditSafeMessages,
|
||||
genericMessage:
|
||||
'Não foi possível registrar a tendência. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const assignPlazaAdvisorsListFormAction = async (
|
||||
export const assignPlazaAdvisorsFormAction = async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
|
|
@ -29,7 +29,7 @@ import type {
|
|||
|
||||
const dateFormatter = new Intl.DateTimeFormat('pt-BR')
|
||||
|
||||
type ListFormAction = (
|
||||
type PlazaStaffFormAction = (
|
||||
state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
) => Promise<CampaignFormActionState>
|
||||
|
|
@ -40,9 +40,9 @@ export type PlazaListProps = {
|
|||
isStaffView: boolean
|
||||
isCoordinator: boolean
|
||||
advisorOptions: EligibleAdvisorOption[]
|
||||
expectedVotesFormAction: ListFormAction
|
||||
trendFormAction: ListFormAction
|
||||
advisorsFormAction: ListFormAction
|
||||
expectedVotesFormAction: PlazaStaffFormAction
|
||||
trendFormAction: PlazaStaffFormAction
|
||||
advisorsFormAction: PlazaStaffFormAction
|
||||
}
|
||||
|
||||
export const PlazaList = ({
|
||||
|
|
|
|||
Loading…
Reference in a new issue