From d6455b1333a7bf1f0bc8dfeeff89596028841c83 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:04:14 +0000 Subject: [PATCH 1/6] =?UTF-8?q?B137=20=E2=80=94=20Colunas=20ao=20lado=20da?= =?UTF-8?q?=20omnibox=20e=20fim=20do=20Ordenado=20por=20residual?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CampaignColumnPicker to omnibox trailing via CampaignColumnPickerTrailing on all seven B17 list surfaces (+ organizações). Remove the picker row from CampaignTable and drop visible sort-summary spans on territórios, lideranças and dobradinhas (sr-only table captions keep sort context). Closes #304 Co-authored-by: Francisco Solla --- .../colunas-ao-lado-omnibox-listas-impl.md | 58 +++++++++++++++++++ .../campanha/(app)/apoiadores/page.tsx | 9 ++- .../campanha/(app)/demandas/page.tsx | 12 +++- .../campanha/(app)/dobradinhas/page.tsx | 12 ++-- .../campanha/(app)/liderancas/page.tsx | 12 ++-- .../campanha/(app)/municipios/page.tsx | 12 +++- .../campanha/(app)/organizacoes/page.tsx | 12 +++- .../campanha/(app)/territorios/page.tsx | 13 ++++- .../campaign/demand/DemandFilters.tsx | 11 +++- .../campaign/leadership/LeadershipFilters.tsx | 5 +- .../municipality/MunicipalityFilters.tsx | 12 +++- .../municipality/MunicipalityList.tsx | 24 ++++++++ .../municipality/TerritoryFilters.tsx | 5 +- .../campaign/municipality/TerritoryList.tsx | 9 +-- .../municipality/TerritoryListColumns.tsx | 14 +++++ .../organization/OrganizationFilters.tsx | 11 +++- .../shared/CampaignColumnPickerTrailing.tsx | 30 ++++++++++ .../campaign/shared/CampaignTable.tsx | 22 +------ .../stateDeputy/StateDeputyFilters.tsx | 5 +- .../campaign/supporter/SupporterFilters.tsx | 5 +- .../campaign/supporter/SupporterList.tsx | 7 ++- src/lib/campaignColumnVisibility.ts | 12 ++++ 22 files changed, 262 insertions(+), 50 deletions(-) create mode 100644 docs/plans/colunas-ao-lado-omnibox-listas-impl.md create mode 100644 src/components/campaign/shared/CampaignColumnPickerTrailing.tsx diff --git a/docs/plans/colunas-ao-lado-omnibox-listas-impl.md b/docs/plans/colunas-ao-lado-omnibox-listas-impl.md new file mode 100644 index 00000000..a8480ea0 --- /dev/null +++ b/docs/plans/colunas-ao-lado-omnibox-listas-impl.md @@ -0,0 +1,58 @@ +# Impl: B137 — Colunas ao lado da omnibox (e fim do “Ordenado por …” residual) + +Status: aprovado +Atualizado em: 2026-08-02 +Issue: #304 +Intenção: docs/plans/colunas-ao-lado-omnibox-listas.md +Appetite restante: herdado (~0,5–1 dia eng) + +## Leitura da intenção + +- **Outcome:** Em todas as listas com seletor de colunas (B17), o botão **Colunas** fica na mesma fileira da omnibox (`trailing`); dados começam logo abaixo da barra. O span visível **“Ordenado por …”** some em Territórios, Lideranças e Dobradinhas (sort continua na omnibox/header + caption `sr-only`). +- **O que NÃO negociar:** persistência/contrato do picker (cookie B17); `hidden md:flex` no mobile (gate 2026-08-02 opção A); lockdown de liderança; não mover sort para fora da omnibox/header. +- **O que reavaliar:** hipótese de editar só `CampaignTable` — o picker precisa sair da tabela e entrar nos `*Filters` via `trailing`, porque a omnibox já vive nos filtros. + +## Abordagem recomendada + +**Opções consideradas:** +- A) Manter picker em `CampaignTable` com CSS `absolute` ao lado da omnibox — rejeitada: DOM distinto, frágil entre listas com/sem cards mobile. +- B) Slot `trailing` nos `*Filters` + remover picker de `CampaignTable` — **recomendada**: precedente B127 (`SaveMunicipalityFilterControl`), boundary de pending intacto. +- C) Novo shell `CampaignListToolbar` — rejeitada: twin desnecessário. + +**Recomendação:** B — extrair `CampaignColumnPickerTrailing` (wrapper `hidden md:flex` + `CampaignColumnPicker`), helper `toCampaignColumnPickerColumns`, exportar metadados de colunas onde a tabela é dinâmica (municípios/apoiadores/territórios), páginas estáticas mapeiam `demandColumns`/`organizationColumns`/etc. + +### Componentes / mudanças + +- **`CampaignColumnPickerTrailing`** (`shared/CampaignColumnPickerTrailing.tsx`): client; encapsula breakpoint B17. +- **`toCampaignColumnPickerColumns`** (`lib/campaignColumnVisibility.ts`): puro; mapeia `{id,label,mandatory}`. +- **`CampaignTable`**: remove render do picker; mantém `columnVisibility` só para `resolveVisibleColumns`. +- **`*Filters.tsx` (7 listas)**: prop opcional `trailing?: ReactNode` composta com trailing existente. +- **Páginas/listas**: passam picker no `trailing` dos filtros; removem `

Ordenado por…

` visível. +- **Exports:** `municipalityListPickerColumns`, `supporterPickerColumns`, `territoryListPickerColumns`. +- **Migration:** sem migration. + +### Dados → forma + +N/A — só chrome de layout. + +## Fases verificáveis + +1. **Chassis** — helper + trailing component + remover picker de `CampaignTable`. +2. **Wire-up** — 7 listas + organizações; remover sort span residual. +3. **Gates** — `pnpm gate:fast`; push via `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- Picker mobile abaixo de `md` — fora (B17 lacuna). +- Reordenar colunas / export / cenário na barra. + +## Riscos e mitigação + +- **Picker fora de `CampaignListResults`:** filtros já estão dentro de `CampaignListPendingBoundary` — `useCampaignListTransition` continua correto. +- **Colunas dinâmicas (staff vs leader):** `municipalityListPickerColumns({ isStaffView })` espelha a mesma bifurcação da tabela. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto +- [x] Invariantes AGENTS/engineering-standards +- [x] Testes de domínio previstos (unit existentes do picker; sem mudança de access/write) diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx b/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx index a7e0abb6..7080f740 100644 --- a/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx +++ b/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx @@ -9,10 +9,11 @@ import { redirect } from 'next/navigation' import { getPayload } from 'payload' import { CampaignListEmptyState } from '@/components/campaign/shared/CampaignListEmptyState' +import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { SupporterFilters } from '@/components/campaign/supporter/SupporterFilters' -import { SupporterList } from '@/components/campaign/supporter/SupporterList' +import { SupporterList, supporterPickerColumns } from '@/components/campaign/supporter/SupporterList' import { SupporterListOverview } from '@/components/campaign/supporter/SupporterListOverview' import { Button } from '@/components/ui/button' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' @@ -102,6 +103,12 @@ export default async function SupportersPage({ searchParams }: SupportersPagePro key={buildSupporterFiltersKey(state)} state={state} municipalityOptions={municipalityOptions} + trailing={ + + } /> {listBody} diff --git a/src/app/(campaign)/campanha/(app)/demandas/page.tsx b/src/app/(campaign)/campanha/(app)/demandas/page.tsx index a18c35a2..9870033d 100644 --- a/src/app/(campaign)/campanha/(app)/demandas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/demandas/page.tsx @@ -4,6 +4,7 @@ import Link from 'next/link' import { getPayload } from 'payload' import { DemandFilters } from '@/components/campaign/demand/DemandFilters' +import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignListEmptyState } from '@/components/campaign/shared/CampaignListEmptyState' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { @@ -15,6 +16,7 @@ import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' +import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' import { campaignDemandKindLabels, campaignDemandStatusLabels, @@ -117,7 +119,15 @@ export default async function DemandsPage({ searchParams }: DemandsPageProps) { - + + } + /> + } /> -

- {sortSummary} -

{/* One shared Drawer for every chip-cell sheet on coarse pointers (miss #52 — never a Drawer root per opened cell). */} diff --git a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx index b6c37b32..eee62cbf 100644 --- a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx @@ -6,6 +6,7 @@ import { getPayload } from 'payload' import { LeadershipInviteRowAction } from '@/components/campaign/invite/LeadershipInviteRowAction' import { LeadershipFilters } from '@/components/campaign/leadership/LeadershipFilters' +import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { LeadershipListSupportStatusControl } from '@/components/campaign/leadership/LeadershipListSupportStatusControl' import { LeadershipFilterHead, @@ -40,7 +41,7 @@ import { EmptyMedia, EmptyTitle, } from '@/components/ui/Empty' -import { resolveVisibleColumns } from '@/lib/campaignColumnVisibility' +import { resolveVisibleColumns, toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { formatBahiaDateTimeLabel } from '@/lib/campaignTime' import { @@ -415,12 +416,15 @@ export default async function LeadershipsPage({ searchParams }: LeadershipsPageP municipalityFilterOptions={municipalityFilterOptions} organizationFilterOptions={organizationFilterOptions} stateDeputyFilterOptions={stateDeputyFilterOptions} + trailing={ + + } /> -

- {sortSummary} -

{/* One shared Drawer for every chip-cell sheet on coarse pointers (miss #52 — never a Drawer root per opened cell). */} diff --git a/src/app/(campaign)/campanha/(app)/municipios/page.tsx b/src/app/(campaign)/campanha/(app)/municipios/page.tsx index 3b3fd4f3..78312330 100644 --- a/src/app/(campaign)/campanha/(app)/municipios/page.tsx +++ b/src/app/(campaign)/campanha/(app)/municipios/page.tsx @@ -9,8 +9,12 @@ import { getPayload } from 'payload' import { RecentVisitTracker } from '@/components/campaign/dashboard/RecentVisitTracker' import { MunicipalityEstimateScenarioProvider } from '@/components/campaign/municipality/MunicipalityEstimateScenarioContext' import { MunicipalityFilters } from '@/components/campaign/municipality/MunicipalityFilters' -import { MunicipalityList } from '@/components/campaign/municipality/MunicipalityList' +import { + MunicipalityList, + municipalityListPickerColumns, +} from '@/components/campaign/municipality/MunicipalityList' import { MunicipalityListPageChrome } from '@/components/campaign/municipality/MunicipalityListPageChrome' +import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' @@ -101,6 +105,12 @@ export default async function MunicipalitiesPage({ searchParams }: Municipalitie regionFilterOptions={columnFilterOptions.region} advisorFilterOptions={columnFilterOptions.advisor} slugFilterValues={columnFilterOptions.name} + trailing={ + + } /> ) diff --git a/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx b/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx index 0894f502..f851d3f4 100644 --- a/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx +++ b/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx @@ -4,6 +4,7 @@ import Link from 'next/link' import { getPayload } from 'payload' import { OrganizationFilters } from '@/components/campaign/organization/OrganizationFilters' +import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignListEmptyState } from '@/components/campaign/shared/CampaignListEmptyState' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { @@ -15,6 +16,7 @@ import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' +import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' import { organizationKindLabels } from '@/lib/schemas/organization' import { readCampaignColumnVisibility } from '@/utilities/campaignColumnVisibilityCookie' import { requireCampaignPageActor } from '@/utilities/campaignPageActor' @@ -87,7 +89,15 @@ export default async function OrganizationsPage({ searchParams }: OrganizationsP - + + } + /> - + + } + /> { +export const DemandFilters = ({ + state, + trailing, +}: { + state: DemandListState + trailing?: ReactNode +}) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, toHref: (next) => buildDemandListHref(next, 1), @@ -68,6 +74,7 @@ export const DemandFilters = ({ state }: { state: DemandListState }) => { onClearAll={() => { runAction(clearDemandOmnibox(state)) }} + trailing={trailing} /> ) diff --git a/src/components/campaign/leadership/LeadershipFilters.tsx b/src/components/campaign/leadership/LeadershipFilters.tsx index 981256c4..e55e6b8f 100644 --- a/src/components/campaign/leadership/LeadershipFilters.tsx +++ b/src/components/campaign/leadership/LeadershipFilters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, type ReactNode } from 'react' import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox' import { useCampaignListFilterNavigation } from '@/components/campaign/shared/useCampaignListFilterNavigation' @@ -24,11 +24,13 @@ export const LeadershipFilters = ({ municipalityFilterOptions, organizationFilterOptions, stateDeputyFilterOptions, + trailing, }: { state: LeadershipListState municipalityFilterOptions: LeadershipFilterOption[] organizationFilterOptions: LeadershipFilterOption[] stateDeputyFilterOptions: LeadershipFilterOption[] + trailing?: ReactNode }) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, @@ -132,6 +134,7 @@ export const LeadershipFilters = ({ onClearAll={() => { runAction(clearLeadershipOmnibox(state)) }} + trailing={trailing} /> ) diff --git a/src/components/campaign/municipality/MunicipalityFilters.tsx b/src/components/campaign/municipality/MunicipalityFilters.tsx index f4e6c888..8fb33f37 100644 --- a/src/components/campaign/municipality/MunicipalityFilters.tsx +++ b/src/components/campaign/municipality/MunicipalityFilters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, type ReactNode } from 'react' import { useMunicipalityEstimateScenarioOptional } from '@/components/campaign/municipality/MunicipalityEstimateScenarioContext' import { SaveMunicipalityFilterControl } from '@/components/campaign/municipality/SaveMunicipalityFilterControl' @@ -32,6 +32,8 @@ type MunicipalityFiltersProps = { advisorFilterOptions: MunicipalityFilterOption[] /** Facet slugs (labeled from the catalog on the client). */ slugFilterValues?: readonly string[] + /** Beside the omnibox (B137): column picker, save bookmark, … */ + trailing?: ReactNode } export const MunicipalityFilters = ({ @@ -40,6 +42,7 @@ export const MunicipalityFilters = ({ regionFilterOptions, advisorFilterOptions, slugFilterValues = [], + trailing, }: MunicipalityFiltersProps) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, @@ -133,7 +136,12 @@ export const MunicipalityFilters = ({ onClearAll={() => { runAction(clearMunicipalityOmnibox(state)) }} - trailing={} + trailing={ + <> + {trailing} + + + } /> ) diff --git a/src/components/campaign/municipality/MunicipalityList.tsx b/src/components/campaign/municipality/MunicipalityList.tsx index 0f761667..15ea0250 100644 --- a/src/components/campaign/municipality/MunicipalityList.tsx +++ b/src/components/campaign/municipality/MunicipalityList.tsx @@ -35,6 +35,7 @@ import { } from '@/components/ui/Empty' import { resolveVisibleColumns, + type CampaignColumnPickerColumn, type CampaignColumnVisibility, } from '@/lib/campaignColumnVisibility' import { formatEngagementLevelLabel } from '@/lib/engagementLevel' @@ -412,6 +413,29 @@ const municipalityListColumns = ({ ] satisfies Array)), ] +export const municipalityListPickerColumns = ({ + isStaffView, +}: Pick): CampaignColumnPickerColumn[] => { + const base: CampaignColumnPickerColumn[] = [ + { id: 'name', label: municipalityColumnLabels.name, mandatory: true }, + { id: 'region', label: municipalityColumnLabels.region }, + { id: 'votos', label: municipalityColumnLabels.votos }, + ] + if (!isStaffView) return base + + return [ + ...base, + { id: 'classe', label: municipalityColumnLabels.classe }, + { id: 'level', label: municipalityColumnLabels.level }, + { id: 'advisors', label: municipalityColumnLabels.advisors }, + { id: 'trend', label: municipalityColumnLabels.trend }, + { id: 'expectedVotes', label: municipalityColumnLabels.expectedVotes }, + { id: 'lastSignal', label: municipalityColumnLabels.lastSignal }, + { id: 'goalCoverage', label: municipalityColumnLabels.goalCoverage }, + { id: 'lastUpdateAt', label: municipalityColumnLabels.lastUpdateAt }, + ] +} + export const MunicipalityList = (props: MunicipalityListProps) => { const { municipalities, advisorNamesById, isStaffView, isCoordinator, advisorOptions, state } = props diff --git a/src/components/campaign/municipality/TerritoryFilters.tsx b/src/components/campaign/municipality/TerritoryFilters.tsx index 85432793..4c1dcd1f 100644 --- a/src/components/campaign/municipality/TerritoryFilters.tsx +++ b/src/components/campaign/municipality/TerritoryFilters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, type ReactNode } from 'react' import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox' import { useCampaignListFilterNavigation } from '@/components/campaign/shared/useCampaignListFilterNavigation' @@ -22,9 +22,11 @@ import { export const TerritoryFilters = ({ state, regionOptions, + trailing, }: { state: TerritoryListState regionOptions: TerritoryFilterOption[] + trailing?: ReactNode }) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, @@ -81,6 +83,7 @@ export const TerritoryFilters = ({ onClearAll={() => { runAction(clearTerritoryOmnibox(state)) }} + trailing={trailing} /> ) diff --git a/src/components/campaign/municipality/TerritoryList.tsx b/src/components/campaign/municipality/TerritoryList.tsx index d76e6c37..34214d28 100644 --- a/src/components/campaign/municipality/TerritoryList.tsx +++ b/src/components/campaign/municipality/TerritoryList.tsx @@ -69,12 +69,8 @@ export const TerritoryList = ({ const sortSummary = formatTerritoryListSortSummary(sort, dir) return ( - <> -

- {sortSummary} -

- } /> - ) } diff --git a/src/components/campaign/municipality/TerritoryListColumns.tsx b/src/components/campaign/municipality/TerritoryListColumns.tsx index ee63bf9a..bbf8c200 100644 --- a/src/components/campaign/municipality/TerritoryListColumns.tsx +++ b/src/components/campaign/municipality/TerritoryListColumns.tsx @@ -4,6 +4,7 @@ import { TerritorySortableHead } from '@/components/campaign/municipality/Territ import { CampaignTransitionAnchor } from '@/components/campaign/shared/CampaignListPending' import type { CampaignTableColumn } from '@/components/campaign/shared/CampaignTable' import { Badge } from '@/components/ui/Badge' +import type { CampaignColumnPickerColumn } from '@/lib/campaignColumnVisibility' import { formatElectionNumber } from '@/lib/electionFormat' import { formatGoalCoverageDeficitShortLabel, @@ -139,6 +140,19 @@ const captureTooltipContent = (row: TerritoryTableRow) => { ) } +export const territoryListPickerColumns: CampaignColumnPickerColumn[] = [ + { id: 'region', label: territoryListSortLabels.region, mandatory: true }, + { id: 'municipalities', label: territoryListSortLabels.municipalities }, + { id: 'votes2022', label: territoryListSortLabels.votes2022 }, + { id: 'pct', label: territoryListSortLabels.pct }, + { id: 'validVotes2022', label: territoryListSortLabels.validVotes2022 }, + { id: 'estimate2026', label: territoryListSortLabels.estimate2026 }, + { id: 'cobertura', label: territoryListSortLabels.cobertura }, + { id: 'captura', label: territoryListSortLabels.captura }, + { id: 'classe', label: territoryListSortLabels.classe }, + { id: 'coverage', label: territoryListSortLabels.coverage }, +] + export const territoryListColumns = ({ state, regionOptions, diff --git a/src/components/campaign/organization/OrganizationFilters.tsx b/src/components/campaign/organization/OrganizationFilters.tsx index 91494bd9..73d3bd54 100644 --- a/src/components/campaign/organization/OrganizationFilters.tsx +++ b/src/components/campaign/organization/OrganizationFilters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, type ReactNode } from 'react' import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox' import { useCampaignListFilterNavigation } from '@/components/campaign/shared/useCampaignListFilterNavigation' @@ -18,7 +18,13 @@ import { type OrganizationOmniboxAction, } from '@/utilities/organization/organizationOmnibox' -export const OrganizationFilters = ({ state }: { state: OrganizationListState }) => { +export const OrganizationFilters = ({ + state, + trailing, +}: { + state: OrganizationListState + trailing?: ReactNode +}) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, toHref: (next) => buildOrganizationListHref(next, 1), @@ -76,6 +82,7 @@ export const OrganizationFilters = ({ state }: { state: OrganizationListState }) onClearAll={() => { runAction(clearOrganizationOmnibox(state)) }} + trailing={trailing} /> ) diff --git a/src/components/campaign/shared/CampaignColumnPickerTrailing.tsx b/src/components/campaign/shared/CampaignColumnPickerTrailing.tsx new file mode 100644 index 00000000..44c6fda6 --- /dev/null +++ b/src/components/campaign/shared/CampaignColumnPickerTrailing.tsx @@ -0,0 +1,30 @@ +'use client' + +import { CampaignColumnPicker } from '@/components/campaign/shared/CampaignColumnPicker' +import type { + CampaignColumnPickerColumn, + CampaignColumnVisibility, +} from '@/lib/campaignColumnVisibility' + +type CampaignColumnPickerTrailingProps = { + columnVisibility: CampaignColumnVisibility + columns: readonly CampaignColumnPickerColumn[] +} + +/** + * B137 — column picker beside the list omnibox (`trailing`), not stacked above + * the table. Gated at `md:` because municípios and apoiadores hide the table + * below that width and show cards instead (B17). + */ +export const CampaignColumnPickerTrailing = ({ + columnVisibility, + columns, +}: CampaignColumnPickerTrailingProps) => ( +
+ +
+) diff --git a/src/components/campaign/shared/CampaignTable.tsx b/src/components/campaign/shared/CampaignTable.tsx index d227b27f..b3deb4b1 100644 --- a/src/components/campaign/shared/CampaignTable.tsx +++ b/src/components/campaign/shared/CampaignTable.tsx @@ -1,7 +1,6 @@ import { Fragment, type ReactNode } from 'react' import { CampaignCellTooltip } from '@/components/campaign/shared/CampaignCellTooltip' -import { CampaignColumnPicker } from '@/components/campaign/shared/CampaignColumnPicker' import { CampaignHoverTooltip } from '@/components/campaign/shared/CampaignHoverTooltip' import { Table, @@ -140,9 +139,9 @@ export const CampaignTableHead = ({ type CampaignTableProps = { columns: Array> /** - * B17 — passing this turns the column picker on. The caller reads the cookie - * (`readCampaignColumnVisibility`) and hands the result over whole: hidden - * columns are never rendered, so they cost nothing in the RSC payload either. + * B17 — hidden column ids from the cookie (`readCampaignColumnVisibility`). + * The picker itself renders in the omnibox `trailing` (B137); this prop only + * filters which columns the table paints. */ columnVisibility?: CampaignColumnVisibility rows: readonly Row[] @@ -179,21 +178,6 @@ export const CampaignTable = ({ return ( <> - {columnVisibility ? ( - // Gated at `md:` because municípios and apoiadores hide this table - // below that width (`className="hidden md:block"`) and show curated - // cards instead — a picker over a table nobody can see is noise. The - // cost is that the five surfaces which DO scroll this table on a phone - // have no picker there; giving them one needs a per-caller seam, not a - // breakpoint (registered as a fill-in in the B17 plan). -
- ({ id, label, mandatory }))} - hiddenColumnIds={columnVisibility.hiddenColumnIds} - /> -
- ) : null}
{caption ? {caption} : null} diff --git a/src/components/campaign/stateDeputy/StateDeputyFilters.tsx b/src/components/campaign/stateDeputy/StateDeputyFilters.tsx index 8885352f..71dd7c87 100644 --- a/src/components/campaign/stateDeputy/StateDeputyFilters.tsx +++ b/src/components/campaign/stateDeputy/StateDeputyFilters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, type ReactNode } from 'react' import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox' import { useCampaignListFilterNavigation } from '@/components/campaign/shared/useCampaignListFilterNavigation' @@ -23,10 +23,12 @@ export const StateDeputyFilters = ({ state, partyOptions, hasNoParty, + trailing, }: { state: StateDeputyListState partyOptions: StateDeputyFilterOption[] hasNoParty: boolean + trailing?: ReactNode }) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, @@ -83,6 +85,7 @@ export const StateDeputyFilters = ({ onClearAll={() => { runAction(clearStateDeputyOmnibox(state)) }} + trailing={trailing} /> ) diff --git a/src/components/campaign/supporter/SupporterFilters.tsx b/src/components/campaign/supporter/SupporterFilters.tsx index e02d7720..81202f8c 100644 --- a/src/components/campaign/supporter/SupporterFilters.tsx +++ b/src/components/campaign/supporter/SupporterFilters.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, type ReactNode } from 'react' import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox' import type { RelationOption } from '@/components/campaign/shared/RelationMultiSelect' @@ -20,9 +20,11 @@ import { municipalityComboboxOptions } from '@/utilities/territory/territoryComb export const SupporterFilters = ({ state, municipalityOptions, + trailing, }: { state: SupporterListState municipalityOptions: RelationOption[] + trailing?: ReactNode }) => { const { navigate, isPending } = useCampaignListFilterNavigation({ state, @@ -109,6 +111,7 @@ export const SupporterFilters = ({ onClearAll={() => { runAction(clearSupporterOmnibox(state)) }} + trailing={trailing} /> ) diff --git a/src/components/campaign/supporter/SupporterList.tsx b/src/components/campaign/supporter/SupporterList.tsx index b6178e89..b289f253 100644 --- a/src/components/campaign/supporter/SupporterList.tsx +++ b/src/components/campaign/supporter/SupporterList.tsx @@ -5,7 +5,10 @@ import { CampaignTable, type CampaignTableColumn } from '@/components/campaign/s import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import type { CampaignColumnVisibility } from '@/lib/campaignColumnVisibility' +import { + toCampaignColumnPickerColumns, + type CampaignColumnVisibility, +} from '@/lib/campaignColumnVisibility' import { formatBrazilianPhoneInput } from '@/lib/phone' import { supporterVoteIntentionLabels } from '@/utilities/supporter/supporterUi' import type { SupporterListItemViewModel } from '@/utilities/supporter/supporterViewModels' @@ -139,6 +142,8 @@ const supporterColumns: Array> = }, ] +export const supporterPickerColumns = toCampaignColumnPickerColumns(supporterColumns) + export const SupporterList = ({ supporters, columnVisibility, empty }: SupporterListProps) => ( <>
diff --git a/src/lib/campaignColumnVisibility.ts b/src/lib/campaignColumnVisibility.ts index 885dbef6..80b9ee53 100644 --- a/src/lib/campaignColumnVisibility.ts +++ b/src/lib/campaignColumnVisibility.ts @@ -42,6 +42,18 @@ export type CampaignColumnVisibility = { hiddenColumnIds: readonly string[] } +/** Serializable column metadata for `CampaignColumnPicker` (B17 / B137). */ +export type CampaignColumnPickerColumn = { + id: string + label: string + mandatory?: boolean +} + +export const toCampaignColumnPickerColumns = ( + columns: readonly { id: string; label: string; mandatory?: boolean }[], +): CampaignColumnPickerColumn[] => + columns.map(({ id, label, mandatory }) => ({ id, label, mandatory })) + /** * `listId:colA~colB|otherList:colC`. Every character here is legal in a * cookie value (RFC 6265 forbids comma, semicolon, whitespace, quotes and From e88073d892e09e0753f60bce255e3e15777ed76a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:08:31 +0000 Subject: [PATCH 2/6] =?UTF-8?q?B137=20=E2=80=94=20simplify=20pass:=20dedup?= =?UTF-8?q?e=20picker=20type,=20derive=20territory=20columns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Solla --- .../campanha/(app)/dobradinhas/page.tsx | 5 ++-- .../campanha/(app)/liderancas/page.tsx | 5 ++-- .../campanha/(app)/territorios/page.tsx | 7 +++-- .../campaign/municipality/TerritoryList.tsx | 28 +++++++++---------- .../municipality/TerritoryListColumns.tsx | 14 ---------- .../campaign/shared/CampaignColumnPicker.tsx | 7 +---- .../campaign/shared/CampaignTable.tsx | 6 ++-- 7 files changed, 25 insertions(+), 47 deletions(-) diff --git a/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx b/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx index ccadfd1c..f107903f 100644 --- a/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx @@ -34,7 +34,7 @@ import { EmptyMedia, EmptyTitle, } from '@/components/ui/Empty' -import { resolveVisibleColumns, toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' +import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import type { MunicipalityPortfolioIndexEntry } from '@/lib/municipalityPortfolio' import { cn } from '@/lib/utils' @@ -187,8 +187,7 @@ export default async function StateDeputiesPage({ searchParams }: StateDeputiesP ]) const columnVisibility = await readCampaignColumnVisibility('dobradinhas') - const isLeadershipVisible = - resolveVisibleColumns([{ id: 'leaderships' }], columnVisibility.hiddenColumnIds).length > 0 + const isLeadershipVisible = !columnVisibility.hiddenColumnIds.includes('leaderships') const [ { rows, totalDocs, totalPages, filterFacets }, diff --git a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx index eee62cbf..86a39b18 100644 --- a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx @@ -41,7 +41,7 @@ import { EmptyMedia, EmptyTitle, } from '@/components/ui/Empty' -import { resolveVisibleColumns, toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' +import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { formatBahiaDateTimeLabel } from '@/lib/campaignTime' import { @@ -328,8 +328,7 @@ export default async function LeadershipsPage({ searchParams }: LeadershipsPageP ]) const columnVisibility = await readCampaignColumnVisibility('liderancas') - const isStateDeputyVisible = - resolveVisibleColumns([{ id: 'stateDeputies' }], columnVisibility.hiddenColumnIds).length > 0 + const isStateDeputyVisible = !columnVisibility.hiddenColumnIds.includes('stateDeputies') const [ { rows, totalDocs, totalPages, filterFacets }, diff --git a/src/app/(campaign)/campanha/(app)/territorios/page.tsx b/src/app/(campaign)/campanha/(app)/territorios/page.tsx index 19c21d00..ba9cbc67 100644 --- a/src/app/(campaign)/campanha/(app)/territorios/page.tsx +++ b/src/app/(campaign)/campanha/(app)/territorios/page.tsx @@ -3,7 +3,7 @@ import { redirect } from 'next/navigation' import { getPayload } from 'payload' import { TerritoryFilters } from '@/components/campaign/municipality/TerritoryFilters' -import { territoryListPickerColumns } from '@/components/campaign/municipality/TerritoryListColumns' +import { territoryListColumns } from '@/components/campaign/municipality/TerritoryListColumns' import { TerritoryList } from '@/components/campaign/municipality/TerritoryList' import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' @@ -13,6 +13,7 @@ import { } from '@/components/campaign/shared/CampaignListPending' import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' +import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' import { readCampaignColumnVisibility } from '@/utilities/campaignColumnVisibilityCookie' import { requireCampaignPageActor } from '@/utilities/campaignPageActor' import { loadTerritoryOverview } from '@/utilities/territory/loadTerritoryOverview' @@ -56,7 +57,9 @@ export default async function TerritoriesPage({ searchParams }: TerritoriesPageP trailing={ } /> diff --git a/src/components/campaign/municipality/TerritoryList.tsx b/src/components/campaign/municipality/TerritoryList.tsx index 34214d28..541fb207 100644 --- a/src/components/campaign/municipality/TerritoryList.tsx +++ b/src/components/campaign/municipality/TerritoryList.tsx @@ -71,20 +71,18 @@ export const TerritoryList = ({ return ( - row.variant === 'parent' ? row.region : `${row.parentRegion}-${row.label}` - } - rowId={(row) => (row.variant === 'parent' ? territoryAnchorId(row.region) : undefined)} - rowClassName={(row) => - row.variant === 'sub' ? 'bg-muted/30' : 'scroll-mt-6 target:bg-muted/50' - } - empty={} - /> + className="overflow-visible" + containerClassName="overflow-x-auto" + headerClassName="[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-background [&_th]:shadow-[inset_0_-1px_0_var(--border)] [&_th:first-child]:rounded-tl-xl [&_th:last-child]:rounded-tr-xl [&_tr]:border-b-0" + columns={territoryListColumns({ state, regionOptions })} + columnVisibility={columnVisibility} + rows={flattenTerritoryRows(rows)} + rowKey={(row) => (row.variant === 'parent' ? row.region : `${row.parentRegion}-${row.label}`)} + rowId={(row) => (row.variant === 'parent' ? territoryAnchorId(row.region) : undefined)} + rowClassName={(row) => + row.variant === 'sub' ? 'bg-muted/30' : 'scroll-mt-6 target:bg-muted/50' + } + empty={} + /> ) } diff --git a/src/components/campaign/municipality/TerritoryListColumns.tsx b/src/components/campaign/municipality/TerritoryListColumns.tsx index bbf8c200..ee63bf9a 100644 --- a/src/components/campaign/municipality/TerritoryListColumns.tsx +++ b/src/components/campaign/municipality/TerritoryListColumns.tsx @@ -4,7 +4,6 @@ import { TerritorySortableHead } from '@/components/campaign/municipality/Territ import { CampaignTransitionAnchor } from '@/components/campaign/shared/CampaignListPending' import type { CampaignTableColumn } from '@/components/campaign/shared/CampaignTable' import { Badge } from '@/components/ui/Badge' -import type { CampaignColumnPickerColumn } from '@/lib/campaignColumnVisibility' import { formatElectionNumber } from '@/lib/electionFormat' import { formatGoalCoverageDeficitShortLabel, @@ -140,19 +139,6 @@ const captureTooltipContent = (row: TerritoryTableRow) => { ) } -export const territoryListPickerColumns: CampaignColumnPickerColumn[] = [ - { id: 'region', label: territoryListSortLabels.region, mandatory: true }, - { id: 'municipalities', label: territoryListSortLabels.municipalities }, - { id: 'votes2022', label: territoryListSortLabels.votes2022 }, - { id: 'pct', label: territoryListSortLabels.pct }, - { id: 'validVotes2022', label: territoryListSortLabels.validVotes2022 }, - { id: 'estimate2026', label: territoryListSortLabels.estimate2026 }, - { id: 'cobertura', label: territoryListSortLabels.cobertura }, - { id: 'captura', label: territoryListSortLabels.captura }, - { id: 'classe', label: territoryListSortLabels.classe }, - { id: 'coverage', label: territoryListSortLabels.coverage }, -] - export const territoryListColumns = ({ state, regionOptions, diff --git a/src/components/campaign/shared/CampaignColumnPicker.tsx b/src/components/campaign/shared/CampaignColumnPicker.tsx index e59b5b0a..2b160aba 100644 --- a/src/components/campaign/shared/CampaignColumnPicker.tsx +++ b/src/components/campaign/shared/CampaignColumnPicker.tsx @@ -15,16 +15,11 @@ import { parseCampaignHiddenColumns, serializeCampaignHiddenColumns, toggleHiddenColumn, + type CampaignColumnPickerColumn, type CampaignListId, } from '@/lib/campaignColumnVisibility' import { sameIdSet } from '@/lib/sameIdSet' -type CampaignColumnPickerColumn = { - id: string - label: string - mandatory?: boolean -} - type CampaignColumnPickerProps = { listId: CampaignListId /** Every column of the table, hidden ones included — this is the menu. */ diff --git a/src/components/campaign/shared/CampaignTable.tsx b/src/components/campaign/shared/CampaignTable.tsx index b3deb4b1..4d2eebb5 100644 --- a/src/components/campaign/shared/CampaignTable.tsx +++ b/src/components/campaign/shared/CampaignTable.tsx @@ -177,8 +177,7 @@ export const CampaignTable = ({ const visibleColumns = resolveVisibleColumns(columns, columnVisibility?.hiddenColumnIds) return ( - <> -
+
{caption ? {caption} : null} @@ -229,7 +228,6 @@ export const CampaignTable = ({ ))}
-
- + ) } From 4c6cad9f416c013151555c7d7166915af885ebb9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:09:14 +0000 Subject: [PATCH 3/6] =?UTF-8?q?B137=20=E2=80=94=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../colunas-ao-lado-omnibox-listas-impl.md | 1 + .../campanha/(app)/apoiadores/page.tsx | 7 +- .../campanha/(app)/demandas/page.tsx | 2 +- .../campanha/(app)/liderancas/page.tsx | 2 +- .../campanha/(app)/organizacoes/page.tsx | 2 +- .../campanha/(app)/territorios/page.tsx | 4 +- .../campaign/shared/CampaignTable.tsx | 98 +++++++++---------- 7 files changed, 60 insertions(+), 56 deletions(-) diff --git a/docs/plans/colunas-ao-lado-omnibox-listas-impl.md b/docs/plans/colunas-ao-lado-omnibox-listas-impl.md index a8480ea0..b6d811cb 100644 --- a/docs/plans/colunas-ao-lado-omnibox-listas-impl.md +++ b/docs/plans/colunas-ao-lado-omnibox-listas-impl.md @@ -15,6 +15,7 @@ Appetite restante: herdado (~0,5–1 dia eng) ## Abordagem recomendada **Opções consideradas:** + - A) Manter picker em `CampaignTable` com CSS `absolute` ao lado da omnibox — rejeitada: DOM distinto, frágil entre listas com/sem cards mobile. - B) Slot `trailing` nos `*Filters` + remover picker de `CampaignTable` — **recomendada**: precedente B127 (`SaveMunicipalityFilterControl`), boundary de pending intacto. - C) Novo shell `CampaignListToolbar` — rejeitada: twin desnecessário. diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx b/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx index 7080f740..e8d8ec62 100644 --- a/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx +++ b/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx @@ -8,12 +8,15 @@ import Link from 'next/link' import { redirect } from 'next/navigation' import { getPayload } from 'payload' -import { CampaignListEmptyState } from '@/components/campaign/shared/CampaignListEmptyState' import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' +import { CampaignListEmptyState } from '@/components/campaign/shared/CampaignListEmptyState' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { SupporterFilters } from '@/components/campaign/supporter/SupporterFilters' -import { SupporterList, supporterPickerColumns } from '@/components/campaign/supporter/SupporterList' +import { + SupporterList, + supporterPickerColumns, +} from '@/components/campaign/supporter/SupporterList' import { SupporterListOverview } from '@/components/campaign/supporter/SupporterListOverview' import { Button } from '@/components/ui/button' import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' diff --git a/src/app/(campaign)/campanha/(app)/demandas/page.tsx b/src/app/(campaign)/campanha/(app)/demandas/page.tsx index 9870033d..5f8fbc9d 100644 --- a/src/app/(campaign)/campanha/(app)/demandas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/demandas/page.tsx @@ -15,8 +15,8 @@ import { CampaignTable, type CampaignTableColumn } from '@/components/campaign/s import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' -import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' +import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { campaignDemandKindLabels, campaignDemandStatusLabels, diff --git a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx index 86a39b18..507332ce 100644 --- a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx @@ -6,12 +6,12 @@ import { getPayload } from 'payload' import { LeadershipInviteRowAction } from '@/components/campaign/invite/LeadershipInviteRowAction' import { LeadershipFilters } from '@/components/campaign/leadership/LeadershipFilters' -import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { LeadershipListSupportStatusControl } from '@/components/campaign/leadership/LeadershipListSupportStatusControl' import { LeadershipFilterHead, LeadershipSortableHead, } from '@/components/campaign/leadership/LeadershipSortableHead' +import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignCopyableCell } from '@/components/campaign/shared/CampaignCopyableCell' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { diff --git a/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx b/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx index f851d3f4..d66671bf 100644 --- a/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx +++ b/src/app/(campaign)/campanha/(app)/organizacoes/page.tsx @@ -15,8 +15,8 @@ import { CampaignTable, type CampaignTableColumn } from '@/components/campaign/s import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' -import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' +import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { organizationKindLabels } from '@/lib/schemas/organization' import { readCampaignColumnVisibility } from '@/utilities/campaignColumnVisibilityCookie' import { requireCampaignPageActor } from '@/utilities/campaignPageActor' diff --git a/src/app/(campaign)/campanha/(app)/territorios/page.tsx b/src/app/(campaign)/campanha/(app)/territorios/page.tsx index ba9cbc67..9cc82bbe 100644 --- a/src/app/(campaign)/campanha/(app)/territorios/page.tsx +++ b/src/app/(campaign)/campanha/(app)/territorios/page.tsx @@ -3,8 +3,8 @@ import { redirect } from 'next/navigation' import { getPayload } from 'payload' import { TerritoryFilters } from '@/components/campaign/municipality/TerritoryFilters' -import { territoryListColumns } from '@/components/campaign/municipality/TerritoryListColumns' import { TerritoryList } from '@/components/campaign/municipality/TerritoryList' +import { territoryListColumns } from '@/components/campaign/municipality/TerritoryListColumns' import { CampaignColumnPickerTrailing } from '@/components/campaign/shared/CampaignColumnPickerTrailing' import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter' import { @@ -12,8 +12,8 @@ import { CampaignListResults, } from '@/components/campaign/shared/CampaignListPending' import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' -import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { toCampaignColumnPickerColumns } from '@/lib/campaignColumnVisibility' +import { campaignPageMetadataFromCatalog } from '@/lib/campaignPageChrome' import { readCampaignColumnVisibility } from '@/utilities/campaignColumnVisibilityCookie' import { requireCampaignPageActor } from '@/utilities/campaignPageActor' import { loadTerritoryOverview } from '@/utilities/territory/loadTerritoryOverview' diff --git a/src/components/campaign/shared/CampaignTable.tsx b/src/components/campaign/shared/CampaignTable.tsx index 4d2eebb5..560430de 100644 --- a/src/components/campaign/shared/CampaignTable.tsx +++ b/src/components/campaign/shared/CampaignTable.tsx @@ -178,56 +178,56 @@ export const CampaignTable = ({ return (
- - {caption ? {caption} : null} - - - {visibleColumns.map((column) => ( - - {column.head ?? {column.label}} - - ))} - - - - {rows.length === 0 && empty ? ( - - - {empty} - - - ) : null} - {rows.map((row) => ( - - {visibleColumns.map((column) => { - const cell = column.cell(row) - const tooltip = column.cellTooltip?.(row) - - return ( - - {tooltip ? ( - {cell} - ) : ( - cell - )} - - ) - })} - +
+ {caption ? {caption} : null} + + + {visibleColumns.map((column) => ( + + {column.head ?? {column.label}} + ))} - -
+ + + + {rows.length === 0 && empty ? ( + + + {empty} + + + ) : null} + {rows.map((row) => ( + + {visibleColumns.map((column) => { + const cell = column.cell(row) + const tooltip = column.cellTooltip?.(row) + + return ( + + {tooltip ? ( + {cell} + ) : ( + cell + )} + + ) + })} + + ))} + +
) } From fd435d54f681e1cd11e40e417dab6c85980e6645 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:29:04 +0000 Subject: [PATCH 4/6] =?UTF-8?q?B137=20=E2=80=94=20remove=20dead=20searchOn?= =?UTF-8?q?lyListOmnibox=20exports=20after=20org=20omnibox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/searchOnlyListOmnibox.ts | 34 +------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/src/lib/searchOnlyListOmnibox.ts b/src/lib/searchOnlyListOmnibox.ts index ef35f92b..23db977e 100644 --- a/src/lib/searchOnlyListOmnibox.ts +++ b/src/lib/searchOnlyListOmnibox.ts @@ -2,11 +2,7 @@ * Degenerate omnibox for lists that only expose free-text search (B128: * organizações). Pure / client-safe. */ -import { - filterOmniboxSuggestionSeeds, - type CampaignListOmniboxChip, - type CampaignListOmniboxSuggestion, -} from '@/lib/campaignListOmnibox' +import { type CampaignListOmniboxChip } from '@/lib/campaignListOmnibox' export type SearchOnlyListState = { q?: string @@ -23,9 +19,6 @@ export const buildSearchOnlyOmniboxChips = ( state: SearchOnlyListState, ): CampaignListOmniboxChip[] => (state.q ? [{ id: 'q', label: chipLabel('Busca', state.q) }] : []) -export const buildSearchOnlyOmniboxSuggestions = (query: string): CampaignListOmniboxSuggestion[] => - filterOmniboxSuggestionSeeds([], query) - export const applySearchOnlyOmniboxSuggestion = ({ state, suggestionId, @@ -41,28 +34,3 @@ export const applySearchOnlyOmniboxSuggestion = ( } return { kind: 'url', state } } - -export const removeSearchOnlyOmniboxChip = ({ - state, - chipId, - withPageReset, -}: { - state: T - chipId: string - withPageReset: (next: T) => T -}): SearchOnlyOmniboxAction => { - if (chipId === 'q') { - return { kind: 'url', state: withPageReset({ ...state, q: undefined }) } - } - return { kind: 'url', state } -} - -export const clearSearchOnlyOmnibox = ({ - cleared, -}: { - state: T - cleared: T -}): SearchOnlyOmniboxAction => ({ - kind: 'clear', - state: cleared, -}) From d25d719ed9d5b580b46614cc06014089d74b06c8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:50:26 +0000 Subject: [PATCH 5/6] =?UTF-8?q?B137=20=E2=80=94=20retrigger=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6c6a742cb4a355984fe436e9a22e55ba5f65a502 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 3 Aug 2026 00:06:10 +0000 Subject: [PATCH 6/6] =?UTF-8?q?B137=20=E2=80=94=20restore=20searchOnlyList?= =?UTF-8?q?Omnibox=20from=20main=20(CI=20merge=20ref)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/searchOnlyListOmnibox.ts | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/lib/searchOnlyListOmnibox.ts b/src/lib/searchOnlyListOmnibox.ts index 23db977e..ef35f92b 100644 --- a/src/lib/searchOnlyListOmnibox.ts +++ b/src/lib/searchOnlyListOmnibox.ts @@ -2,7 +2,11 @@ * Degenerate omnibox for lists that only expose free-text search (B128: * organizações). Pure / client-safe. */ -import { type CampaignListOmniboxChip } from '@/lib/campaignListOmnibox' +import { + filterOmniboxSuggestionSeeds, + type CampaignListOmniboxChip, + type CampaignListOmniboxSuggestion, +} from '@/lib/campaignListOmnibox' export type SearchOnlyListState = { q?: string @@ -19,6 +23,9 @@ export const buildSearchOnlyOmniboxChips = ( state: SearchOnlyListState, ): CampaignListOmniboxChip[] => (state.q ? [{ id: 'q', label: chipLabel('Busca', state.q) }] : []) +export const buildSearchOnlyOmniboxSuggestions = (query: string): CampaignListOmniboxSuggestion[] => + filterOmniboxSuggestionSeeds([], query) + export const applySearchOnlyOmniboxSuggestion = ({ state, suggestionId, @@ -34,3 +41,28 @@ export const applySearchOnlyOmniboxSuggestion = ( } return { kind: 'url', state } } + +export const removeSearchOnlyOmniboxChip = ({ + state, + chipId, + withPageReset, +}: { + state: T + chipId: string + withPageReset: (next: T) => T +}): SearchOnlyOmniboxAction => { + if (chipId === 'q') { + return { kind: 'url', state: withPageReset({ ...state, q: undefined }) } + } + return { kind: 'url', state } +} + +export const clearSearchOnlyOmnibox = ({ + cleared, +}: { + state: T + cleared: T +}): SearchOnlyOmniboxAction => ({ + kind: 'clear', + state: cleared, +})