mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
Merge pull request #80 from fsolla/cursor/pool-i21-97442a76-e99b
B85 — Ações rápidas — Demandas
This commit is contained in:
commit
90d130af33
9 changed files with 234 additions and 2 deletions
|
|
@ -6,6 +6,7 @@ import { getPayload } from 'payload'
|
|||
|
||||
import { DemandWorkflowCard } from '@/components/campaign/demand/DemandWorkflowCard'
|
||||
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
|
||||
import { CampaignQuickActionContextSync } from '@/components/campaign/shell/CampaignQuickActionContextSync'
|
||||
import { Badge } from '@/components/ui/Badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
|
|
@ -59,6 +60,11 @@ export default async function DemandDetailPage({ params }: DemandDetailPageProps
|
|||
|
||||
return (
|
||||
<CampaignPageShell>
|
||||
<CampaignQuickActionContextSync
|
||||
municipalitySlug={demand.municipalitySlug}
|
||||
municipalityId={demand.municipalityId > 0 ? demand.municipalityId : undefined}
|
||||
demandSlug={demand.slug}
|
||||
/>
|
||||
<header className="flex flex-col gap-2">
|
||||
<Button asChild variant="ghost" className="min-h-11 self-start">
|
||||
<Link href="/campanha/demandas">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useCampaignQuickActionContext } from '@/components/campaign/shell/CampaignQuickActionContext'
|
||||
import {
|
||||
emptyCampaignQuickActionContext,
|
||||
type CampaignQuickActionContext,
|
||||
} from '@/lib/campaignQuickActionContext'
|
||||
|
||||
/** RSC pages push route context into the B79 drawer host (B80+). */
|
||||
export const CampaignQuickActionContextSync = ({
|
||||
municipalitySlug,
|
||||
municipalityId,
|
||||
leadershipId,
|
||||
organizationSlug,
|
||||
activitySlug,
|
||||
demandSlug,
|
||||
}: CampaignQuickActionContext) => {
|
||||
const { setContext } = useCampaignQuickActionContext()
|
||||
|
||||
useEffect(() => {
|
||||
setContext({
|
||||
municipalitySlug,
|
||||
municipalityId,
|
||||
leadershipId,
|
||||
organizationSlug,
|
||||
activitySlug,
|
||||
demandSlug,
|
||||
})
|
||||
return () => setContext(emptyCampaignQuickActionContext())
|
||||
}, [
|
||||
setContext,
|
||||
municipalitySlug,
|
||||
municipalityId,
|
||||
leadershipId,
|
||||
organizationSlug,
|
||||
activitySlug,
|
||||
demandSlug,
|
||||
])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -3,4 +3,6 @@ export const CAMPAIGN_HOME = '/campanha' as const
|
|||
|
||||
export const CAMPAIGN_TERRITORIES_HOME = '/campanha/territorios' as const
|
||||
|
||||
export const CAMPAIGN_DEMANDS_HOME = '/campanha/demandas' as const
|
||||
|
||||
export const LEADER_CONTACTS_HOME = '/campanha/contatos' as const
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
export type CampaignQuickActionContext = {
|
||||
municipalitySlug?: string
|
||||
municipalityId?: number
|
||||
leadershipId?: number
|
||||
organizationSlug?: string
|
||||
activitySlug?: string
|
||||
|
|
|
|||
100
src/lib/campaignQuickActionDemands.ts
Normal file
100
src/lib/campaignQuickActionDemands.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
CAMPAIGN_WIZARD_ACTION_SLUGS,
|
||||
wizardActionHref,
|
||||
type CampaignWizardActionId,
|
||||
} from '@/lib/campaignActionRoutes'
|
||||
import { homeActionsForRole } from '@/lib/campaignHomeActions'
|
||||
import { CAMPAIGN_DEMANDS_HOME } from '@/lib/campaignPaths'
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import type { CampaignQuickAction } from '@/lib/campaignQuickActionTypes'
|
||||
import { isStaffCampaignRole, type CampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
export const CAMPAIGN_DEMANDS_CREATE_HREF = `${CAMPAIGN_DEMANDS_HOME}/nova` as const
|
||||
|
||||
const DETAIL_WIZARD_ACTION_IDS: readonly CampaignWizardActionId[] = [
|
||||
'update-votes',
|
||||
'register-signal',
|
||||
'change-trend',
|
||||
'update-leadership',
|
||||
'register-demand',
|
||||
]
|
||||
|
||||
export const demandCreateHref = (municipalityId?: number): string => {
|
||||
if (municipalityId === undefined) {
|
||||
return CAMPAIGN_DEMANDS_CREATE_HREF
|
||||
}
|
||||
return `${CAMPAIGN_DEMANDS_CREATE_HREF}?municipality=${municipalityId}`
|
||||
}
|
||||
|
||||
export const isDemandsListPath = (pathname: string): boolean =>
|
||||
pathname === CAMPAIGN_DEMANDS_HOME || pathname === `${CAMPAIGN_DEMANDS_HOME}/`
|
||||
|
||||
export const isDemandDetailPath = (pathname: string): boolean => {
|
||||
if (!pathname.startsWith(`${CAMPAIGN_DEMANDS_HOME}/`)) {
|
||||
return false
|
||||
}
|
||||
const rest = pathname.slice(`${CAMPAIGN_DEMANDS_HOME}/`.length)
|
||||
if (!rest || rest.includes('/')) {
|
||||
return false
|
||||
}
|
||||
return rest !== 'nova'
|
||||
}
|
||||
|
||||
const registerDemandListAction = (role: CampaignRole): CampaignQuickAction | null => {
|
||||
const source = homeActionsForRole(role).find((action) => action.id === 'register-demand')
|
||||
if (!source) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...source,
|
||||
href: CAMPAIGN_DEMANDS_CREATE_HREF,
|
||||
}
|
||||
}
|
||||
|
||||
/** B85 list: single "Registrar pedido" launcher (form route — wizard A5 is still a stub). */
|
||||
export const resolveDemandsListQuickActions = (
|
||||
role: CampaignRole,
|
||||
): readonly CampaignQuickAction[] => {
|
||||
if (!isStaffCampaignRole(role)) {
|
||||
return []
|
||||
}
|
||||
const action = registerDemandListAction(role)
|
||||
return action ? [action] : []
|
||||
}
|
||||
|
||||
/** B85 detail: staff Início A1–A5 with municipality prefill when linked. */
|
||||
export const resolveDemandDetailQuickActions = (
|
||||
role: CampaignRole,
|
||||
context: CampaignQuickActionContext,
|
||||
): readonly CampaignQuickAction[] => {
|
||||
if (!isStaffCampaignRole(role)) {
|
||||
return []
|
||||
}
|
||||
const { municipalitySlug, municipalityId } = context
|
||||
if (!municipalitySlug) {
|
||||
return []
|
||||
}
|
||||
|
||||
const byId = new Map(homeActionsForRole(role).map((action) => [action.id, action]))
|
||||
|
||||
return DETAIL_WIZARD_ACTION_IDS.flatMap((id) => {
|
||||
const action = byId.get(id)
|
||||
if (!action) {
|
||||
return []
|
||||
}
|
||||
if (id === 'register-demand') {
|
||||
return [
|
||||
{
|
||||
...action,
|
||||
href: demandCreateHref(municipalityId),
|
||||
},
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
...action,
|
||||
href: wizardActionHref(CAMPAIGN_WIZARD_ACTION_SLUGS[id], municipalitySlug),
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
|
@ -4,6 +4,12 @@ import { resolveMunicipalityQuickActionsForPath } from '@/lib/campaignMunicipali
|
|||
import { CAMPAIGN_TERRITORIES_HOME } from '@/lib/campaignPaths'
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import { isLeaderContactsPath } from '@/lib/campaignQuickActionMount'
|
||||
import {
|
||||
isDemandDetailPath,
|
||||
isDemandsListPath,
|
||||
resolveDemandDetailQuickActions,
|
||||
resolveDemandsListQuickActions,
|
||||
} from '@/lib/campaignQuickActionDemands'
|
||||
import {
|
||||
parseActivityQuickActionSurface,
|
||||
parseOrganizationQuickActionSurface,
|
||||
|
|
@ -40,12 +46,18 @@ export const resolveQuickActionsForPath = (
|
|||
return resolveOrganizationQuickActions(organizationSurface, role, context)
|
||||
}
|
||||
|
||||
if (isDemandsListPath(pathname)) {
|
||||
return resolveDemandsListQuickActions(role)
|
||||
}
|
||||
if (isDemandDetailPath(pathname)) {
|
||||
return resolveDemandDetailQuickActions(role, context)
|
||||
}
|
||||
|
||||
const municipalityActions = resolveMunicipalityQuickActionsForPath(pathname, role, context)
|
||||
if (municipalityActions.length > 0) return municipalityActions
|
||||
|
||||
if (isTerritoriesListPath(pathname)) {
|
||||
return resolveStaffHomeQuickActions(role)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export type DemandRowViewModel = {
|
|||
slug: string
|
||||
kind: CampaignDemandKind
|
||||
status: CampaignDemandStatus
|
||||
municipalityId: number
|
||||
municipalityName: string
|
||||
municipalitySlug: string
|
||||
requesterName: string | null
|
||||
|
|
@ -107,6 +108,7 @@ const toDemandRow = (
|
|||
requesterBy: Map<number, string>,
|
||||
): DemandRowViewModel => {
|
||||
const municipality = municipalityBy.get(relationshipId(demand.municipality) ?? -1)
|
||||
const municipalityID = relationshipId(demand.municipality)
|
||||
const leadershipID = relationshipId(demand.leadership)
|
||||
return {
|
||||
id: demand.id,
|
||||
|
|
@ -114,6 +116,7 @@ const toDemandRow = (
|
|||
slug: demand.slug,
|
||||
kind: demand.kind as CampaignDemandKind,
|
||||
status: demand.status as CampaignDemandStatus,
|
||||
municipalityId: municipalityID ?? 0,
|
||||
municipalityName: municipality?.name ?? 'Município',
|
||||
municipalitySlug: municipality?.slug ?? '',
|
||||
requesterName: leadershipID ? (requesterBy.get(leadershipID) ?? null) : null,
|
||||
|
|
|
|||
|
|
@ -61,11 +61,12 @@ describe('searchStaffMunicipalityHits (B60)', () => {
|
|||
throw new Error('could not allocate two municipalities with distinct word-start queries')
|
||||
}
|
||||
}
|
||||
await fixtures.assignMunicipalityAdvisors(administered.id, [advisor.id])
|
||||
|
||||
const otherQuery = distinctWordStartQuery(other.name, administered.name)
|
||||
const administeredQuery = distinctWordStartQuery(administered.name, other.name)
|
||||
|
||||
await fixtures.assignMunicipalityAdvisors(administered.id, [advisor.id])
|
||||
|
||||
const advisorResult = await searchStaffMunicipalityHits(payload, advisor, otherQuery)
|
||||
expect(advisorResult.map((hit) => hit.slug)).toEqual([])
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ import { describe, expect, it } from 'vitest'
|
|||
import { resolveActivityQuickActions } from '@/lib/activityQuickActions'
|
||||
import { CAMPAIGN_WIZARD_ACTION_SLUGS } from '@/lib/campaignActionRoutes'
|
||||
import { UNCOVERED_MUNICIPALITIES_LIST_HREF, homeActionsForRole } from '@/lib/campaignHomeActions'
|
||||
import {
|
||||
CAMPAIGN_DEMANDS_CREATE_HREF,
|
||||
demandCreateHref,
|
||||
isDemandDetailPath,
|
||||
isDemandsListPath,
|
||||
resolveDemandDetailQuickActions,
|
||||
resolveDemandsListQuickActions,
|
||||
} from '@/lib/campaignQuickActionDemands'
|
||||
import {
|
||||
isCampaignActionsPath,
|
||||
isCampaignHomePath,
|
||||
|
|
@ -49,6 +57,8 @@ describe('campaignQuickActionMount', () => {
|
|||
it('mounts for staff outside Início and acoes', () => {
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/municipios', 'coordinator')).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/territorios', 'coordinator')).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/demandas', 'coordinator')).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/demandas/foo', 'coordinator')).toBe(true)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha', 'coordinator')).toBe(false)
|
||||
expect(shouldMountQuickActionsDrawer('/campanha/acoes/registrar-sinal', 'advisor')).toBe(false)
|
||||
})
|
||||
|
|
@ -234,6 +244,60 @@ describe('campaignQuickActionRegistry', () => {
|
|||
it('returns empty catalog on territorios for leader lockdown', () => {
|
||||
expect(resolveQuickActionsForPath('/campanha/territorios', 'leader', {})).toEqual([])
|
||||
})
|
||||
|
||||
it('returns single register-demand launcher on demandas list (B85)', () => {
|
||||
const actions = resolveQuickActionsForPath('/campanha/demandas', 'coordinator', {})
|
||||
expect(actions.map((action) => action.id)).toEqual(['register-demand'])
|
||||
expect(actions[0]?.href).toBe(CAMPAIGN_DEMANDS_CREATE_HREF)
|
||||
expect(actions[0]?.href).not.toContain('municipio=')
|
||||
})
|
||||
|
||||
it('returns A1–A5 prefilled on demand detail when municipality context is set (B85)', () => {
|
||||
const actions = resolveQuickActionsForPath('/campanha/demandas/pedido-cairu', 'coordinator', {
|
||||
municipalitySlug: 'cairu',
|
||||
municipalityId: 42,
|
||||
demandSlug: 'pedido-cairu',
|
||||
})
|
||||
expect(actions.map((action) => action.id)).toEqual([
|
||||
'update-votes',
|
||||
'register-signal',
|
||||
'change-trend',
|
||||
'update-leadership',
|
||||
'register-demand',
|
||||
])
|
||||
expect(actions.find((action) => action.id === 'update-votes')?.href).toBe(
|
||||
'/campanha/acoes/atualizar-votos?municipio=cairu',
|
||||
)
|
||||
expect(actions.find((action) => action.id === 'register-demand')?.href).toBe(
|
||||
demandCreateHref(42),
|
||||
)
|
||||
expect(actions.some((action) => action.id === 'uncovered-municipalities')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns empty catalog on demand detail without municipality context', () => {
|
||||
expect(
|
||||
resolveQuickActionsForPath('/campanha/demandas/pedido-cairu', 'coordinator', {}),
|
||||
).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('campaignQuickActionDemands paths', () => {
|
||||
it('matches list and detail paths but not nova', () => {
|
||||
expect(isDemandsListPath('/campanha/demandas')).toBe(true)
|
||||
expect(isDemandsListPath('/campanha/demandas/')).toBe(true)
|
||||
expect(isDemandDetailPath('/campanha/demandas/pedido-cairu')).toBe(true)
|
||||
expect(isDemandDetailPath('/campanha/demandas/nova')).toBe(false)
|
||||
expect(isDemandDetailPath('/campanha/demandas')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('campaignQuickActionDemands resolvers', () => {
|
||||
it('returns empty catalog for leader lockdown', () => {
|
||||
expect(resolveDemandsListQuickActions('leader')).toEqual([])
|
||||
expect(
|
||||
resolveDemandDetailQuickActions('leader', { municipalitySlug: 'cairu', municipalityId: 1 }),
|
||||
).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('campaignQuickActionSnap', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue