mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
B88 — Ações rápidas em Organizações (lista + detalhe)
Registra /campanha/organizacoes no registry B79 com launcher new-organization na lista; detalhe vazio até existir filtro de lideranças por org ou município óbvio único. Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
d7bef57933
commit
9b76f19b85
4 changed files with 126 additions and 4 deletions
|
|
@ -1,4 +1,4 @@
|
|||
/** Client-safe `/campanha/atividades` path helpers for the B84 quick-action registry. */
|
||||
/** Client-safe quick-action path helpers for the B84+ vertical registries. */
|
||||
|
||||
export const ACTIVITY_LIST_PATH = '/campanha/atividades' as const
|
||||
|
||||
|
|
@ -6,20 +6,35 @@ export const ACTIVITY_NEW_PATH = '/campanha/atividades/nova' as const
|
|||
|
||||
export const ACTIVITY_TOUR_COMPOSER_PATH = '/campanha/atividades/giros' as const
|
||||
|
||||
export const ORGANIZATIONS_LIST_PATH = '/campanha/organizacoes' as const
|
||||
|
||||
export const ORGANIZATION_NEW_PATH = '/campanha/organizacoes/nova' as const
|
||||
|
||||
export type ActivityQuickActionSurface = { kind: 'list' } | { kind: 'detail'; activitySlug: string }
|
||||
|
||||
export type OrganizationQuickActionSurface =
|
||||
| { kind: 'list' }
|
||||
| { kind: 'detail'; organizationSlug: string }
|
||||
|
||||
const normalizePathname = (pathname: string): string =>
|
||||
pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname
|
||||
|
||||
const activityDetailSlug = (pathname: string): string | null => {
|
||||
const prefix = `${ACTIVITY_LIST_PATH}/`
|
||||
const listDetailSlug = (
|
||||
pathname: string,
|
||||
listPath: string,
|
||||
excludedSegments: readonly string[],
|
||||
): string | null => {
|
||||
const prefix = `${listPath}/`
|
||||
if (!pathname.startsWith(prefix)) return null
|
||||
const rest = pathname.slice(prefix.length)
|
||||
if (!rest || rest.includes('/')) return null
|
||||
if (rest === 'nova' || rest === 'giros') return null
|
||||
if (excludedSegments.includes(rest)) return null
|
||||
return rest
|
||||
}
|
||||
|
||||
const activityDetailSlug = (pathname: string): string | null =>
|
||||
listDetailSlug(pathname, ACTIVITY_LIST_PATH, ['nova', 'giros'])
|
||||
|
||||
export const parseActivityQuickActionSurface = (
|
||||
pathname: string,
|
||||
): ActivityQuickActionSurface | null => {
|
||||
|
|
@ -29,5 +44,14 @@ export const parseActivityQuickActionSurface = (
|
|||
return slug ? { kind: 'detail', activitySlug: slug } : null
|
||||
}
|
||||
|
||||
export const parseOrganizationQuickActionSurface = (
|
||||
pathname: string,
|
||||
): OrganizationQuickActionSurface | null => {
|
||||
const normalized = normalizePathname(pathname)
|
||||
if (normalized === ORGANIZATIONS_LIST_PATH) return { kind: 'list' }
|
||||
const slug = listDetailSlug(normalized, ORGANIZATIONS_LIST_PATH, ['nova'])
|
||||
return slug ? { kind: 'detail', organizationSlug: slug } : null
|
||||
}
|
||||
|
||||
export const isActivityTourComposerPath = (pathname: string): boolean =>
|
||||
normalizePathname(pathname) === ACTIVITY_TOUR_COMPOSER_PATH
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { resolveActivityQuickActions } from '@/lib/activityQuickActions'
|
||||
import { resolveStaffHomeQuickActions } from '@/lib/campaignHomeActions'
|
||||
import { resolveMunicipalityQuickActionsForPath } from '@/lib/campaignMunicipalityQuickActions'
|
||||
import { parseOrganizationQuickActionSurface } from '@/lib/campaignQuickActionPaths'
|
||||
import { resolveOrganizationQuickActions } from '@/lib/organizationQuickActions'
|
||||
import { CAMPAIGN_TERRITORIES_HOME } from '@/lib/campaignPaths'
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import { parseActivityQuickActionSurface } from '@/lib/campaignQuickActionPaths'
|
||||
|
|
@ -25,6 +27,11 @@ export const resolveQuickActionsForPath = (
|
|||
return resolveActivityQuickActions(activitySurface, role, context)
|
||||
}
|
||||
|
||||
const organizationSurface = parseOrganizationQuickActionSurface(pathname)
|
||||
if (organizationSurface) {
|
||||
return resolveOrganizationQuickActions(organizationSurface, role, context)
|
||||
}
|
||||
|
||||
const municipalityActions = resolveMunicipalityQuickActionsForPath(pathname, role, context)
|
||||
if (municipalityActions.length > 0) return municipalityActions
|
||||
|
||||
|
|
|
|||
35
src/lib/organizationQuickActions.ts
Normal file
35
src/lib/organizationQuickActions.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { Plus } from 'lucide-react'
|
||||
|
||||
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
|
||||
import {
|
||||
ORGANIZATION_NEW_PATH,
|
||||
type OrganizationQuickActionSurface,
|
||||
} from '@/lib/campaignQuickActionPaths'
|
||||
import type { CampaignQuickAction } from '@/lib/campaignQuickActionTypes'
|
||||
import { isStaffCampaignRole, type CampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
const listQuickActions = (): readonly CampaignQuickAction[] => [
|
||||
{
|
||||
id: 'new-organization',
|
||||
label: 'Nova organização',
|
||||
icon: Plus,
|
||||
description: 'Cadastrar sindicato, associação ou movimento de apoio',
|
||||
href: ORGANIZATION_NEW_PATH,
|
||||
},
|
||||
]
|
||||
|
||||
export const resolveOrganizationQuickActions = (
|
||||
surface: OrganizationQuickActionSurface,
|
||||
role: CampaignRole,
|
||||
_context: CampaignQuickActionContext,
|
||||
): readonly CampaignQuickAction[] => {
|
||||
if (!isStaffCampaignRole(role)) return []
|
||||
|
||||
if (surface.kind === 'list') {
|
||||
return listQuickActions()
|
||||
}
|
||||
|
||||
// Detail: leadership list has no organization filter URL yet (B88 as-built);
|
||||
// orgs rarely have a single obvious municipality for A1–A5 prefills.
|
||||
return []
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { resolveActivityQuickActions } from '@/lib/activityQuickActions'
|
||||
import { resolveOrganizationQuickActions } from '@/lib/organizationQuickActions'
|
||||
import { CAMPAIGN_WIZARD_ACTION_SLUGS } from '@/lib/campaignActionRoutes'
|
||||
import { UNCOVERED_MUNICIPALITIES_LIST_HREF, homeActionsForRole } from '@/lib/campaignHomeActions'
|
||||
import {
|
||||
|
|
@ -13,8 +14,11 @@ import {
|
|||
ACTIVITY_LIST_PATH,
|
||||
ACTIVITY_NEW_PATH,
|
||||
ACTIVITY_TOUR_COMPOSER_PATH,
|
||||
ORGANIZATION_NEW_PATH,
|
||||
ORGANIZATIONS_LIST_PATH,
|
||||
isActivityTourComposerPath,
|
||||
parseActivityQuickActionSurface,
|
||||
parseOrganizationQuickActionSurface,
|
||||
} from '@/lib/campaignQuickActionPaths'
|
||||
import { resolveQuickActionsForPath } from '@/lib/campaignQuickActionRegistry'
|
||||
import {
|
||||
|
|
@ -77,6 +81,44 @@ describe('campaignQuickActionPaths (activities)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('organizationQuickActions (B88)', () => {
|
||||
it('parses list and detail surfaces', () => {
|
||||
expect(parseOrganizationQuickActionSurface(ORGANIZATIONS_LIST_PATH)).toEqual({ kind: 'list' })
|
||||
expect(parseOrganizationQuickActionSurface(`${ORGANIZATIONS_LIST_PATH}/`)).toEqual({
|
||||
kind: 'list',
|
||||
})
|
||||
expect(parseOrganizationQuickActionSurface('/campanha/organizacoes/sindmed')).toEqual({
|
||||
kind: 'detail',
|
||||
organizationSlug: 'sindmed',
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores nova and nested routes', () => {
|
||||
expect(parseOrganizationQuickActionSurface(ORGANIZATION_NEW_PATH)).toBeNull()
|
||||
expect(parseOrganizationQuickActionSurface('/campanha/organizacoes/foo/editar')).toBeNull()
|
||||
})
|
||||
|
||||
it('lists new-organization on the catalog page', () => {
|
||||
const actions = resolveOrganizationQuickActions({ kind: 'list' }, 'coordinator', {})
|
||||
expect(actions.map((action) => action.id)).toEqual(['new-organization'])
|
||||
expect(actions[0]?.href).toBe(ORGANIZATION_NEW_PATH)
|
||||
})
|
||||
|
||||
it('returns empty detail catalog without leadership filter URL', () => {
|
||||
expect(
|
||||
resolveOrganizationQuickActions(
|
||||
{ kind: 'detail', organizationSlug: 'sindmed' },
|
||||
'coordinator',
|
||||
{ organizationSlug: 'sindmed' },
|
||||
),
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty catalog for leader lockdown', () => {
|
||||
expect(resolveOrganizationQuickActions({ kind: 'list' }, 'leader', {})).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('activityQuickActions (B84)', () => {
|
||||
it('lists vertical verbs on the activity list', () => {
|
||||
const actions = resolveActivityQuickActions({ kind: 'list' }, 'coordinator', {})
|
||||
|
|
@ -125,6 +167,20 @@ describe('activityQuickActions (B84)', () => {
|
|||
describe('campaignQuickActionRegistry', () => {
|
||||
const staffActionIds = homeActionsForRole('coordinator').map((action) => action.id)
|
||||
|
||||
it('delegates organization routes to the B88 catalog', () => {
|
||||
const actions = resolveQuickActionsForPath(ORGANIZATIONS_LIST_PATH, 'coordinator', {})
|
||||
expect(actions.map((action) => action.id)).toEqual(['new-organization'])
|
||||
expect(actions[0]?.href).toBe(ORGANIZATION_NEW_PATH)
|
||||
})
|
||||
|
||||
it('returns empty organization detail catalog in the registry', () => {
|
||||
expect(
|
||||
resolveQuickActionsForPath('/campanha/organizacoes/sindmed', 'advisor', {
|
||||
organizationSlug: 'sindmed',
|
||||
}),
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty catalog for unregistered paths', () => {
|
||||
expect(resolveQuickActionsForPath('/campanha/apoiadores', 'coordinator', {})).toEqual([])
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue