diff --git a/src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx b/src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx new file mode 100644 index 00000000..04a53dba --- /dev/null +++ b/src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx @@ -0,0 +1,23 @@ +'use client' + +import type { ReactNode } from 'react' + +import { CampaignStaffGlobalSearchBody } from '@/components/campaign/dashboard/CampaignStaffGlobalSearch' +import { HomeSearchProvider } from '@/components/campaign/dashboard/HomeSearchContext' +import { useHomeSearchQuery } from '@/components/campaign/dashboard/useHomeSearchQuery' + +/** Owns debounce + focus state for any global search surface (Início, drawer B91). */ +export const CampaignGlobalSearchProvider = ({ children }: { children: ReactNode }) => { + const searchState = useHomeSearchQuery() + return {children} +} + +/** Input + result groups — requires `CampaignGlobalSearchProvider` unless using the mount. */ +export const CampaignGlobalSearchBody = CampaignStaffGlobalSearchBody + +/** Provider + default global search body (drawer, smoke tests). */ +export const CampaignGlobalSearchMount = () => ( + + + +) diff --git a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx index ab17d8ec..41e3d69c 100644 --- a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx +++ b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx @@ -2,15 +2,17 @@ import type { ReactNode } from 'react' +import { + CampaignGlobalSearchBody, + CampaignGlobalSearchProvider, +} from '@/components/campaign/dashboard/CampaignGlobalSearchMount' import { CampaignHomeLayout } from '@/components/campaign/dashboard/CampaignHomeLayout' import { CampaignHomeSearch } from '@/components/campaign/dashboard/CampaignHomeSearch' -import { CampaignStaffGlobalSearchBody } from '@/components/campaign/dashboard/CampaignStaffGlobalSearch' -import { HomeSearchProvider } from '@/components/campaign/dashboard/HomeSearchContext' +import { useHomeSearch } from '@/components/campaign/dashboard/HomeSearchContext' import { HomeSearchResultsProvider, useHomeSearchResultsState, } from '@/components/campaign/dashboard/HomeSearchResultsContext' -import { useHomeSearchQuery } from '@/components/campaign/dashboard/useHomeSearchQuery' const CampaignHomeStaffSearchSlot = ({ searchResults }: { searchResults: ReactNode }) => { const searchResultsState = useHomeSearchResultsState() @@ -24,7 +26,7 @@ const CampaignHomeStaffSearchSlot = ({ searchResults }: { searchResults: ReactNo ) } -export const CampaignHomeStaffChrome = ({ +const CampaignHomeStaffChromeInner = ({ actions, searchResults, summarySlot, @@ -34,25 +36,40 @@ export const CampaignHomeStaffChrome = ({ searchResults?: ReactNode summarySlot?: ReactNode }) => { - const searchState = useHomeSearchQuery() - const focused = searchState.uiFocused + const { uiFocused: focused } = useHomeSearch() return ( - -
- - ) : ( - - ) - } - summarySlot={summarySlot} - /> -
-
+
+ + ) : ( + + ) + } + summarySlot={summarySlot} + /> +
) } + +export const CampaignHomeStaffChrome = ({ + actions, + searchResults, + summarySlot, +}: { + actions: ReactNode + searchResults?: ReactNode + summarySlot?: ReactNode +}) => ( + + + +) diff --git a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx index 2e137d2d..c5af7831 100644 --- a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx +++ b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx @@ -2,6 +2,7 @@ import { useCallback, useState } from 'react' +import { CampaignGlobalSearchMount } from '@/components/campaign/dashboard/CampaignGlobalSearchMount' import { CampaignHomeActionStrip } from '@/components/campaign/dashboard/CampaignHomeActionStrip' import { Drawer, DrawerContent, DrawerTitle } from '@/components/ui/Drawer' import { @@ -103,6 +104,7 @@ export const CampaignQuickActionsDrawer = ({ ) : (

Nenhuma ação rápida nesta página.

)} + {expanded ? : null} diff --git a/src/lib/campaignQuickActionRegistry.ts b/src/lib/campaignQuickActionRegistry.ts index 3c875182..82f3b170 100644 --- a/src/lib/campaignQuickActionRegistry.ts +++ b/src/lib/campaignQuickActionRegistry.ts @@ -3,13 +3,13 @@ import { resolveStaffHomeQuickActions } from '@/lib/campaignHomeActions' import { resolveMunicipalityQuickActionsForPath } from '@/lib/campaignMunicipalityQuickActions' 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 { isLeaderContactsPath } from '@/lib/campaignQuickActionMount' import { parseActivityQuickActionSurface, parseOrganizationQuickActionSurface, diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx new file mode 100644 index 00000000..8481f257 --- /dev/null +++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx @@ -0,0 +1,67 @@ +import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer' +import { postCampaignJson } from '@/lib/campaignJsonRequest' + +vi.mock('@/lib/campaignJsonRequest', () => ({ + postCampaignJson: vi.fn(), +})) + +const idleSuggestPayload = { + status: 'success' as const, + resultKind: 'suggest' as const, + municipalities: [], + territories: [], + advisors: [], + leaderships: [], + stateDeputies: [], + activities: [], + demands: [], +} + +afterEach(() => { + cleanup() + vi.mocked(postCampaignJson).mockReset() +}) + +describe('CampaignQuickActionsDrawer (B91)', () => { + it('shows global search in the expanded snap', () => { + vi.mocked(postCampaignJson).mockResolvedValue({ + ok: true, + payload: idleSuggestPayload, + }) + + render() + + expect(screen.queryByLabelText('Buscar na campanha')).toBeNull() + + fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + + const context = document.getElementById('quickActionContext') + expect(context?.className).not.toContain('hidden') + expect(context?.getAttribute('data-snap')).toBe('expanded') + expect(screen.getByLabelText('Buscar na campanha')).toBeTruthy() + expect(screen.getByRole('region', { name: 'Resultados da busca' })).toBeTruthy() + }) + + it('posts home-search when the drawer query is active', async () => { + vi.mocked(postCampaignJson).mockResolvedValue({ + ok: true, + payload: idleSuggestPayload, + }) + + render() + + fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + fireEvent.change(screen.getByLabelText('Buscar na campanha'), { target: { value: 'cairu' } }) + + await waitFor(() => { + expect(postCampaignJson).toHaveBeenCalledWith( + '/campanha/home-search', + { mode: 'search', query: 'cairu' }, + expect.any(AbortSignal), + ) + }) + }) +})