Pass 2: formatacao da revisao (prettier em tabelas md + ordenacao de imports; SQL da migration inalterado) e remocao de duplicata stray do CampaignListEmptyState no path antigo
This commit is contained in:
parent
e8a440b837
commit
51dd779ddd
18 changed files with 101 additions and 129 deletions
|
|
@ -73,7 +73,6 @@ Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.
|
|||
|
||||
**Recently resolved (2026-07-24):** **E9** Fila de alocação — the municipality list became the allocation queue, with **no new route, no new column and no migration** (the column picker is B17 and is not shipped, so every new signal folds into an existing cell). Ordering: two new sort keys in `municipalityUi.ts` — `deficit` (**the new staff default**, descending) and `frescor` — both derived in memory in `applyDerivedMunicipalitySort` over the already-loaded bundle, fixed to the `central` scenario server-side (moving the scenario into the URL is the "Cenário junto aos filtros" fill-in). Freshness: `aggregatePledgesByMunicipality` now selects `declaredAt`/`estimatedAt` and exposes `lastPledgeAt`; `resolveMunicipalityLastSignalAt` (max of `municipality.lastUpdateAt` and `lastPledgeAt`) feeds both the comparator and the view model's `lastSignalAt`, so server ordering and rendered copy cannot drift; cold threshold `MUNICIPALITY_COLD_SIGNAL_DAYS` = 21. UI: "há N dias" in the existing "Última atualização" column (amber `text-estimate-pending-foreground`, deliberately not `destructive` — the priority and "sem responsável" badges already compete for red on the same row), a "sem responsável" badge when `priority === 'alta'` with zero advisors, the "coluna da vergonha" as the advisor-coverage metric's detail linking `?priority=alta&coverage=sem_assessor`, a scenario-named coverage tooltip, and the leftover "Praças" copy fixed to "Municípios". Cut and re-homed: votes at stake → **B13**, LQ/capture → **E10**, a dedicated deficit column → unnecessary (the E8 cell already shows % and signed deficit). Plano: [`docs/plans/fila-de-alocacao.md`](docs/plans/fila-de-alocacao.md).
|
||||
|
||||
|
||||
**Recently resolved (2026-07-24):** **E18** Documentação dos conceitos de inteligência — staff-only route `/campanha/conceitos` (`isCampaignStaff`; `leader` redirected to `/campanha`) rendering `src/lib/campaignIntelligenceConcepts.ts`. The glossary is **curated content in code, not a Payload collection**: each entry (what it measures, the formula in plain text, a hand-written example, why it matters, where it appears) sits next to the formulas it documents (`municipalityPotential.ts` / `goalCoverage.ts`) and is reviewed in the same PR that changes them; a unit test guards anchor-safe unique IDs and content completeness. Seven E8 concepts in three groups — base do cálculo (válidos projetados, teto do campo), diagnóstico (captura, share intracampo, roll-off), meta e cobertura (meta/meta sugerida, cobertura da meta). Reading surface, not cards: one `article` per concept with hairline dividers, `max-w-prose` column, sticky index at `lg` only, `article:target` tint so anchor arrivals land oriented. Two discovery paths from `MunicipalityGoalAccountCard`: per-metric "Saiba mais" links inside the hover tooltips (`MetricExplanation` takes a `conceptID`) and a whole-page link in the title `CampaignInfoHint` **Popover** — the Popover is the keyboard path because Radix Tooltip content is not tabbable; for touch, `MunicipalityHoverTooltip` now treats its own content as "inside" in the `pointerdown` dismiss handler (the link used to unmount before the tap landed). Sidebar entry lives in `staffSecondaryNav` (`nav.ts`) rendered in its own `SidebarGroup` with `mt-auto` at the foot of the navigation — deliberately outside `staffNav` (the seven work destinations) and outside the mobile bottom bar, since it is reference material; `getCampaignSecondaryNav` returns nothing for `leader`. Fixed an E8 defect found while writing the docs: `formatElectionNumber` rendered a fractional suggested goal as "100,968" (read as a hundred thousand in pt-BR) — now `maximumFractionDigits: 0`. No migration, no collection, no server action; later slices of the intelligence program (E9/E10/B13/E11/E12/E13/E14) each append their section to the array. Plano: [`docs/plans/documentacao-conceitos-campanha.md`](docs/plans/documentacao-conceitos-campanha.md).
|
||||
|
||||
**Recently resolved (2026-07-24):** **B19** Gerenciar assessores — `/campanha/assessores` (lista + novo + detalhe) for coordinator/candidate only (`isCampaignUnrestricted`); create/edit `advisor` accounts, municipality portfolio (per-municipality auto-save + advisory lock), password-reset email (blocks `@planilha.invalid`); aligned `canUpdateCampaignUser` / phone field access / `canAssignMunicipalityAdvisors` / `assignMunicipalityAdvisorsRecord` to unrestricted; privileged email read in `advisorData` loaders after the route gate; `reloadUnrestrictedActor`. No migration. Plano: [`docs/plans/gerenciar-assessores.md`](docs/plans/gerenciar-assessores.md).
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ O mapa conceitual do repositório: como o sistema se divide, em que direção o
|
|||
|
||||
Um único app Next.js (App Router) + Payload CMS sobre um Postgres (Neon em produção, Docker local), hospedado na Vercel. Três produtos em três route groups, com **duas barreiras de autenticação independentes** que podem coexistir no mesmo navegador:
|
||||
|
||||
| Route group | Produto | Auth |
|
||||
| -------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| `(frontend)` | Site público (notícias, petições, WhatsApp) | anônimo; ISR + tag caching |
|
||||
| `(payload)` | Admin editorial `/admin` | collection `users`, cookie `payload-token` |
|
||||
| `(campaign)` | Ferramenta interna `/campanha` | collection `campaignUser`, cookie próprio `campaign-token` (path `/campanha`); barreira no layout de `(app)` |
|
||||
| Route group | Produto | Auth |
|
||||
| ------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `(frontend)` | Site público (notícias, petições, WhatsApp) | anônimo; ISR + tag caching |
|
||||
| `(payload)` | Admin editorial `/admin` | collection `users`, cookie `payload-token` |
|
||||
| `(campaign)` | Ferramenta interna `/campanha` | collection `campaignUser`, cookie próprio `campaign-token` (path `/campanha`); barreira no layout de `(app)` |
|
||||
|
||||
Donations ficam FORA do app (QueroApoiar). Dados eleitorais TSE são importados por seeds locais e servidos por artefato commitado + collections cacheadas — o build da Vercel **nunca** depende de conteúdo do banco além de rodar `payload migrate`.
|
||||
|
||||
|
|
@ -61,11 +61,11 @@ flowchart LR
|
|||
|
||||
Papéis em `campaignUser.role` — predicados client-safe em `lib/campaignRoles.ts`, checks de ator em `utilities/access/shared.ts`, regras por domínio em `utilities/access/*` (re-exportadas por `campaignAccess.ts`):
|
||||
|
||||
| Papel | Escopo |
|
||||
| ------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `coordinator` | tudo (staff irrestrito); único que importa CSV de apoiadores e gerencia assessores (com `candidate`) |
|
||||
| `candidate` | visibilidade irrestrita (staff); elegível como assessor responsável |
|
||||
| `advisor` | staff restrito aos municípios em `municipality.advisors` |
|
||||
| Papel | Escopo |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `coordinator` | tudo (staff irrestrito); único que importa CSV de apoiadores e gerencia assessores (com `candidate`) |
|
||||
| `candidate` | visibilidade irrestrita (staff); elegível como assessor responsável |
|
||||
| `advisor` | staff restrito aos municípios em `municipality.advisors` |
|
||||
| `leader` | **lockdown**: home = ferramenta de contatos; lê só apoiadores que criou; sem municípios/pledges/demandas/planos/eleições |
|
||||
|
||||
Assimetria estrutural de pledge: staff registra `declaredVotes` + `estimatedVotes` (3 cenários); a liderança **nunca** vê estimativas (view models por papel; agregados usam `estimated[S] ?? declared`, default `central`).
|
||||
|
|
@ -79,7 +79,7 @@ Dois contexts principais + um de suporte, espelhados nos prefixos de módulo e n
|
|||
- **Município** — a unidade operacional PREDEFINIDA (435 no catálogo: 416 municípios; Salvador em 19 zonas; Camaçari inteiro). ≠ município IBGE genérico; geografia é read-only, estratégia é editável. Sinônimos históricos mortos: Núcleo, Praça (copy varrida no Pass 2 W4f; só migrations congeladas e textos provisórios de consent do Onda 0 ainda dizem "Praça").
|
||||
- **Liderança** — ficha única por pessoa (`contact` UNIQUE), atua em N municípios/organizações; pode ter conta `leader`.
|
||||
- **Pledge (compromisso de votos)** — por liderança×município; `declaredVotes` (staff-entered, visível à liderança) vs `estimatedVotes` (staff-only, 3 cenários: `pessimistic|central|optimistic`).
|
||||
- **Conta da cadeira (E8)** — vocabulário staff-only: *válidos projetados*, *teto do campo*, *captura*, *share intracampo*, *roll-off*, *meta* (`expectedVotes[cenário] ?? meta sugerida`), *cobertura da meta* (`comprometido ÷ meta`, comprometido = SÓ pledges). Glossário do usuário em `/campanha/conceitos` (`lib/campaignIntelligenceConcepts.ts`).
|
||||
- **Conta da cadeira (E8)** — vocabulário staff-only: _válidos projetados_, _teto do campo_, _captura_, _share intracampo_, _roll-off_, _meta_ (`expectedVotes[cenário] ?? meta sugerida`), _cobertura da meta_ (`comprometido ÷ meta`, comprometido = SÓ pledges). Glossário do usuário em `/campanha/conceitos` (`lib/campaignIntelligenceConcepts.ts`).
|
||||
- **Demanda** — pedido de material/serviço, staff-only, escalada decidida por coordinator/candidate. **Plano de Ação** — evento/agenda ancorado em UM município. **Apoiador** — join `Contact`↔campanha (consent LGPD fail-closed por chave estável). **Dobradinha** — deputado estadual parceiro (`stateDeputy`). **Assessor responsável** — carteira em `municipality.advisors`.
|
||||
- **Contact** é o registro normalizado de PESSOA compartilhado com o site público — features novas criam JOINS para `contact`, nunca uma segunda tabela de pessoa.
|
||||
|
||||
|
|
@ -89,17 +89,17 @@ Dois contexts principais + um de suporte, espelhados nos prefixos de módulo e n
|
|||
|
||||
## Log de decisões estruturais
|
||||
|
||||
| Data | Decisão |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 2026-07-16 | Auth da campanha isolada do admin: collection `campaignUser` + cookie `campaign-token` com path próprio |
|
||||
| 2026-07-23 | Toda collection declara `access` explícito; default do Payload tratado como bug (JWT de campanha alcança `/api/*`) |
|
||||
| 2026-07-23 | Município como unidade operacional predefinida (remodel destrutivo aplicado em produção) |
|
||||
| 2026-07-23 | Agregados TSE imutáveis viram artefato commitado; build da Vercel não depende de conteúdo do banco |
|
||||
| Data | Decisão |
|
||||
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 2026-07-16 | Auth da campanha isolada do admin: collection `campaignUser` + cookie `campaign-token` com path próprio |
|
||||
| 2026-07-23 | Toda collection declara `access` explícito; default do Payload tratado como bug (JWT de campanha alcança `/api/*`) |
|
||||
| 2026-07-23 | Município como unidade operacional predefinida (remodel destrutivo aplicado em produção) |
|
||||
| 2026-07-23 | Agregados TSE imutáveis viram artefato commitado; build da Vercel não depende de conteúdo do banco |
|
||||
| 2026-07-25 | **Pass 2 D1:** NO-GO em `src/domains/` e ports-and-adapters; GO em convenções + correções de fronteira + subpastas por domínio DENTRO de `components/campaign/`; subpastas em `utilities/` adiadas (gatilho: 3º módulo novo de um domínio num mês) — [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) |
|
||||
| 2026-07-25 | **Pass 2 D2:** matemática de insights da era núcleos DELETADA (`electionInsights.ts`); E10 nasce em módulo novo com classificação relativa |
|
||||
| 2026-07-25 | **Pass 2 W1/D5:** listas de tabela no sistema compartilhado (colunas como dado); exceções documentadas: planos (cards), `TerritoryOverviewTable` (sort client, ≤27 linhas), comparação de candidatos, LeaderContacts, preview de import |
|
||||
| 2026-07-25 | **Pass 2 W2:** dedup por POLÍTICA continua valendo (wrappers nomeados como `runCampaignFormAction`), nunca plumbing genérico de dados; aliases de access `canUpdateX = canReadX` são declarações de política deliberadas |
|
||||
| 2026-07-25 | **Pass 2 W4b:** knip `exports`/`types`/`enumMembers` em ERROR no CI; tests fazem parte do grafo; kit `ui/` sem peças mortas (shadcn re-adiciona sob demanda) |
|
||||
| 2026-07-25 | **Pass 2 D2:** matemática de insights da era núcleos DELETADA (`electionInsights.ts`); E10 nasce em módulo novo com classificação relativa |
|
||||
| 2026-07-25 | **Pass 2 W1/D5:** listas de tabela no sistema compartilhado (colunas como dado); exceções documentadas: planos (cards), `TerritoryOverviewTable` (sort client, ≤27 linhas), comparação de candidatos, LeaderContacts, preview de import |
|
||||
| 2026-07-25 | **Pass 2 W2:** dedup por POLÍTICA continua valendo (wrappers nomeados como `runCampaignFormAction`), nunca plumbing genérico de dados; aliases de access `canUpdateX = canReadX` são declarações de política deliberadas |
|
||||
| 2026-07-25 | **Pass 2 W4b:** knip `exports`/`types`/`enumMembers` em ERROR no CI; tests fazem parte do grafo; kit `ui/` sem peças mortas (shadcn re-adiciona sob demanda) |
|
||||
|
||||
## Manutenção deste documento
|
||||
|
||||
|
|
|
|||
|
|
@ -66,33 +66,33 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<rea
|
|||
|
||||
## Workstream Status
|
||||
|
||||
| WS | Content | Plan file | Status | Date |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------- | ---- |
|
||||
| W0 | Safety net: pin list URL parsers + list loaders (unit + int smokes); TESTING/TECH-DEBT truth-up | [pinagem-superficies-lista.md](plans/pinagem-superficies-lista.md) | done | 2026-07-25 |
|
||||
| W1 | Generalized campaign list system (D1 core + municipality; D2 migrate entity triplet/demandas/apoiadores/assessores + delete superseded) | [sistema-listas-campanha.md](plans/sistema-listas-campanha.md) | done | 2026-07-25 |
|
||||
| W2 | Architecture decision (DDD/clean-arch/SDP evaluation → conventions + boundary fixes) + staged `components/campaign` subfolders | [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) | done | 2026-07-25 |
|
||||
| W3 | `docs/ARCHITECTURE.md` + `.cursor/rules/codebase-map.mdc` | [mapa-mental-arquitetura.md](plans/mapa-mental-arquitetura.md) | done | 2026-07-25 |
|
||||
| W4a | `electionInsights.ts` gut/split: extract `electionFormat`, keep voteTrend, delete dead clusters (before E10) | [split-election-insights.md](plans/split-election-insights.md) | done | 2026-07-25 |
|
||||
| W4b | knip unused-exports warn→error: delete zero-ref exports, un-export in-file symbols, config, CI flip | (tracker only) | done | 2026-07-25 |
|
||||
| W4c | Detail-page RSC extraction (`municipios/[slug]`, `planos/[slug]`) + dedupe detail-tab helper twins | (tracker only) | done | 2026-07-25 |
|
||||
| W4d | formActions finish: `runCampaignFormAction` (stay-on-page) + migrate 9 hand-rolled ladders (closes C8 F4) | (tracker only) | done | 2026-07-25 |
|
||||
| W4e | Single-source types: scenario triple, strategy VM, trend enum, duplicated constants/helpers, `DynamicFind` doc | (tracker only) | done | 2026-07-25 |
|
||||
| W4f | "Praça"→"Município" user-visible copy sweep (~55 visible hits + admin labels; consent texts stay for the legal batch) | (tracker only) | done | 2026-07-25 |
|
||||
| W5 | Documentation close-out: AGENTS.md refresh, ledger/TESTING reconcile, plan-status patches, roadmap fold-in, product-doc drift findings | (tracker only) | done | 2026-07-25 |
|
||||
| D3 | `consent: 2` hardcode → Onda 0 provisional-key pattern (data migration + fail-closed switch; **migration SQL reviewed before landing**) | (tracker only) | done | 2026-07-25 |
|
||||
| WS | Content | Plan file | Status | Date |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------ | ---------- |
|
||||
| W0 | Safety net: pin list URL parsers + list loaders (unit + int smokes); TESTING/TECH-DEBT truth-up | [pinagem-superficies-lista.md](plans/pinagem-superficies-lista.md) | done | 2026-07-25 |
|
||||
| W1 | Generalized campaign list system (D1 core + municipality; D2 migrate entity triplet/demandas/apoiadores/assessores + delete superseded) | [sistema-listas-campanha.md](plans/sistema-listas-campanha.md) | done | 2026-07-25 |
|
||||
| W2 | Architecture decision (DDD/clean-arch/SDP evaluation → conventions + boundary fixes) + staged `components/campaign` subfolders | [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) | done | 2026-07-25 |
|
||||
| W3 | `docs/ARCHITECTURE.md` + `.cursor/rules/codebase-map.mdc` | [mapa-mental-arquitetura.md](plans/mapa-mental-arquitetura.md) | done | 2026-07-25 |
|
||||
| W4a | `electionInsights.ts` gut/split: extract `electionFormat`, keep voteTrend, delete dead clusters (before E10) | [split-election-insights.md](plans/split-election-insights.md) | done | 2026-07-25 |
|
||||
| W4b | knip unused-exports warn→error: delete zero-ref exports, un-export in-file symbols, config, CI flip | (tracker only) | done | 2026-07-25 |
|
||||
| W4c | Detail-page RSC extraction (`municipios/[slug]`, `planos/[slug]`) + dedupe detail-tab helper twins | (tracker only) | done | 2026-07-25 |
|
||||
| W4d | formActions finish: `runCampaignFormAction` (stay-on-page) + migrate 9 hand-rolled ladders (closes C8 F4) | (tracker only) | done | 2026-07-25 |
|
||||
| W4e | Single-source types: scenario triple, strategy VM, trend enum, duplicated constants/helpers, `DynamicFind` doc | (tracker only) | done | 2026-07-25 |
|
||||
| W4f | "Praça"→"Município" user-visible copy sweep (~55 visible hits + admin labels; consent texts stay for the legal batch) | (tracker only) | done | 2026-07-25 |
|
||||
| W5 | Documentation close-out: AGENTS.md refresh, ledger/TESTING reconcile, plan-status patches, roadmap fold-in, product-doc drift findings | (tracker only) | done | 2026-07-25 |
|
||||
| D3 | `consent: 2` hardcode → Onda 0 provisional-key pattern (data migration + fail-closed switch; **migration SQL reviewed before landing**) | (tracker only) | done | 2026-07-25 |
|
||||
|
||||
Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<reason\> · skipped: \<reason\>
|
||||
|
||||
## Pass 2 Decisions (signed off 2026-07-25)
|
||||
|
||||
| ID | Decision | Rationale |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| D1 | **No `src/domains/` reorg, no ports-and-adapters.** Conventions + mechanical boundary fixes + staged within-layer subfolders for `components/campaign/`; `utilities/` subfoldering deferred with trigger | Payload collections ARE the data model; a big-bang move freezes the repo mid-window and risks the importmap. Full evaluation in [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) |
|
||||
| D2 | **Delete** the ~52 consumer-less `electionInsights` exports (+ their tests) rather than quarantining them | git history + plans carry the formulas; E10 replaces the 35/20/10 threshold approach with relative classification — keeping dead math invites accidental reuse |
|
||||
| D3 | `consent: 2` hardcode folded into the Onda 0 provisional-key pattern in-pass, as an isolated delivery with the migration SQL explicitly reviewed by the user before landing | Same fail-closed pattern as `lideranca-autopreenchimento`; leaving the hardcode makes the legal batch harder to verify |
|
||||
| D4 | users-roles migration stays ledgered/deferred | Schema migration; only blocks widening `/admin`, which isn't scheduled |
|
||||
| D5 | W1 scope boundary: planos stays cards (adopts toolbar/URL state only), `TerritoryOverviewTable` stays a documented client-sort exception, LeaderContacts/candidate-comparison/import-preview stay out | Anti-classitis: the system earns its migrations; small static tables don't pay for the abstraction |
|
||||
| D6 | Praça sweep = user-visible copy + admin labels; provisional Onda 0 consent texts untouched (legal batch) | Consent text versions are counsel-owned; code shouldn't edit them cosmetically |
|
||||
| ID | Decision | Rationale |
|
||||
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| D1 | **No `src/domains/` reorg, no ports-and-adapters.** Conventions + mechanical boundary fixes + staged within-layer subfolders for `components/campaign/`; `utilities/` subfoldering deferred with trigger | Payload collections ARE the data model; a big-bang move freezes the repo mid-window and risks the importmap. Full evaluation in [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) |
|
||||
| D2 | **Delete** the ~52 consumer-less `electionInsights` exports (+ their tests) rather than quarantining them | git history + plans carry the formulas; E10 replaces the 35/20/10 threshold approach with relative classification — keeping dead math invites accidental reuse |
|
||||
| D3 | `consent: 2` hardcode folded into the Onda 0 provisional-key pattern in-pass, as an isolated delivery with the migration SQL explicitly reviewed by the user before landing | Same fail-closed pattern as `lideranca-autopreenchimento`; leaving the hardcode makes the legal batch harder to verify |
|
||||
| D4 | users-roles migration stays ledgered/deferred | Schema migration; only blocks widening `/admin`, which isn't scheduled |
|
||||
| D5 | W1 scope boundary: planos stays cards (adopts toolbar/URL state only), `TerritoryOverviewTable` stays a documented client-sort exception, LeaderContacts/candidate-comparison/import-preview stay out | Anti-classitis: the system earns its migrations; small static tables don't pay for the abstraction |
|
||||
| D6 | Praça sweep = user-visible copy + admin labels; provisional Onda 0 consent texts untouched (legal batch) | Consent text versions are counsel-owned; code shouldn't edit them cosmetically |
|
||||
|
||||
## Next Actions
|
||||
|
||||
|
|
|
|||
|
|
@ -4,40 +4,40 @@ Ledger of known engineering debt. Items large enough to be scheduled work become
|
|||
|
||||
## Debt Ledger
|
||||
|
||||
| Item | Location | Type | Risk | Effort | Priority | Status | Ref |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------- | ----------------------------------------------------------------------------------------- | -------------------- |
|
||||
| Item | Location | Type | Risk | Effort | Priority | Status | Ref |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
| 164 unused exports + 60 unused types reported by knip (warn-level; 158 at Pass 1) | god modules, ui kit families | dead code | low | M | P2 | closed 2026-07-25 (Pass 2 W4b): 0 findings; `exports`/`types`/`enumMembers` at **error** in CI; tests entered the knip graph; `duplicates` off (only hits were the 6 deliberate access aliases `canUpdateX = canReadX`) | Pass 2 W4b |
|
||||
| knip loader noise: `Error loading src/payload.config.ts` (server-only guard) | knip run output | tooling noise | none (exit 0, static graph resolves) | S | P3 | open | |
|
||||
| Missing-consent window fixture releases its exclusive lease during the observation window — readers can interleave (rare flake). Full fix: session-level advisory lock spanning delete→op→restore + updated fault-path pins | `tests/helpers/testDatabaseLease.ts` (`withMissingInviteConsentFixture`) | test-infra race | low (ms-wide window; shared leases added 2026-07-23 narrow it) | M | P2 | open | |
|
||||
| `users` collection has no roles (all admins omnipotent) | `src/collections/Users.ts` | RBAC | medium (small trusted admin set) | M (schema migration) | P1 before widening `/admin` | open | AGENTS Known Gaps #1 |
|
||||
| Any campaign user can enumerate staff names/roles (`canReadCampaignUsers`) | `src/utilities/access/campaignUsers.ts` | info disclosure | low | S | P2 | open | |
|
||||
| `removeSupporterData` deletes via `overrideAccess: true` while `canDeleteSupporter` is admin-only | `src/app/(campaign)/campanha/actions/supporter.ts` | access asymmetry (likely intentional LGPD path) | low | S | P3 | open — document or align | |
|
||||
| Legacy public consent flows hardcode document IDs (`consent: 2`) | `src/app/(frontend)/actions/submitWhatsapp.ts` | correctness | medium | S | P1 | closed 2026-07-25 (Pass 2 D3): resolves `Consent.key = 'whatsapp-inscricao'` fail-closed; migration `20260725_170000` tags the live doc (id 2, key vazio) com a chave | AGENTS Known Gaps #2 |
|
||||
| E2E suite thin (7 specs; most campaign CRUD int-only) | `tests/e2e/` | coverage | medium | L | P2 | open | |
|
||||
| CI runs on GitHub mirror only (no Codeberg/Woodpecker pipeline) | `.github/workflows/ci.yml` | process | low | M | P3 | open | |
|
||||
| E2E campaign journeys latency-flake on loaded local machines (5s expect / 30s test budgets vs cold dev compiles; suite green with CI retries) | `tests/e2e/campaignMunicipalities.e2e.spec.ts` | test-infra | low | S | P3 | open — consider per-step budgets or a warmed dist dir | |
|
||||
| Post listing cache holds all published posts, filters in memory | `src/utilities/posts.ts` | scale (fine at ~tens of posts) | low | M | P3 | open — watch corpus growth | |
|
||||
| Petition `generateStaticParams` uses uncached `getDocuments` without `select`/`depth: 0` | `src/utilities/documents.ts` consumers | over-fetch (build-time) | low | S | P2 | closed 2026-07-23 (Phase 2: `getPetitionIds` with `depth: 0` + empty `select`) | |
|
||||
| Compare mode (`?compare=`) still queries per candidate uncached cross-request | `src/utilities/municipalityMapData.ts` | perf | low | M | P3 | open — React `cache()` dedups in-request; add `unstable_cache` if profiling warrants | |
|
||||
| Map bundle serializes all years × scenarios (~100KB raw) | `municipalityMapContract.ts` consumers | payload size | low — gzip crushes repeating keys to ~15-25KB on the wire; instant year/scenario switching is a core analysis flow worth the bytes | M | P3 | deferred 2026-07-23 with rationale (revisit only if RSC payload profiling says otherwise) | |
|
||||
| Props→state `useEffect` mirrors in list controls/comboboxes | `MunicipalityListExpectedVotesControl`, `AsyncSearchCombobox`, `StrictCombobox` | pattern | none — reviewed 2026-07-23: guarded-equality sync is the correct "server echo vs local draft" reconciliation (A10 pattern), not derived state | — | — | closed (accepted as correct) | |
|
||||
| User-visible "Praça" copy survived the M1 rename (map panel title "Mapa das Praças", admin field descriptions, provisional Onda 0 consent texts, `SupportStatusBadge` summary, fallback/error strings, code comments) | `rg -n 'Praça' src` (~96 hits in 53 files, ~55 user-visible — 2026-07-25 audit) | copy drift | low (cosmetic; consent texts are provisional Onda 0 copies) | S | P2 | open — scheduled Pass 2 W4f (consent texts stay for the legal batch) | Pass 2 W4f |
|
||||
| knip loader noise: `Error loading src/payload.config.ts` (server-only guard) | knip run output | tooling noise | none (exit 0, static graph resolves) | S | P3 | open | |
|
||||
| Missing-consent window fixture releases its exclusive lease during the observation window — readers can interleave (rare flake). Full fix: session-level advisory lock spanning delete→op→restore + updated fault-path pins | `tests/helpers/testDatabaseLease.ts` (`withMissingInviteConsentFixture`) | test-infra race | low (ms-wide window; shared leases added 2026-07-23 narrow it) | M | P2 | open | |
|
||||
| `users` collection has no roles (all admins omnipotent) | `src/collections/Users.ts` | RBAC | medium (small trusted admin set) | M (schema migration) | P1 before widening `/admin` | open | AGENTS Known Gaps #1 |
|
||||
| Any campaign user can enumerate staff names/roles (`canReadCampaignUsers`) | `src/utilities/access/campaignUsers.ts` | info disclosure | low | S | P2 | open | |
|
||||
| `removeSupporterData` deletes via `overrideAccess: true` while `canDeleteSupporter` is admin-only | `src/app/(campaign)/campanha/actions/supporter.ts` | access asymmetry (likely intentional LGPD path) | low | S | P3 | open — document or align | |
|
||||
| Legacy public consent flows hardcode document IDs (`consent: 2`) | `src/app/(frontend)/actions/submitWhatsapp.ts` | correctness | medium | S | P1 | closed 2026-07-25 (Pass 2 D3): resolves `Consent.key = 'whatsapp-inscricao'` fail-closed; migration `20260725_170000` tags the live doc (id 2, key vazio) com a chave | AGENTS Known Gaps #2 |
|
||||
| E2E suite thin (7 specs; most campaign CRUD int-only) | `tests/e2e/` | coverage | medium | L | P2 | open | |
|
||||
| CI runs on GitHub mirror only (no Codeberg/Woodpecker pipeline) | `.github/workflows/ci.yml` | process | low | M | P3 | open | |
|
||||
| E2E campaign journeys latency-flake on loaded local machines (5s expect / 30s test budgets vs cold dev compiles; suite green with CI retries) | `tests/e2e/campaignMunicipalities.e2e.spec.ts` | test-infra | low | S | P3 | open — consider per-step budgets or a warmed dist dir | |
|
||||
| Post listing cache holds all published posts, filters in memory | `src/utilities/posts.ts` | scale (fine at ~tens of posts) | low | M | P3 | open — watch corpus growth | |
|
||||
| Petition `generateStaticParams` uses uncached `getDocuments` without `select`/`depth: 0` | `src/utilities/documents.ts` consumers | over-fetch (build-time) | low | S | P2 | closed 2026-07-23 (Phase 2: `getPetitionIds` with `depth: 0` + empty `select`) | |
|
||||
| Compare mode (`?compare=`) still queries per candidate uncached cross-request | `src/utilities/municipalityMapData.ts` | perf | low | M | P3 | open — React `cache()` dedups in-request; add `unstable_cache` if profiling warrants | |
|
||||
| Map bundle serializes all years × scenarios (~100KB raw) | `municipalityMapContract.ts` consumers | payload size | low — gzip crushes repeating keys to ~15-25KB on the wire; instant year/scenario switching is a core analysis flow worth the bytes | M | P3 | deferred 2026-07-23 with rationale (revisit only if RSC payload profiling says otherwise) | |
|
||||
| Props→state `useEffect` mirrors in list controls/comboboxes | `MunicipalityListExpectedVotesControl`, `AsyncSearchCombobox`, `StrictCombobox` | pattern | none — reviewed 2026-07-23: guarded-equality sync is the correct "server echo vs local draft" reconciliation (A10 pattern), not derived state | — | — | closed (accepted as correct) | |
|
||||
| User-visible "Praça" copy survived the M1 rename (map panel title "Mapa das Praças", admin field descriptions, provisional Onda 0 consent texts, `SupportStatusBadge` summary, fallback/error strings, code comments) | `rg -n 'Praça' src` (~96 hits in 53 files, ~55 user-visible — 2026-07-25 audit) | copy drift | low (cosmetic; consent texts are provisional Onda 0 copies) | S | P2 | open — scheduled Pass 2 W4f (consent texts stay for the legal batch) | Pass 2 W4f |
|
||||
|
||||
## Smell Inventory
|
||||
|
||||
| Smell | Location | Refactoring | Status |
|
||||
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| God module: RBAC for every collection in one file (964 lines, ~80 exports) | `src/utilities/campaignAccess.ts` | split per domain under `src/utilities/access/` | done 2026-07-23 (14 domain modules; `campaignAccess.ts` kept as re-export surface) |
|
||||
| God module: ~71 exports of electoral math | `src/lib/electionInsights.ts` | split by concern when next touched | done 2026-07-25 (Pass 2 W4a): module DELETED; survivors in `electionFormat.ts` + `voteTrend.ts` |
|
||||
| Multi-feature action bag: CRUD + CSV import pipeline + LGPD in one file (722 lines) | `src/app/(campaign)/campanha/actions/supporter.ts` | split CRUD vs import | done 2026-07-23 (`supporterImport.ts`) |
|
||||
| Clone twins: create/update/staff-assert/unique-conflict skeleton | `actions/organization.ts` ↔ `actions/stateDeputy.ts` | extract shared factory | done 2026-07-23 (`runStaffEntityMutation` in `campaignEntityActions.ts`) |
|
||||
| 14 near-identical `formActions.ts` try/catch→mapError→redirect/return ladders | `src/app/(campaign)/campanha/**/formActions.ts` | shared form-action wrapper | partially done — C8 F4 (2026-07-21/23) unified the municipality staff wrappers + 4 `*/nova` ladders on `runCampaignRedirectFormAction`; **10 ladders written since remain hand-rolled** (844 lines: assessores, planos, apoiadores ×2, demandas/[slug], liderancas/[id], organizacoes/[slug], dobradinhas/[slug], municipios editar, convite) — done 2026-07-25 (Pass 2 W4d): `runCampaignFormAction` + 13 ações migradas; exceções documentadas em comentário (planos, apoiadores/[id], convite login) |
|
||||
| Flat 80+-file `src/utilities/` junk drawer; `lib/` vs `utilities/` boundary undocumented | `src/utilities/`, `src/lib/` | document convention + move misfiled modules | done 2026-07-23 (convention in `.cursor/rules/engineering-standards.mdc`; `RefreshRouteOnSave` moved to `components/`) |
|
||||
| Praça→Município rename leftovers in identifiers (`PLAZA_MAP_YEARS`, `ACCESSIBLE_PLAZA_IDS_CONTEXT_KEY`, …) | map/list utilities | finish rename | done 2026-07-23 |
|
||||
| Props→state `useEffect` mirrors | `MunicipalityListExpectedVotesControl`, comboboxes | key-remount or fully controlled | closed 2026-07-23 (accepted as correct — see Debt Ledger row) |
|
||||
| Detail-page RSCs mixing loading + tab UI + forms (300+ lines) | `municipios/[slug]/page.tsx`, `planos/[slug]/page.tsx` | extract tab loaders/sections | done 2026-07-25 (Pass 2 W4c): tabs em `MunicipalityDetailTabs.tsx` / `ActionPlanOverviewTab.tsx`; gêmeos de tab dedupados em `detailTabUi.ts` |
|
||||
| God module: list URL + filters + sort + labels + freshness in one file (765 lines, 59 exports) | `src/utilities/municipalityUi.ts` | split along url/filters/sort/labels/freshness seams | done 2026-07-25 (Pass 2 W1-D1): `municipalityListUrl` / `municipalityListFilters` / `municipalityLabels` / `municipalitySignal` |
|
||||
| `components/campaign/` flat with 106 files; `utilities/` flat with 92 | `src/components/campaign/`, `src/utilities/` | within-layer domain subfolders | components done 2026-07-25 (Pass 2 W2, 15 domínios); `utilities/` deferred with trigger: a domain gaining its 3rd new module in a month gets its subfolder |
|
||||
| Smell | Location | Refactoring | Status |
|
||||
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| God module: RBAC for every collection in one file (964 lines, ~80 exports) | `src/utilities/campaignAccess.ts` | split per domain under `src/utilities/access/` | done 2026-07-23 (14 domain modules; `campaignAccess.ts` kept as re-export surface) |
|
||||
| God module: ~71 exports of electoral math | `src/lib/electionInsights.ts` | split by concern when next touched | done 2026-07-25 (Pass 2 W4a): module DELETED; survivors in `electionFormat.ts` + `voteTrend.ts` |
|
||||
| Multi-feature action bag: CRUD + CSV import pipeline + LGPD in one file (722 lines) | `src/app/(campaign)/campanha/actions/supporter.ts` | split CRUD vs import | done 2026-07-23 (`supporterImport.ts`) |
|
||||
| Clone twins: create/update/staff-assert/unique-conflict skeleton | `actions/organization.ts` ↔ `actions/stateDeputy.ts` | extract shared factory | done 2026-07-23 (`runStaffEntityMutation` in `campaignEntityActions.ts`) |
|
||||
| 14 near-identical `formActions.ts` try/catch→mapError→redirect/return ladders | `src/app/(campaign)/campanha/**/formActions.ts` | shared form-action wrapper | partially done — C8 F4 (2026-07-21/23) unified the municipality staff wrappers + 4 `*/nova` ladders on `runCampaignRedirectFormAction`; **10 ladders written since remain hand-rolled** (844 lines: assessores, planos, apoiadores ×2, demandas/[slug], liderancas/[id], organizacoes/[slug], dobradinhas/[slug], municipios editar, convite) — done 2026-07-25 (Pass 2 W4d): `runCampaignFormAction` + 13 ações migradas; exceções documentadas em comentário (planos, apoiadores/[id], convite login) |
|
||||
| Flat 80+-file `src/utilities/` junk drawer; `lib/` vs `utilities/` boundary undocumented | `src/utilities/`, `src/lib/` | document convention + move misfiled modules | done 2026-07-23 (convention in `.cursor/rules/engineering-standards.mdc`; `RefreshRouteOnSave` moved to `components/`) |
|
||||
| Praça→Município rename leftovers in identifiers (`PLAZA_MAP_YEARS`, `ACCESSIBLE_PLAZA_IDS_CONTEXT_KEY`, …) | map/list utilities | finish rename | done 2026-07-23 |
|
||||
| Props→state `useEffect` mirrors | `MunicipalityListExpectedVotesControl`, comboboxes | key-remount or fully controlled | closed 2026-07-23 (accepted as correct — see Debt Ledger row) |
|
||||
| Detail-page RSCs mixing loading + tab UI + forms (300+ lines) | `municipios/[slug]/page.tsx`, `planos/[slug]/page.tsx` | extract tab loaders/sections | done 2026-07-25 (Pass 2 W4c): tabs em `MunicipalityDetailTabs.tsx` / `ActionPlanOverviewTab.tsx`; gêmeos de tab dedupados em `detailTabUi.ts` |
|
||||
| God module: list URL + filters + sort + labels + freshness in one file (765 lines, 59 exports) | `src/utilities/municipalityUi.ts` | split along url/filters/sort/labels/freshness seams | done 2026-07-25 (Pass 2 W1-D1): `municipalityListUrl` / `municipalityListFilters` / `municipalityLabels` / `municipalitySignal` |
|
||||
| `components/campaign/` flat with 106 files; `utilities/` flat with 92 | `src/components/campaign/`, `src/utilities/` | within-layer domain subfolders | components done 2026-07-25 (Pass 2 W2, 15 domínios); `utilities/` deferred with trigger: a domain gaining its 3rd new module in a month gets its subfolder |
|
||||
|
||||
## Sprout / Wrap Register
|
||||
|
||||
|
|
|
|||
12
knip.json
12
knip.json
|
|
@ -9,16 +9,10 @@
|
|||
"tests/**/*.spec.ts",
|
||||
"tests/helpers/serverOnly.ts"
|
||||
],
|
||||
"project": [
|
||||
"src/**/*.{ts,tsx,js}",
|
||||
"scripts/**/*.mjs",
|
||||
"tests/**/*.ts"
|
||||
],
|
||||
"project": ["src/**/*.{ts,tsx,js}", "scripts/**/*.mjs", "tests/**/*.ts"],
|
||||
"ignore": [],
|
||||
"paths": {
|
||||
"server-only": [
|
||||
"tests/helpers/serverOnly.ts"
|
||||
]
|
||||
"server-only": ["tests/helpers/serverOnly.ts"]
|
||||
},
|
||||
"ignoreBinaries": [],
|
||||
"ignoreDependencies": [
|
||||
|
|
@ -34,4 +28,4 @@
|
|||
"enumMembers": "error",
|
||||
"duplicates": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,9 @@ import {
|
|||
removeSupporterData,
|
||||
setSupporterVoteIntention,
|
||||
} from '@/app/(campaign)/campanha/actions/supporter'
|
||||
import {
|
||||
checkboxFormValue,
|
||||
requiredRelationshipFormValue,
|
||||
} from '@/lib/formData'
|
||||
import { supporterRemoveSchema, supporterVoteIntentionSchema } from '@/lib/schemas/supporter'
|
||||
import { checkboxFormValue, requiredRelationshipFormValue } from '@/lib/formData'
|
||||
import type { SupporterVoteIntention } from '@/lib/schemas/supporter'
|
||||
import { supporterRemoveSchema, supporterVoteIntentionSchema } from '@/lib/schemas/supporter'
|
||||
import {
|
||||
mapCampaignFormActionError,
|
||||
type CampaignFormErrorState,
|
||||
|
|
@ -38,9 +35,7 @@ const safeVoteIntentionMessages = [
|
|||
'Somente a coordenação e a assessoria podem gerenciar apoiadores.',
|
||||
] as const
|
||||
|
||||
const toMessageOnlyState = (
|
||||
mapped: CampaignFormErrorState<unknown>,
|
||||
): { message?: string } => {
|
||||
const toMessageOnlyState = (mapped: CampaignFormErrorState<unknown>): { message?: string } => {
|
||||
if (mapped.message) return { message: mapped.message }
|
||||
if (!mapped.fieldErrors) return {}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,10 @@
|
|||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
import { createSupporter } from '@/app/(campaign)/campanha/actions/supporter'
|
||||
import {
|
||||
checkboxFormValue,
|
||||
nullableRelationshipFormValue,
|
||||
optionalFormText,
|
||||
} from '@/lib/formData'
|
||||
import { checkboxFormValue, nullableRelationshipFormValue, optionalFormText } from '@/lib/formData'
|
||||
import { sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
import { supporterCreateSchema } from '@/lib/schemas/supporter'
|
||||
import { runCampaignFormAction } from '@/utilities/campaignFormActionError'
|
||||
import { sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
|
||||
export type SupporterFormState = {
|
||||
status?: 'success'
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { getPayload } from 'payload'
|
|||
|
||||
import { CampaignListEmptyState } from '@/components/campaign/shared/CampaignListEmptyState'
|
||||
import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
import { CampaignScopeBadge } from '@/components/campaign/shared/CampaignScopeBadge'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
import { SupporterFilters } from '@/components/campaign/supporter/SupporterFilters'
|
||||
import { SupporterList } from '@/components/campaign/supporter/SupporterList'
|
||||
import { SupporterListOverview } from '@/components/campaign/supporter/SupporterListOverview'
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ import {
|
|||
setAdvisorMunicipalityMembership,
|
||||
updateAdvisorProfile,
|
||||
} from '@/app/(campaign)/campanha/actions/advisor'
|
||||
import {
|
||||
nullableFormText,
|
||||
requiredFormText,
|
||||
requiredRelationshipFormValue,
|
||||
} from '@/lib/formData'
|
||||
import { nullableFormText, requiredFormText, requiredRelationshipFormValue } from '@/lib/formData'
|
||||
import { ADVISOR_ACTION_SAFE_MESSAGES } from '@/lib/schemas/advisor'
|
||||
import {
|
||||
runCampaignFormAction,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import {
|
|||
CampaignListPendingBoundary,
|
||||
CampaignListResults,
|
||||
} from '@/components/campaign/shared/CampaignListPending'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
import {
|
||||
CampaignTable,
|
||||
CampaignTableHead,
|
||||
type CampaignTableColumn,
|
||||
} from '@/components/campaign/shared/CampaignTable'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
import { Badge } from '@/components/ui/Badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,5 @@ export const updateStateDeputyFormAction = async (
|
|||
return { message: 'Dobradinha atualizada.' }
|
||||
},
|
||||
safeMessages: ['Somente a coordenação e a assessoria gerenciam dobradinhas.'],
|
||||
genericMessage:
|
||||
'Não foi possível salvar a dobradinha. Verifique seu acesso e tente novamente.',
|
||||
genericMessage: 'Não foi possível salvar a dobradinha. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -39,6 +39,5 @@ export const updateMunicipalityStrategyFormAction = async (
|
|||
return { message: 'Estratégia atualizada.' }
|
||||
},
|
||||
safeMessages: municipalityStaffEditSafeMessages,
|
||||
genericMessage:
|
||||
'Não foi possível salvar a estratégia. Verifique seu acesso e tente novamente.',
|
||||
genericMessage: 'Não foi possível salvar a estratégia. Verifique seu acesso e tente novamente.',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,17 +17,17 @@ import { Badge } from '@/components/ui/Badge'
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { actionPlanKindLabels } from '@/lib/schemas/actionPlan'
|
||||
import { isCampaignStaff } from '@/utilities/campaignAccess'
|
||||
import { getCampaignUser } from '@/utilities/campaignAuth'
|
||||
import {
|
||||
ActionPlanNotFoundError,
|
||||
resolveAccessibleActionPlanContext,
|
||||
} from '@/utilities/actionPlanPageData'
|
||||
import { getActionPlanDetailPageData } from '@/utilities/actionPlanDetailPageData'
|
||||
import {
|
||||
getActionPlanDetailTabRedirect,
|
||||
resolveActionPlanDetailTab,
|
||||
} from '@/utilities/actionPlanDetailTabUi'
|
||||
import {
|
||||
ActionPlanNotFoundError,
|
||||
resolveAccessibleActionPlanContext,
|
||||
} from '@/utilities/actionPlanPageData'
|
||||
import { isCampaignStaff } from '@/utilities/campaignAccess'
|
||||
import { getCampaignUser } from '@/utilities/campaignAuth'
|
||||
import { formatBahiaDateTimeLabel } from '@/utilities/campaignTime'
|
||||
|
||||
import { ActionPlanOverviewTab } from './ActionPlanOverviewTab'
|
||||
|
|
|
|||
|
|
@ -7,19 +7,19 @@
|
|||
// don't grow parameters for.
|
||||
|
||||
import config from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
import { createActionPlan, updateActionPlan } from '@/app/(campaign)/campanha/actions/actionPlan'
|
||||
import type { ActionPlanFormState } from '@/components/campaign/actionPlan/ActionPlanForm'
|
||||
import { optionalFormText } from '@/lib/formData'
|
||||
import { getCampaignUser } from '@/utilities/campaignAuth'
|
||||
import { mapCampaignFormActionError } from '@/utilities/campaignFormActionError'
|
||||
import { slugify } from '@/lib/slug'
|
||||
import {
|
||||
parseActionPlanCreateFormData,
|
||||
parseActionPlanUpdateFormData,
|
||||
} from '@/utilities/actionPlanFormData'
|
||||
import { slugify } from '@/lib/slug'
|
||||
import { getCampaignUser } from '@/utilities/campaignAuth'
|
||||
import { mapCampaignFormActionError } from '@/utilities/campaignFormActionError'
|
||||
|
||||
const getActionError = (error: unknown): ActionPlanFormState => {
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ import {
|
|||
reloadCampaignActor,
|
||||
reloadStaffActor,
|
||||
} from '@/utilities/campaignActionContext'
|
||||
import { hookFilledCreateData } from '@/utilities/hookFilledData'
|
||||
import { withPayloadTransaction } from '@/utilities/payloadTransaction'
|
||||
import { acquireTextAdvisoryLocks } from '@/utilities/postgresTransactionLocks'
|
||||
import { hookFilledCreateData } from '@/utilities/hookFilledData'
|
||||
|
||||
export const createCampaignDemandRecord = async (
|
||||
payload: Payload,
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import {
|
|||
redeemCampaignInviteAutofill,
|
||||
redeemCampaignInviteLogin,
|
||||
} from '@/app/(campaign)/campanha/actions/invite'
|
||||
import { campaignInviteAutofillSchema, campaignInviteLoginSchema } from '@/lib/schemas/invite'
|
||||
import {
|
||||
checkboxFormValue,
|
||||
nullableFormText,
|
||||
requiredFormSecret,
|
||||
requiredFormText,
|
||||
} from '@/lib/formData'
|
||||
import { campaignInviteAutofillSchema, campaignInviteLoginSchema } from '@/lib/schemas/invite'
|
||||
import {
|
||||
mapCampaignFormActionError,
|
||||
runCampaignFormAction,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
import { MigrateDownArgs, MigrateUpArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
/**
|
||||
* Pass 2 D3 — retire the last hardcoded consent document id.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { Where } from 'payload'
|
||||
|
||||
import { isStaffCampaignRole } from '@/lib/campaignRoles'
|
||||
import {
|
||||
isSupporterVoteIntention,
|
||||
resolveBahiaMunicipality,
|
||||
type SupporterVoteIntention,
|
||||
} from '@/lib/schemas/supporter'
|
||||
import { isStaffCampaignRole } from '@/lib/campaignRoles'
|
||||
import type { CampaignUser, Supporter } from '@/payload-types'
|
||||
import {
|
||||
buildListHref,
|
||||
|
|
@ -44,13 +44,7 @@ export type SupporterListState = {
|
|||
|
||||
type RawSearchParams = CampaignListRawSearchParams
|
||||
|
||||
const supporterListParamNames = [
|
||||
'q',
|
||||
'voteIntention',
|
||||
'city',
|
||||
'municipality',
|
||||
'page',
|
||||
] as const
|
||||
const supporterListParamNames = ['q', 'voteIntention', 'city', 'municipality', 'page'] as const
|
||||
|
||||
const supporterListParamNameSet = new Set<string>(supporterListParamNames)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue