mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
B80 — Ações rápidas em Municípios (lista + detalhe A1–A5)
Registra catálogo staff no drawer B79 para /campanha/municipios e /campanha/municipios/[slug]: lista espelha Início (sem prefill); detalhe prefill ?municipio= nos wizards A1–A5 e omite Ver esquecidos. Closes #16 Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
a638561ce9
commit
6030d42e2f
7 changed files with 198 additions and 22 deletions
|
|
@ -7,10 +7,10 @@ import { CampaignHomeLayout } from '@/components/campaign/dashboard/CampaignHome
|
|||
import { CampaignHomeStaffChrome } from '@/components/campaign/dashboard/CampaignHomeStaffChrome'
|
||||
import { CampaignHomeSummary } from '@/components/campaign/dashboard/CampaignHomeSummary'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
import { UNCOVERED_MUNICIPALITIES_LIST_HREF } from '@/lib/campaignHomeActions'
|
||||
import { isStaffCampaignRole } from '@/lib/campaignRoles'
|
||||
import { loadCampaignHomeSummary } from '@/utilities/campaignDashboardData'
|
||||
import { requireCampaignPageActor } from '@/utilities/campaignPageActor'
|
||||
import { buildMunicipalityListHref } from '@/utilities/municipality/municipalityListUrl'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
|
@ -19,9 +19,7 @@ export default async function CampaignHomePage() {
|
|||
|
||||
const staff = isStaffCampaignRole(user.role)
|
||||
|
||||
const uncoveredMunicipalitiesHref = staff
|
||||
? buildMunicipalityListHref({ page: 1, coverage: 'sem_assessor', sort: 'votos' }, 1)
|
||||
: undefined
|
||||
const uncoveredMunicipalitiesHref = staff ? UNCOVERED_MUNICIPALITIES_LIST_HREF : undefined
|
||||
|
||||
const actions = (
|
||||
<CampaignHomeActions
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ import {
|
|||
} from 'lucide-react'
|
||||
|
||||
import {
|
||||
CAMPAIGN_WIZARD_ACTION_SLUGS,
|
||||
campaignActionEntryHref,
|
||||
isCampaignWizardActionId,
|
||||
wizardActionHref,
|
||||
type CampaignWizardActionId,
|
||||
} from '@/lib/campaignActionRoutes'
|
||||
import { LEADER_CONTACTS_HOME } from '@/lib/campaignPaths'
|
||||
|
|
@ -127,7 +129,12 @@ export type ResolvedCampaignHomeAction = CampaignHomeAction & {
|
|||
export const UNCOVERED_MUNICIPALITIES_LIST_HREF =
|
||||
'/campanha/municipios?coverage=sem_assessor&sort=votos' as const
|
||||
|
||||
/** Staff Início catalog with hrefs — shared by quick-action registries (B81+). */
|
||||
export type HomeActionButtonPropsOptions = {
|
||||
uncoveredMunicipalitiesHref?: string
|
||||
municipalitySlug?: string
|
||||
}
|
||||
|
||||
/** Staff Início catalog with hrefs — shared by quick-action registries (B80+). */
|
||||
const staffHomeQuickActionsByRole = new Map<CampaignRole, readonly ResolvedCampaignHomeAction[]>()
|
||||
|
||||
export const resolveStaffHomeQuickActions = (
|
||||
|
|
@ -136,29 +143,34 @@ export const resolveStaffHomeQuickActions = (
|
|||
if (!isStaffCampaignRole(role)) return []
|
||||
const cached = staffHomeQuickActionsByRole.get(role)
|
||||
if (cached) return cached
|
||||
const actions = toHomeActionButtonProps(
|
||||
homeActionsForRole(role),
|
||||
UNCOVERED_MUNICIPALITIES_LIST_HREF,
|
||||
)
|
||||
const actions = toHomeActionButtonProps(homeActionsForRole(role), {
|
||||
uncoveredMunicipalitiesHref: UNCOVERED_MUNICIPALITIES_LIST_HREF,
|
||||
})
|
||||
staffHomeQuickActionsByRole.set(role, actions)
|
||||
return actions
|
||||
}
|
||||
|
||||
export const toHomeActionButtonProps = (
|
||||
actions: readonly CampaignHomeAction[],
|
||||
uncoveredMunicipalitiesHref?: string,
|
||||
): ResolvedCampaignHomeAction[] =>
|
||||
actions.map((action) => {
|
||||
options?: string | HomeActionButtonPropsOptions,
|
||||
): ResolvedCampaignHomeAction[] => {
|
||||
const resolved =
|
||||
typeof options === 'string' ? { uncoveredMunicipalitiesHref: options } : (options ?? {})
|
||||
|
||||
return actions.map((action) => {
|
||||
let href: string | undefined
|
||||
if (action.id === 'my-contacts') {
|
||||
href = LEADER_CONTACTS_HOME
|
||||
} else if (action.id === 'uncovered-municipalities') {
|
||||
href = uncoveredMunicipalitiesHref
|
||||
href = resolved.uncoveredMunicipalitiesHref
|
||||
} else if (isCampaignWizardActionId(action.id)) {
|
||||
href = campaignActionEntryHref(action.id)
|
||||
href = resolved.municipalitySlug
|
||||
? wizardActionHref(CAMPAIGN_WIZARD_ACTION_SLUGS[action.id], resolved.municipalitySlug)
|
||||
: campaignActionEntryHref(action.id)
|
||||
}
|
||||
return {
|
||||
...action,
|
||||
...(href ? { href } : {}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
54
src/lib/campaignMunicipalityQuickActions.ts
Normal file
54
src/lib/campaignMunicipalityQuickActions.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
homeActionsForRole,
|
||||
resolveStaffHomeQuickActions,
|
||||
toHomeActionButtonProps,
|
||||
type ResolvedCampaignHomeAction,
|
||||
} from '@/lib/campaignHomeActions'
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import type { CampaignRole } from '@/lib/campaignRoles'
|
||||
import { isStaffCampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
const MUNICIPALITIES_LIST_PATH = '/campanha/municipios' as const
|
||||
|
||||
const municipalityDetailPathPattern = /^\/campanha\/municipios\/([^/]+)(?:\/|$)/
|
||||
|
||||
export const isMunicipalitiesListPath = (pathname: string): boolean =>
|
||||
pathname === MUNICIPALITIES_LIST_PATH || pathname === `${MUNICIPALITIES_LIST_PATH}/`
|
||||
|
||||
export const parseMunicipalityDetailSlug = (pathname: string): string | undefined => {
|
||||
const match = pathname.match(municipalityDetailPathPattern)
|
||||
return match?.[1]
|
||||
}
|
||||
|
||||
export const resolveMunicipalityListQuickActions = (
|
||||
role: CampaignRole,
|
||||
): readonly ResolvedCampaignHomeAction[] => resolveStaffHomeQuickActions(role)
|
||||
|
||||
export const resolveMunicipalityDetailQuickActions = (
|
||||
role: CampaignRole,
|
||||
municipalitySlug: string,
|
||||
): readonly ResolvedCampaignHomeAction[] => {
|
||||
if (!isStaffCampaignRole(role)) return []
|
||||
|
||||
const actions = homeActionsForRole(role).filter(
|
||||
(action) => action.id !== 'uncovered-municipalities',
|
||||
)
|
||||
return toHomeActionButtonProps(actions, { municipalitySlug })
|
||||
}
|
||||
|
||||
export const resolveMunicipalityQuickActionsForPath = (
|
||||
pathname: string,
|
||||
role: CampaignRole,
|
||||
context: CampaignQuickActionContext,
|
||||
): readonly ResolvedCampaignHomeAction[] => {
|
||||
if (isMunicipalitiesListPath(pathname)) {
|
||||
return resolveMunicipalityListQuickActions(role)
|
||||
}
|
||||
|
||||
const slug = context.municipalitySlug ?? parseMunicipalityDetailSlug(pathname)
|
||||
if (slug) {
|
||||
return resolveMunicipalityDetailQuickActions(role, slug)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
|
@ -2,8 +2,9 @@ import {
|
|||
resolveStaffHomeQuickActions,
|
||||
type ResolvedCampaignHomeAction,
|
||||
} from '@/lib/campaignHomeActions'
|
||||
import { CAMPAIGN_TERRITORIES_HOME } from '@/lib/campaignPaths'
|
||||
import { resolveMunicipalityQuickActionsForPath } from '@/lib/campaignMunicipalityQuickActions'
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import { CAMPAIGN_TERRITORIES_HOME } from '@/lib/campaignPaths'
|
||||
import type { CampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
const isTerritoriesListPath = (pathname: string): boolean =>
|
||||
|
|
@ -12,15 +13,19 @@ const isTerritoriesListPath = (pathname: string): boolean =>
|
|||
/**
|
||||
* Pathname + role + page context → contextual quick actions.
|
||||
* Catalogs per vertical land in B80–B90; the B79 chassis renders whatever
|
||||
* this helper returns (empty until those items register providers).
|
||||
* this helper returns.
|
||||
*/
|
||||
export const resolveQuickActionsForPath = (
|
||||
pathname: string,
|
||||
role: CampaignRole,
|
||||
_context: CampaignQuickActionContext,
|
||||
context: CampaignQuickActionContext,
|
||||
): readonly ResolvedCampaignHomeAction[] => {
|
||||
const municipalityActions = resolveMunicipalityQuickActionsForPath(pathname, role, context)
|
||||
if (municipalityActions.length > 0) return municipalityActions
|
||||
|
||||
if (isTerritoriesListPath(pathname)) {
|
||||
return resolveStaffHomeQuickActions(role)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ describe('toHomeActionButtonProps', () => {
|
|||
const props = toHomeActionButtonProps(homeActionsForRole('coordinator'))
|
||||
expect(props.find((action) => action.id === 'uncovered-municipalities')?.href).toBeUndefined()
|
||||
})
|
||||
|
||||
it('prefills wizard hrefs when municipalitySlug is provided', () => {
|
||||
const props = toHomeActionButtonProps(homeActionsForRole('coordinator'), {
|
||||
municipalitySlug: 'cairu',
|
||||
})
|
||||
expect(props.find((action) => action.id === 'update-votes')?.href).toBe(
|
||||
'/campanha/acoes/atualizar-votos?municipio=cairu',
|
||||
)
|
||||
expect(props.find((action) => action.id === 'uncovered-municipalities')?.href).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('UNCOVERED_MUNICIPALITIES_LIST_HREF', () => {
|
||||
|
|
|
|||
88
tests/unit/campaignMunicipalityQuickActions.unit.spec.ts
Normal file
88
tests/unit/campaignMunicipalityQuickActions.unit.spec.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { CAMPAIGN_WIZARD_ACTION_SLUGS } from '@/lib/campaignActionRoutes'
|
||||
import {
|
||||
isMunicipalitiesListPath,
|
||||
parseMunicipalityDetailSlug,
|
||||
resolveMunicipalityDetailQuickActions,
|
||||
resolveMunicipalityListQuickActions,
|
||||
resolveMunicipalityQuickActionsForPath,
|
||||
} from '@/lib/campaignMunicipalityQuickActions'
|
||||
import { UNCOVERED_MUNICIPALITIES_LIST_HREF } from '@/lib/campaignHomeActions'
|
||||
|
||||
const wizardActionIds = [
|
||||
'update-votes',
|
||||
'register-signal',
|
||||
'change-trend',
|
||||
'update-leadership',
|
||||
'register-demand',
|
||||
] as const
|
||||
|
||||
describe('campaignMunicipalityQuickActions paths', () => {
|
||||
it('detects the municipality list path', () => {
|
||||
expect(isMunicipalitiesListPath('/campanha/municipios')).toBe(true)
|
||||
expect(isMunicipalitiesListPath('/campanha/municipios/')).toBe(true)
|
||||
expect(isMunicipalitiesListPath('/campanha/municipios/cairu')).toBe(false)
|
||||
})
|
||||
|
||||
it('parses municipality detail slugs', () => {
|
||||
expect(parseMunicipalityDetailSlug('/campanha/municipios/cairu')).toBe('cairu')
|
||||
expect(parseMunicipalityDetailSlug('/campanha/municipios/cairu/editar')).toBe('cairu')
|
||||
expect(parseMunicipalityDetailSlug('/campanha/municipios')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveMunicipalityListQuickActions', () => {
|
||||
it('mirrors the Início staff catalog without municipality prefill', () => {
|
||||
const actions = resolveMunicipalityListQuickActions('coordinator')
|
||||
expect(actions.map((action) => action.id)).toEqual([
|
||||
...wizardActionIds,
|
||||
'uncovered-municipalities',
|
||||
])
|
||||
expect(actions.find((action) => action.id === 'update-votes')?.href).toBe(
|
||||
'/campanha/acoes/atualizar-votos',
|
||||
)
|
||||
expect(actions.find((action) => action.id === 'uncovered-municipalities')?.href).toBe(
|
||||
UNCOVERED_MUNICIPALITIES_LIST_HREF,
|
||||
)
|
||||
})
|
||||
|
||||
it('returns nothing for leader', () => {
|
||||
expect(resolveMunicipalityListQuickActions('leader')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveMunicipalityDetailQuickActions', () => {
|
||||
it('prefills wizard hrefs and omits uncovered municipalities', () => {
|
||||
const actions = resolveMunicipalityDetailQuickActions('coordinator', 'cairu')
|
||||
expect(actions.map((action) => action.id)).toEqual([...wizardActionIds])
|
||||
expect(actions.find((action) => action.id === 'update-votes')?.href).toBe(
|
||||
`/campanha/acoes/${CAMPAIGN_WIZARD_ACTION_SLUGS['update-votes']}?municipio=cairu`,
|
||||
)
|
||||
expect(actions.some((action) => action.id === 'uncovered-municipalities')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveMunicipalityQuickActionsForPath', () => {
|
||||
it('routes list and detail paths', () => {
|
||||
const list = resolveMunicipalityQuickActionsForPath('/campanha/municipios', 'advisor', {})
|
||||
expect(list).toHaveLength(6)
|
||||
|
||||
const detail = resolveMunicipalityQuickActionsForPath(
|
||||
'/campanha/municipios/feira-de-santana',
|
||||
'candidate',
|
||||
{},
|
||||
)
|
||||
expect(detail).toHaveLength(5)
|
||||
expect(detail[0]?.href).toContain('municipio=feira-de-santana')
|
||||
})
|
||||
|
||||
it('prefers context slug over pathname', () => {
|
||||
const actions = resolveMunicipalityQuickActionsForPath(
|
||||
'/campanha/municipios/feira-de-santana',
|
||||
'coordinator',
|
||||
{ municipalitySlug: 'cairu' },
|
||||
)
|
||||
expect(actions.find((action) => action.id === 'update-votes')?.href).toContain('municipio=cairu')
|
||||
})
|
||||
})
|
||||
|
|
@ -50,11 +50,20 @@ describe('campaignQuickActionRegistry', () => {
|
|||
const staffActionIds = homeActionsForRole('coordinator').map((action) => action.id)
|
||||
|
||||
it('returns empty catalog for unregistered paths', () => {
|
||||
expect(
|
||||
resolveQuickActionsForPath('/campanha/municipios/foo', 'coordinator', {
|
||||
municipalitySlug: 'foo',
|
||||
}),
|
||||
).toEqual([])
|
||||
expect(resolveQuickActionsForPath('/campanha/apoiadores', 'coordinator', {})).toEqual([])
|
||||
})
|
||||
|
||||
it('delegates municipality list and detail catalogs (B80)', () => {
|
||||
const list = resolveQuickActionsForPath('/campanha/municipios', 'coordinator', {})
|
||||
expect(list).toHaveLength(6)
|
||||
|
||||
const detail = resolveQuickActionsForPath('/campanha/municipios/foo', 'coordinator', {
|
||||
municipalitySlug: 'foo',
|
||||
})
|
||||
expect(detail).toHaveLength(5)
|
||||
expect(detail.find((action) => action.id === 'update-votes')?.href).toBe(
|
||||
'/campanha/acoes/atualizar-votos?municipio=foo',
|
||||
)
|
||||
})
|
||||
|
||||
it('returns staff Início catalog on territorios without municipality prefill (B81)', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue