mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
feat(campanha): B133 — listas sem subtítulo de prosa + filtro salvo no header
- Remove prose subtitles from list routes, Quadro and create/edit chrome - Keep subtitles on Organizações, Conceitos, Perfil, Contatos (out of scope) - MunicipalityListPageChrome: saved filter name as header subtitle when URL matches - Edit pages: title-only chrome override - Impl plan: docs/plans/listas-sem-subtitulo-prosa-impl.md Closes #283 Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
62171f5127
commit
f89fc4b81f
7 changed files with 160 additions and 92 deletions
67
docs/plans/listas-sem-subtitulo-prosa-impl.md
Normal file
67
docs/plans/listas-sem-subtitulo-prosa-impl.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Impl: Listas `/campanha` — subtítulos curtos (sem prosa; filtro salvo em Municípios)
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-08-02
|
||||
Issue: #283
|
||||
Intenção: docs/plans/listas-sem-subtitulo-prosa.md
|
||||
Appetite restante: ~0,5 dia eng (herdado)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** Header das listas citadas, Quadro e create/edit estáticos mostram só o título — sem parágrafo de prosa. Em `/campanha/municipios`, quando a URL casa com um filtro salvo (B18), o subtítulo do header passa a ser o **nome** do filtro; a label de resumo duplicada some da barra.
|
||||
- **O que NÃO negociar:** Organizações, Conceitos, Perfil, Contatos mantêm subtítulos atuais; chips/omnibox como editor de filtro ficam; leader lockdown intocado; não puxar serializador pesado de URL para o layout `(app)`.
|
||||
- **O que reavaliar:** A label de resumo (`formatMunicipalityActiveFiltersSummary`) ao lado da busca **já foi removida** no B127 (omnibox). O aceite de “sumir a label duplicada” está satisfeito — só garantir que não reintroduzimos resumo visível na barra.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[campaignPageChrome pathRules] -->|sectionOnly| B[Header título só]
|
||||
C[MunicipalityListPageChrome] -->|SetCampaignPageChrome| D[Subtítulo = nome filtro salvo]
|
||||
E[Edit pages] -->|remove subtitle override| B
|
||||
```
|
||||
|
||||
**Opções consideradas:**
|
||||
- A) Apagar `subtitle` no catálogo e manter `pathRules` com `resolveCatalogEntry` — catálogo ainda carrega prosa morta.
|
||||
- B) **`pathRules` → `sectionOnly(title)`** para rotas no escopo; catálogo limpo nas entradas afetadas; metadata já usa só `title`.
|
||||
- C) `SetCampaignPageChrome` em cada rota create/edit — duplica o catálogo.
|
||||
|
||||
**Recomendação:** B — o dono (`campaignPageChrome.ts`) já resolve chrome por pathname; overrides pontuais só onde o estado é client-side (filtro salvo) ou dinâmico (editar com nome da entidade).
|
||||
|
||||
**Rejeitadas:** C (twins); A (prosa morta no catálogo).
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`campaignPageChrome.ts`**: `sectionOnly` nas listas citadas, Quadro, create/edit estáticos; remove prosa das entradas de catálogo afetadas; mantém subtítulos em org/conceitos/perfil/contatos.
|
||||
- **`MunicipalityListPageChrome.tsx`** (novo): client; `useMunicipalitySavedFilters` + `isSameListHref` (mesmo contrato da sidebar B18); `SetCampaignPageChrome` só quando há match.
|
||||
- **Edit pages** (`municipios/.../editar`, `atividades/.../editar`): chrome/metadata sem `subtitle`.
|
||||
- **Migration:** sem migration.
|
||||
- **Access / Consent:** nenhum.
|
||||
- **UI:** B — encaixe no chrome B123; sem redesign.
|
||||
|
||||
### Dados → forma
|
||||
|
||||
- Não há dados novos — só orientação espacial no header.
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Chrome catálogo + edit** — `campaignPageChrome.ts`, edit pages, testes unitários.
|
||||
2. **Filtro salvo** — `MunicipalityListPageChrome` na página de lista; teste unitário do match.
|
||||
3. **Gates** — `pnpm gate:fast`; push via `pnpm push`.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- Reescrever subtítulos de Organizações/Conceitos/Perfil/Contatos.
|
||||
- Subtítulo para URL filtrada ad hoc (só filtro salvo casado).
|
||||
- Reintroduzir `formatMunicipalityActiveFiltersSummary` visível na barra.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Acoplamento layout ↔ URL municípios:** `isSameListHref` já é client-safe e usado na sidebar — reusar, não importar `buildMunicipalityListHref` no layout.
|
||||
- **Regressão E2E filtros salvos:** sidebar + Renomear intactos; só chrome do header muda.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto
|
||||
- [x] Invariantes AGENTS/engineering-standards
|
||||
- [x] Testes de domínio previstos (unit chrome + saved-filter match)
|
||||
|
|
@ -22,12 +22,9 @@ type EditActivityPageProps = {
|
|||
params: Promise<{ slug: string }>
|
||||
}
|
||||
|
||||
const activityEditSubtitle = 'Atualize os detalhes da atividade sem alterar o título original.'
|
||||
|
||||
export async function generateMetadata({ params }: EditActivityPageProps) {
|
||||
const { slug } = await params
|
||||
if (!slug)
|
||||
return campaignPageMetadata({ title: 'Editar atividade', subtitle: activityEditSubtitle })
|
||||
if (!slug) return campaignPageMetadata({ title: 'Editar atividade' })
|
||||
|
||||
const [user, payload] = await Promise.all([
|
||||
requireCampaignPageActor({ gate: 'staff', redirectTo: '/campanha/atividades' }),
|
||||
|
|
@ -36,9 +33,9 @@ export async function generateMetadata({ params }: EditActivityPageProps) {
|
|||
|
||||
try {
|
||||
const view = await getActivityEditPageData(payload, user, slug)
|
||||
return campaignPageMetadata({ title: `Editar ${view.title}`, subtitle: activityEditSubtitle })
|
||||
return campaignPageMetadata({ title: `Editar ${view.title}` })
|
||||
} catch {
|
||||
return campaignPageMetadata({ title: 'Editar atividade', subtitle: activityEditSubtitle })
|
||||
return campaignPageMetadata({ title: 'Editar atividade' })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,9 +60,7 @@ export default async function EditActivityPage({ params }: EditActivityPageProps
|
|||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-4xl flex-col gap-6">
|
||||
<SetCampaignPageChrome
|
||||
chrome={{ title: `Editar ${view.title}`, subtitle: activityEditSubtitle }}
|
||||
/>
|
||||
<SetCampaignPageChrome chrome={{ title: `Editar ${view.title}` }} />
|
||||
<ActivityForm
|
||||
action={updateActivityFormAction}
|
||||
activity={view}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,6 @@ type MunicipalityEditPageProps = {
|
|||
params: Promise<{ slug: string }>
|
||||
}
|
||||
|
||||
const municipalityEditSubtitle =
|
||||
'A geografia do município é pré-definida e não pode ser alterada. Aqui você edita metas, votos estimados, inteligência e tendência política.'
|
||||
|
||||
export async function generateMetadata({ params }: MunicipalityEditPageProps) {
|
||||
const { slug } = await params
|
||||
const payload = await getPayload({ config })
|
||||
|
|
@ -43,12 +40,9 @@ export async function generateMetadata({ params }: MunicipalityEditPageProps) {
|
|||
try {
|
||||
const context = await resolveAccessibleMunicipalityContext(payload, user, slug)
|
||||
const view = await getMunicipalityDetailViewModel(payload, context, user)
|
||||
return campaignPageMetadata({
|
||||
title: `Editar ${view.name}`,
|
||||
subtitle: municipalityEditSubtitle,
|
||||
})
|
||||
return campaignPageMetadata({ title: `Editar ${view.name}` })
|
||||
} catch {
|
||||
return campaignPageMetadata({ title: 'Editar município', subtitle: municipalityEditSubtitle })
|
||||
return campaignPageMetadata({ title: 'Editar município' })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +70,7 @@ export default async function MunicipalityEditPage({ params }: MunicipalityEditP
|
|||
|
||||
return (
|
||||
<CampaignPageShell>
|
||||
<SetCampaignPageChrome
|
||||
chrome={{ title: `Editar ${view.name}`, subtitle: municipalityEditSubtitle }}
|
||||
/>
|
||||
<SetCampaignPageChrome chrome={{ title: `Editar ${view.name}` }} />
|
||||
<Button asChild variant="ghost" className="min-h-11 self-start">
|
||||
<Link href={`/campanha/municipios/${view.slug}`}>
|
||||
<ArrowLeftIcon data-icon="inline-start" aria-hidden="true" />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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 { MunicipalityListPageChrome } from '@/components/campaign/municipality/MunicipalityListPageChrome'
|
||||
import { MunicipalityList } from '@/components/campaign/municipality/MunicipalityList'
|
||||
import { CampaignListFooter } from '@/components/campaign/shared/CampaignListFooter'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
|
|
@ -150,6 +151,7 @@ export default async function MunicipalitiesPage({ searchParams }: Municipalitie
|
|||
|
||||
return (
|
||||
<CampaignPageShell>
|
||||
<MunicipalityListPageChrome />
|
||||
{main}
|
||||
{listVisitLabel ? (
|
||||
<RecentVisitTracker
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
'use client'
|
||||
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useMunicipalitySavedFilters } from '@/components/campaign/shared/useMunicipalitySavedFilters'
|
||||
import { SetCampaignPageChrome } from '@/components/campaign/shell/CampaignPageChromeContext'
|
||||
import { campaignPageChromeCatalog } from '@/lib/campaignPageChrome'
|
||||
import { isSameListHref } from '@/lib/listQueryMatch'
|
||||
|
||||
/** A page is a position inside a recorte, not part of it (B18). */
|
||||
const IGNORED_PARAMS = ['page']
|
||||
|
||||
/**
|
||||
* When the current URL matches a saved municipality filter, the filter name
|
||||
* becomes the page subtitle (B133) — the bar keeps chips as the editable recorte.
|
||||
*/
|
||||
export const MunicipalityListPageChrome = (): null => {
|
||||
const savedFilters = useMunicipalitySavedFilters()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const activeFilter = useMemo(() => {
|
||||
const query = searchParams.toString()
|
||||
const currentHref = query ? `${pathname}?${query}` : pathname
|
||||
return savedFilters.find((entry) =>
|
||||
isSameListHref(currentHref, entry.href, IGNORED_PARAMS),
|
||||
)
|
||||
}, [pathname, searchParams, savedFilters])
|
||||
|
||||
if (!activeFilter) return null
|
||||
|
||||
return (
|
||||
<SetCampaignPageChrome
|
||||
chrome={{
|
||||
title: campaignPageChromeCatalog.municipios.title,
|
||||
subtitle: activeFilter.name,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -17,87 +17,56 @@ const normalizePathname = (pathname: string): string => {
|
|||
return pathname
|
||||
}
|
||||
|
||||
const quadroSubtitle = (role: CampaignRole): string =>
|
||||
role === 'advisor'
|
||||
? 'Quadro dos municípios sob sua assessoria.'
|
||||
: 'Quadro geral da campanha por município.'
|
||||
|
||||
/** Client-safe vocabulary for shell chrome and tab titles (B123). */
|
||||
export const campaignPageChromeCatalog = {
|
||||
home: null,
|
||||
quadro: {
|
||||
title: 'Quadro',
|
||||
subtitle: quadroSubtitle,
|
||||
},
|
||||
municipios: {
|
||||
title: 'Municípios',
|
||||
subtitle:
|
||||
'Os 435 municípios da campanha: um por município da Bahia — em Salvador, uma zona eleitoral cada.',
|
||||
},
|
||||
territorios: {
|
||||
title: 'Territórios de Identidade',
|
||||
subtitle:
|
||||
'Compare a concentração histórica e a cobertura de assessoria das regiões da Bahia. Abra um território para ver seus municípios.',
|
||||
},
|
||||
liderancas: {
|
||||
title: 'Lideranças',
|
||||
subtitle:
|
||||
'Uma ficha por pessoa — cada liderança pode atuar em vários municípios e organizações.',
|
||||
},
|
||||
liderancasNova: {
|
||||
title: 'Nova liderança',
|
||||
subtitle:
|
||||
'O contato é reaproveitado pelo celular quando já existe. Registre o consentimento antes de inserir dados reais.',
|
||||
},
|
||||
dobradinhas: {
|
||||
title: 'Dobradinhas',
|
||||
subtitle:
|
||||
'Deputados estaduais com quem a campanha dobra — vincule lideranças e municípios direto na lista.',
|
||||
},
|
||||
dobradinhasNova: {
|
||||
title: 'Nova dobradinha',
|
||||
subtitle:
|
||||
'Cadastre deputados estaduais com quem a campanha dobra. Vincule a municípios e lideranças nas fichas correspondentes.',
|
||||
},
|
||||
atividades: {
|
||||
title: 'Atividades',
|
||||
subtitle: 'Organize caminhadas, comícios, panfletagens e demais ações de campanha.',
|
||||
},
|
||||
atividadesNova: {
|
||||
title: 'Nova atividade',
|
||||
subtitle: 'Defina a ação, quando e onde ela acontece e quem responde por ela.',
|
||||
},
|
||||
atividadesGiros: {
|
||||
title: 'Planejar giro',
|
||||
subtitle:
|
||||
'Um giro por território: o ato onde o voto comprometido já está, as paradas com rede para receber, e uma semente de expansão para o giro não repetir só a base. A proposta é sugestão — você edita antes de gerar.',
|
||||
},
|
||||
demandas: {
|
||||
title: 'Demandas',
|
||||
subtitle:
|
||||
'Necessidades da campanha abertas pelas lideranças, revisadas pela assessoria e — quando preciso — decididas pelo Coordenador Geral.',
|
||||
},
|
||||
demandasNova: {
|
||||
title: 'Nova demanda',
|
||||
subtitle:
|
||||
'Descreva a necessidade (material, transporte, espaço, apoio…) e o município. A assessoria revisa e responde por aqui.',
|
||||
},
|
||||
apoiadores: {
|
||||
title: 'Apoiadores',
|
||||
subtitle: 'Base nominal de apoio com intenção de voto e vínculo opcional a municípios.',
|
||||
},
|
||||
apoiadoresNovo: {
|
||||
title: 'Novo apoiador',
|
||||
subtitle: 'Cadastre um apoiador com telefone obrigatório e consentimento LGPD.',
|
||||
},
|
||||
apoiadoresImportar: {
|
||||
title: 'Importar apoiadores via CSV',
|
||||
subtitle: 'Envie uma planilha, confira a prévia e confirme a importação em lote.',
|
||||
},
|
||||
assessores: {
|
||||
title: 'Assessores',
|
||||
subtitle:
|
||||
'Consulte na tabela; ative Editar para alterar campos e carteira. Nome abre a ficha; e-mail/celular copiam; município abre o município.',
|
||||
},
|
||||
organizacoes: {
|
||||
title: 'Organizações',
|
||||
|
|
@ -122,25 +91,20 @@ export const campaignPageChromeCatalog = {
|
|||
title: 'Meu perfil',
|
||||
subtitle: 'Gerencie sua foto, sua senha e a entrada por biometria.',
|
||||
},
|
||||
} as const satisfies Record<
|
||||
string,
|
||||
CampaignPageChrome | { title: string; subtitle: (role: CampaignRole) => string } | null
|
||||
>
|
||||
} as const satisfies Record<string, CampaignPageChrome | null>
|
||||
|
||||
const resolveCatalogEntry = (
|
||||
entry: (typeof campaignPageChromeCatalog)[keyof typeof campaignPageChromeCatalog],
|
||||
role: CampaignRole,
|
||||
): CampaignPageChrome | null => {
|
||||
if (!entry) return null
|
||||
const subtitle = typeof entry.subtitle === 'function' ? entry.subtitle(role) : entry.subtitle
|
||||
return { title: entry.title, subtitle }
|
||||
return { title: entry.title, subtitle: entry.subtitle }
|
||||
}
|
||||
|
||||
const sectionOnly = (title: string): CampaignPageChrome => ({ title })
|
||||
|
||||
type PathRule = {
|
||||
match: (pathname: string) => boolean
|
||||
resolve: (role: CampaignRole) => CampaignPageChrome | null
|
||||
resolve: () => CampaignPageChrome | null
|
||||
}
|
||||
|
||||
const pathRules: PathRule[] = [
|
||||
|
|
@ -150,11 +114,11 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/quadro',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.quadro, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.quadro),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/municipios',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.municipios, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.municipios),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/municipios\/[^/]+\/editar$/.test(pathname),
|
||||
|
|
@ -166,11 +130,11 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/territorios',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.territorios, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.territorios),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/liderancas/nova',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.liderancasNova, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.liderancasNova),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/liderancas\/[^/]+$/.test(pathname),
|
||||
|
|
@ -178,11 +142,11 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/liderancas',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.liderancas, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.liderancas),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/dobradinhas/nova',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.dobradinhasNova, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.dobradinhasNova),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/dobradinhas\/[^/]+$/.test(pathname),
|
||||
|
|
@ -190,15 +154,15 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/dobradinhas',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.dobradinhas, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.dobradinhas),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/atividades/nova',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.atividadesNova, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.atividadesNova),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/atividades/giros',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.atividadesGiros, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.atividadesGiros),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/atividades\/[^/]+\/editar$/.test(pathname),
|
||||
|
|
@ -210,11 +174,11 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/atividades',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.atividades, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.atividades),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/demandas/nova',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.demandasNova, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.demandasNova),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/demandas\/[^/]+$/.test(pathname),
|
||||
|
|
@ -222,15 +186,15 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/demandas',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.demandas, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.demandas),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/apoiadores/novo',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.apoiadoresNovo, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.apoiadoresNovo),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/apoiadores/importar',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.apoiadoresImportar, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.apoiadoresImportar),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/apoiadores\/[^/]+$/.test(pathname),
|
||||
|
|
@ -238,7 +202,7 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/apoiadores',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.apoiadores, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.apoiadores),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/assessores\/[^/]+$/.test(pathname),
|
||||
|
|
@ -246,11 +210,11 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/assessores',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.assessores, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.assessores),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/organizacoes/nova',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.organizacoesNova, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.organizacoesNova),
|
||||
},
|
||||
{
|
||||
match: (pathname) => /^\/campanha\/organizacoes\/[^/]+$/.test(pathname),
|
||||
|
|
@ -258,30 +222,30 @@ const pathRules: PathRule[] = [
|
|||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/organizacoes',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.organizacoes, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.organizacoes),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/conceitos',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.conceitos, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.conceitos),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === LEADER_CONTACTS_HOME,
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.contatos, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.contatos),
|
||||
},
|
||||
{
|
||||
match: (pathname) => pathname === '/campanha/perfil',
|
||||
resolve: (role) => resolveCatalogEntry(campaignPageChromeCatalog.perfil, role),
|
||||
resolve: () => resolveCatalogEntry(campaignPageChromeCatalog.perfil),
|
||||
},
|
||||
]
|
||||
|
||||
export const resolveCampaignPageChrome = (
|
||||
pathname: string,
|
||||
role: CampaignRole,
|
||||
_role: CampaignRole,
|
||||
): CampaignPageChrome | null => {
|
||||
const normalized = normalizePathname(pathname)
|
||||
for (const rule of pathRules) {
|
||||
if (rule.match(normalized)) {
|
||||
return rule.resolve(role)
|
||||
return rule.resolve()
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
|
@ -301,5 +265,4 @@ export const campaignPageMetadata = (chrome: CampaignPageChrome | null): Metadat
|
|||
|
||||
export const campaignPageMetadataFromCatalog = (
|
||||
key: keyof typeof campaignPageChromeCatalog,
|
||||
role: CampaignRole = 'coordinator',
|
||||
): Metadata => campaignPageMetadata(resolveCatalogEntry(campaignPageChromeCatalog[key], role))
|
||||
): Metadata => campaignPageMetadata(resolveCatalogEntry(campaignPageChromeCatalog[key]))
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ describe('campaignPageChrome', () => {
|
|||
expect(resolveCampaignPageChrome('/campanha/', 'advisor')).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves section list chrome', () => {
|
||||
it('resolves section list chrome without prose subtitle', () => {
|
||||
expect(resolveCampaignPageChrome('/campanha/municipios', 'coordinator')).toEqual({
|
||||
title: 'Municípios',
|
||||
subtitle:
|
||||
'Os 435 municípios da campanha: um por município da Bahia — em Salvador, uma zona eleitoral cada.',
|
||||
})
|
||||
expect(resolveCampaignPageChrome('/campanha/liderancas', 'advisor')).toEqual({
|
||||
title: 'Lideranças',
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -26,12 +27,19 @@ describe('campaignPageChrome', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('uses role-based subtitle for quadro', () => {
|
||||
expect(resolveCampaignPageChrome('/campanha/quadro', 'advisor')?.subtitle).toContain(
|
||||
'sua assessoria',
|
||||
it('resolves quadro without role-based prose subtitle', () => {
|
||||
expect(resolveCampaignPageChrome('/campanha/quadro', 'advisor')).toEqual({ title: 'Quadro' })
|
||||
expect(resolveCampaignPageChrome('/campanha/quadro', 'coordinator')).toEqual({
|
||||
title: 'Quadro',
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps prose subtitle on out-of-scope sections', () => {
|
||||
expect(resolveCampaignPageChrome('/campanha/conceitos', 'coordinator')?.subtitle).toContain(
|
||||
'cada número',
|
||||
)
|
||||
expect(resolveCampaignPageChrome('/campanha/quadro', 'coordinator')?.subtitle).toContain(
|
||||
'geral',
|
||||
expect(resolveCampaignPageChrome('/campanha/perfil', 'coordinator')?.subtitle).toContain(
|
||||
'biometria',
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue