Merge pull request #82 from fsolla/cursor/pool-i23-0fce1b59-4cb2
B87 — Ações rápidas — Assessores (unrestricted)
This commit is contained in:
commit
5c6ed8cf31
9 changed files with 180 additions and 5 deletions
|
|
@ -1,6 +1,8 @@
|
|||
import { redirect } from 'next/navigation'
|
||||
|
||||
/** Inline create lives on the assessors list — keep old bookmarks working. */
|
||||
import { advisorQuickCreateHref } from '@/lib/campaignAdvisorQuickActions'
|
||||
|
||||
/** Inline create lives on the assessores list — keep old bookmarks working. */
|
||||
export default function AdvisorNewRedirectPage() {
|
||||
redirect('/campanha/assessores')
|
||||
redirect(advisorQuickCreateHref)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { ADVISOR_QUICK_CREATE_PARAM } from '@/lib/campaignAdvisorQuickActions'
|
||||
import { CAMPAIGN_ADVISORS_HOME } from '@/lib/campaignPaths'
|
||||
import config from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
|
|
@ -14,6 +16,7 @@ import {
|
|||
loadAdvisorListPageData,
|
||||
parseAdvisorListParams,
|
||||
} from '@/utilities/advisorData'
|
||||
import { firstValue } from '@/utilities/campaignListUrl'
|
||||
import { requireCampaignPageActor } from '@/utilities/campaignPageActor'
|
||||
import { loadMunicipalityPortfolioIndex } from '@/utilities/municipality/municipalityPortfolioIndex'
|
||||
import {
|
||||
|
|
@ -35,6 +38,7 @@ export default async function AdvisorsPage({ searchParams }: AdvisorsPageProps)
|
|||
])
|
||||
|
||||
const state = parseAdvisorListParams(rawSearchParams)
|
||||
const autoCreateDraft = firstValue(rawSearchParams[ADVISOR_QUICK_CREATE_PARAM]) === '1'
|
||||
const [{ rows, totalDocs, totalPages }, municipalityIndex] = await Promise.all([
|
||||
loadAdvisorListPageData(payload, state),
|
||||
loadMunicipalityPortfolioIndex(),
|
||||
|
|
@ -55,7 +59,7 @@ export default async function AdvisorsPage({ searchParams }: AdvisorsPageProps)
|
|||
ariaLabel="Buscar assessor por nome ou e-mail"
|
||||
placeholder="Buscar por nome ou e-mail…"
|
||||
initialQuery={state.q ?? ''}
|
||||
basePath="/campanha/assessores"
|
||||
basePath={CAMPAIGN_ADVISORS_HOME}
|
||||
/>
|
||||
|
||||
<CampaignListResults>
|
||||
|
|
@ -67,6 +71,7 @@ export default async function AdvisorsPage({ searchParams }: AdvisorsPageProps)
|
|||
municipalitiesAction={setAdvisorMunicipalitiesFormAction}
|
||||
createAction={createAdvisorFormAction}
|
||||
passwordResetAction={sendAdvisorPasswordResetFormAction}
|
||||
autoCreateDraft={autoCreateDraft}
|
||||
/>
|
||||
|
||||
{totalDocs > 0 ? (
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useMemo, useState, useTransition } from 'react'
|
||||
import { useEffect, useMemo, useState, useTransition } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { AdvisorDebouncedTextCell } from '@/components/campaign/advisor/AdvisorDebouncedTextCell'
|
||||
|
|
@ -73,6 +73,7 @@ type AdvisorsTableProps = {
|
|||
state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
) => Promise<CampaignFormActionState>
|
||||
autoCreateDraft?: boolean
|
||||
}
|
||||
|
||||
const emptyDraft = (): DraftAdvisor => ({
|
||||
|
|
@ -95,6 +96,7 @@ export const AdvisorsTable = ({
|
|||
municipalitiesAction,
|
||||
createAction,
|
||||
passwordResetAction,
|
||||
autoCreateDraft = false,
|
||||
}: AdvisorsTableProps) => {
|
||||
const router = useRouter()
|
||||
const [editing, setEditing] = useState(false)
|
||||
|
|
@ -115,6 +117,10 @@ export const AdvisorsTable = ({
|
|||
setDraft(emptyDraft())
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (autoCreateDraft) setDraft(emptyDraft())
|
||||
}, [autoCreateDraft])
|
||||
|
||||
const cancelDraft = () => setDraft(null)
|
||||
|
||||
const leaveEditing = () => {
|
||||
|
|
|
|||
67
src/lib/campaignAdvisorQuickActions.ts
Normal file
67
src/lib/campaignAdvisorQuickActions.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { UserPlus } from 'lucide-react'
|
||||
|
||||
import { CAMPAIGN_ADVISORS_HOME } from '@/lib/campaignPaths'
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import type { CampaignQuickAction } from '@/lib/campaignQuickActionTypes'
|
||||
import { isUnrestrictedCampaignRole, type CampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
export const ADVISOR_QUICK_CREATE_PARAM = 'criar' as const
|
||||
|
||||
export const advisorQuickCreateHref =
|
||||
`${CAMPAIGN_ADVISORS_HOME}?${ADVISOR_QUICK_CREATE_PARAM}=1` as const
|
||||
|
||||
const advisorDetailPathPattern = /^\/campanha\/assessores\/(\d+)(?:\/|$)/
|
||||
|
||||
export const isAdvisorsListPath = (pathname: string): boolean =>
|
||||
pathname === CAMPAIGN_ADVISORS_HOME || pathname === `${CAMPAIGN_ADVISORS_HOME}/`
|
||||
|
||||
export const isAdvisorsPath = (pathname: string): boolean =>
|
||||
isAdvisorsListPath(pathname) || parseAdvisorDetailId(pathname) !== undefined
|
||||
|
||||
export const parseAdvisorDetailId = (pathname: string): number | undefined => {
|
||||
const match = pathname.match(advisorDetailPathPattern)
|
||||
if (!match) return undefined
|
||||
const id = Number(match[1])
|
||||
return Number.isInteger(id) && id > 0 ? id : undefined
|
||||
}
|
||||
|
||||
const newAdvisorAction: CampaignQuickAction = {
|
||||
id: 'new-advisor',
|
||||
label: 'Novo assessor',
|
||||
icon: UserPlus,
|
||||
description: 'Criar conta de assessor na tabela',
|
||||
href: advisorQuickCreateHref,
|
||||
}
|
||||
|
||||
const advisorQuickActionsByRole = new Map<CampaignRole, readonly CampaignQuickAction[]>()
|
||||
|
||||
const advisorQuickActionsForRole = (role: CampaignRole): readonly CampaignQuickAction[] => {
|
||||
if (!isUnrestrictedCampaignRole(role)) return []
|
||||
const cached = advisorQuickActionsByRole.get(role)
|
||||
if (cached) return cached
|
||||
const actions = [newAdvisorAction] as const
|
||||
advisorQuickActionsByRole.set(role, actions)
|
||||
return actions
|
||||
}
|
||||
|
||||
export const resolveAdvisorListQuickActions = (
|
||||
role: CampaignRole,
|
||||
): readonly CampaignQuickAction[] => advisorQuickActionsForRole(role)
|
||||
|
||||
export const resolveAdvisorDetailQuickActions = (
|
||||
role: CampaignRole,
|
||||
): readonly CampaignQuickAction[] => advisorQuickActionsForRole(role)
|
||||
|
||||
export const resolveAdvisorQuickActionsForPath = (
|
||||
pathname: string,
|
||||
role: CampaignRole,
|
||||
_context: CampaignQuickActionContext,
|
||||
): readonly CampaignQuickAction[] => {
|
||||
if (!isUnrestrictedCampaignRole(role)) return []
|
||||
|
||||
if (isAdvisorsListPath(pathname) || parseAdvisorDetailId(pathname) !== undefined) {
|
||||
return advisorQuickActionsForRole(role)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
|
@ -5,4 +5,6 @@ export const CAMPAIGN_TERRITORIES_HOME = '/campanha/territorios' as const
|
|||
|
||||
export const CAMPAIGN_DEMANDS_HOME = '/campanha/demandas' as const
|
||||
|
||||
export const CAMPAIGN_ADVISORS_HOME = '/campanha/assessores' as const
|
||||
|
||||
export const LEADER_CONTACTS_HOME = '/campanha/contatos' as const
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import { CAMPAIGN_ACTIONS_HOME } from '@/lib/campaignActionRoutes'
|
||||
import { isAdvisorsPath } from '@/lib/campaignAdvisorQuickActions'
|
||||
import { CAMPAIGN_HOME, LEADER_CONTACTS_HOME } from '@/lib/campaignPaths'
|
||||
import { isActivityTourComposerPath } from '@/lib/campaignQuickActionPaths'
|
||||
import { isStaffCampaignRole, type CampaignRole } from '@/lib/campaignRoles'
|
||||
import {
|
||||
isStaffCampaignRole,
|
||||
isUnrestrictedCampaignRole,
|
||||
type CampaignRole,
|
||||
} from '@/lib/campaignRoles'
|
||||
|
||||
/** Início matches only exactly — same rule as sidebar nav. */
|
||||
export const isCampaignHomePath = (pathname: string): boolean =>
|
||||
|
|
@ -23,5 +28,6 @@ export const shouldMountQuickActionsDrawer = (pathname: string, role: CampaignRo
|
|||
// B84: E13 compositor already exposes primary CTAs — skip competing drawer chrome.
|
||||
if (isActivityTourComposerPath(pathname)) return false
|
||||
if (role === 'leader') return isLeaderContactsPath(pathname)
|
||||
if (isAdvisorsPath(pathname)) return isUnrestrictedCampaignRole(role)
|
||||
return isStaffCampaignRole(role)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { resolveActivityQuickActions } from '@/lib/activityQuickActions'
|
||||
import { resolveAdvisorQuickActionsForPath } from '@/lib/campaignAdvisorQuickActions'
|
||||
import { resolveStaffHomeQuickActions } from '@/lib/campaignHomeActions'
|
||||
import { resolveMunicipalityQuickActionsForPath } from '@/lib/campaignMunicipalityQuickActions'
|
||||
import { CAMPAIGN_TERRITORIES_HOME } from '@/lib/campaignPaths'
|
||||
|
|
@ -56,6 +57,9 @@ export const resolveQuickActionsForPath = (
|
|||
const municipalityActions = resolveMunicipalityQuickActionsForPath(pathname, role, context)
|
||||
if (municipalityActions.length > 0) return municipalityActions
|
||||
|
||||
const advisorActions = resolveAdvisorQuickActionsForPath(pathname, role, context)
|
||||
if (advisorActions.length > 0) return advisorActions
|
||||
|
||||
if (isTerritoriesListPath(pathname)) {
|
||||
return resolveStaffHomeQuickActions(role)
|
||||
}
|
||||
|
|
|
|||
61
tests/unit/campaignAdvisorQuickActions.unit.spec.ts
Normal file
61
tests/unit/campaignAdvisorQuickActions.unit.spec.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
advisorQuickCreateHref,
|
||||
isAdvisorsListPath,
|
||||
isAdvisorsPath,
|
||||
parseAdvisorDetailId,
|
||||
resolveAdvisorDetailQuickActions,
|
||||
resolveAdvisorListQuickActions,
|
||||
resolveAdvisorQuickActionsForPath,
|
||||
} from '@/lib/campaignAdvisorQuickActions'
|
||||
|
||||
describe('campaignAdvisorQuickActions paths', () => {
|
||||
it('matches assessores list exactly', () => {
|
||||
expect(isAdvisorsListPath('/campanha/assessores')).toBe(true)
|
||||
expect(isAdvisorsListPath('/campanha/assessores/')).toBe(true)
|
||||
expect(isAdvisorsListPath('/campanha/assessores/novo')).toBe(false)
|
||||
})
|
||||
|
||||
it('matches assessores detail ids', () => {
|
||||
expect(parseAdvisorDetailId('/campanha/assessores/42')).toBe(42)
|
||||
expect(parseAdvisorDetailId('/campanha/assessores/42/')).toBe(42)
|
||||
expect(parseAdvisorDetailId('/campanha/assessores/novo')).toBeUndefined()
|
||||
expect(isAdvisorsPath('/campanha/assessores/42')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveAdvisorListQuickActions', () => {
|
||||
it('returns new-advisor for unrestricted roles', () => {
|
||||
const actions = resolveAdvisorListQuickActions('coordinator')
|
||||
expect(actions).toHaveLength(1)
|
||||
expect(actions[0]?.id).toBe('new-advisor')
|
||||
expect(actions[0]?.href).toBe(advisorQuickCreateHref)
|
||||
})
|
||||
|
||||
it('returns empty catalog for advisor lockdown', () => {
|
||||
expect(resolveAdvisorListQuickActions('advisor')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveAdvisorDetailQuickActions', () => {
|
||||
it('returns new-advisor for unrestricted roles', () => {
|
||||
const actions = resolveAdvisorDetailQuickActions('candidate')
|
||||
expect(actions.map((action) => action.id)).toEqual(['new-advisor'])
|
||||
expect(actions[0]?.href).toBe(advisorQuickCreateHref)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveAdvisorQuickActionsForPath', () => {
|
||||
it('delegates list and detail catalogs (B87)', () => {
|
||||
const list = resolveAdvisorQuickActionsForPath('/campanha/assessores', 'coordinator', {})
|
||||
expect(list.map((action) => action.id)).toEqual(['new-advisor'])
|
||||
|
||||
const detail = resolveAdvisorQuickActionsForPath('/campanha/assessores/7', 'coordinator', {})
|
||||
expect(detail.map((action) => action.id)).toEqual(['new-advisor'])
|
||||
})
|
||||
|
||||
it('returns empty catalog for advisor role even on assessores paths', () => {
|
||||
expect(resolveAdvisorQuickActionsForPath('/campanha/assessores', 'advisor', {})).toEqual([])
|
||||
})
|
||||
})
|
||||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|||
|
||||
import { resolveActivityQuickActions } from '@/lib/activityQuickActions'
|
||||
import { CAMPAIGN_WIZARD_ACTION_SLUGS } from '@/lib/campaignActionRoutes'
|
||||
import { advisorQuickCreateHref } from '@/lib/campaignAdvisorQuickActions'
|
||||
import { UNCOVERED_MUNICIPALITIES_LIST_HREF, homeActionsForRole } from '@/lib/campaignHomeActions'
|
||||
import {
|
||||
CAMPAIGN_DEMANDS_CREATE_HREF,
|
||||
|
|
@ -72,6 +73,13 @@ describe('campaignQuickActionMount', () => {
|
|||
expect(isActivityTourComposerPath(ACTIVITY_TOUR_COMPOSER_PATH)).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer(ACTIVITY_TOUR_COMPOSER_PATH, 'coordinator')).toBe(false)
|
||||
})
|
||||
|
||||
it('mounts assessores only for unrestricted roles (B87)', () => {
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/assessores', 'coordinator')).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/assessores/12', 'candidate')).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/assessores', 'advisor')).toBe(false)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/assessores/12', 'advisor')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('campaignQuickActionPaths (activities)', () => {
|
||||
|
|
@ -279,6 +287,20 @@ describe('campaignQuickActionRegistry', () => {
|
|||
resolveQuickActionsForPath('/campanha/demandas/pedido-cairu', 'coordinator', {}),
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('delegates assessores list and detail catalogs (B87)', () => {
|
||||
const list = resolveQuickActionsForPath('/campanha/assessores', 'coordinator', {})
|
||||
expect(list.map((action) => action.id)).toEqual(['new-advisor'])
|
||||
expect(list[0]?.href).toBe(advisorQuickCreateHref)
|
||||
|
||||
const detail = resolveQuickActionsForPath('/campanha/assessores/9', 'candidate', {})
|
||||
expect(detail.map((action) => action.id)).toEqual(['new-advisor'])
|
||||
expect(detail[0]?.href).toBe(advisorQuickCreateHref)
|
||||
})
|
||||
|
||||
it('returns empty catalog on assessores for advisor lockdown', () => {
|
||||
expect(resolveQuickActionsForPath('/campanha/assessores', 'advisor', {})).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('campaignQuickActionDemands paths', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue