From d639988b8306dafb199385d27c9dda8c32388140 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 05:42:59 +0000 Subject: [PATCH 1/4] =?UTF-8?q?B91=20=E2=80=94=20Global=20search=20in=20mo?= =?UTF-8?q?bile=20quick-actions=20drawer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract CampaignGlobalSearchMount (provider + body) from home chrome so the same home-search contract mounts in CampaignQuickActionsDrawer when expanded. Refactor CampaignHomeStaffChrome to use the provider. Closes #55 Co-authored-by: Francisco Solla --- .../dashboard/CampaignGlobalSearchMount.tsx | 23 +++++++ .../dashboard/CampaignHomeStaffChrome.tsx | 57 ++++++++++------ .../shell/CampaignQuickActionsDrawer.tsx | 2 + .../campaignQuickActionsDrawer.unit.spec.tsx | 67 +++++++++++++++++++ 4 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx create mode 100644 tests/unit/campaignQuickActionsDrawer.unit.spec.tsx 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..596387fb 100644 --- a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx +++ b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx @@ -2,15 +2,15 @@ import type { ReactNode } from 'react' +import { 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 +24,7 @@ const CampaignHomeStaffSearchSlot = ({ searchResults }: { searchResults: ReactNo ) } -export const CampaignHomeStaffChrome = ({ +const CampaignHomeStaffChromeInner = ({ actions, searchResults, summarySlot, @@ -34,25 +34,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..30b9f8a4 100644 --- a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx +++ b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx @@ -3,6 +3,7 @@ import { useCallback, useState } from 'react' import { CampaignHomeActionStrip } from '@/components/campaign/dashboard/CampaignHomeActionStrip' +import { CampaignGlobalSearchMount } from '@/components/campaign/dashboard/CampaignGlobalSearchMount' import { Drawer, DrawerContent, DrawerTitle } from '@/components/ui/Drawer' import { QUICK_ACTIONS_SNAP_COLLAPSED, @@ -103,6 +104,7 @@ export const CampaignQuickActionsDrawer = ({ ) : (

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

)} + diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx new file mode 100644 index 00000000..38564915 --- /dev/null +++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx @@ -0,0 +1,67 @@ +import { cleanup, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +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', async () => { + vi.mocked(postCampaignJson).mockResolvedValue({ + ok: true, + payload: idleSuggestPayload, + }) + + const user = userEvent.setup() + render() + + expect(screen.queryByLabelText('Buscar na campanha')).toBeNull() + + await user.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + + 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, + }) + + const user = userEvent.setup() + render() + + await user.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + await user.type(screen.getByLabelText('Buscar na campanha'), 'cairu') + + await vi.waitFor(() => { + expect(postCampaignJson).toHaveBeenCalledWith( + '/campanha/home-search', + { mode: 'search', query: 'cairu' }, + expect.any(AbortSignal), + ) + }) + }) +}) From 5ec167f8dc08810ae03a66d46c757c43aa267cc9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 05:44:39 +0000 Subject: [PATCH 2/4] =?UTF-8?q?B91=20=E2=80=94=20Fix=20drawer=20search=20u?= =?UTF-8?q?nit=20test=20assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Solla --- .../campaignQuickActionsDrawer.unit.spec.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx index 38564915..a5297fec 100644 --- a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx +++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx @@ -1,5 +1,4 @@ -import { cleanup, render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' +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' @@ -27,19 +26,22 @@ afterEach(() => { }) describe('CampaignQuickActionsDrawer (B91)', () => { - it('shows global search in the expanded snap', async () => { + it('shows global search in the expanded snap', () => { vi.mocked(postCampaignJson).mockResolvedValue({ ok: true, payload: idleSuggestPayload, }) - const user = userEvent.setup() render() - expect(screen.queryByLabelText('Buscar na campanha')).toBeNull() + const context = document.getElementById('quickActionContext') + expect(context?.className).toContain('hidden') + expect(context?.getAttribute('data-snap')).toBe('collapsed') - await user.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + 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() }) @@ -50,13 +52,12 @@ describe('CampaignQuickActionsDrawer (B91)', () => { payload: idleSuggestPayload, }) - const user = userEvent.setup() render() - await user.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) - await user.type(screen.getByLabelText('Buscar na campanha'), 'cairu') + fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' })) + fireEvent.change(screen.getByLabelText('Buscar na campanha'), { target: { value: 'cairu' } }) - await vi.waitFor(() => { + await waitFor(() => { expect(postCampaignJson).toHaveBeenCalledWith( '/campanha/home-search', { mode: 'search', query: 'cairu' }, From c73034aa77ddbd014babb8cca49e85eafe855dc6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 05:46:42 +0000 Subject: [PATCH 3/4] =?UTF-8?q?B91=20=E2=80=94=20Mount=20drawer=20search?= =?UTF-8?q?=20only=20when=20expanded;=20unify=20body=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Solla --- .../campaign/dashboard/CampaignHomeStaffChrome.tsx | 5 ++--- src/components/campaign/shell/CampaignQuickActionsDrawer.tsx | 2 +- tests/unit/campaignQuickActionsDrawer.unit.spec.tsx | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx index 596387fb..24becd79 100644 --- a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx +++ b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx @@ -2,10 +2,9 @@ import type { ReactNode } from 'react' -import { CampaignGlobalSearchProvider } from '@/components/campaign/dashboard/CampaignGlobalSearchMount' +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 { useHomeSearch } from '@/components/campaign/dashboard/HomeSearchContext' import { HomeSearchResultsProvider, @@ -45,7 +44,7 @@ const CampaignHomeStaffChromeInner = ({ searchResults ? ( ) : ( - + ) } summarySlot={summarySlot} diff --git a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx index 30b9f8a4..06ca3723 100644 --- a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx +++ b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx @@ -104,7 +104,7 @@ export const CampaignQuickActionsDrawer = ({ ) : (

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

)} - + {expanded ? : null} diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx index a5297fec..8481f257 100644 --- a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx +++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx @@ -34,12 +34,11 @@ describe('CampaignQuickActionsDrawer (B91)', () => { render() - const context = document.getElementById('quickActionContext') - expect(context?.className).toContain('hidden') - expect(context?.getAttribute('data-snap')).toBe('collapsed') + 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() From a9b0db7a9956a93617859d9903d72d2ac2837f2b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 05:47:26 +0000 Subject: [PATCH 4/4] =?UTF-8?q?B91=20=E2=80=94=20Prettier=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../campaign/dashboard/CampaignHomeStaffChrome.tsx | 5 ++++- src/components/campaign/shell/CampaignQuickActionsDrawer.tsx | 2 +- src/lib/campaignQuickActionRegistry.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx index 24becd79..41e3d69c 100644 --- a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx +++ b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx @@ -2,7 +2,10 @@ import type { ReactNode } from 'react' -import { CampaignGlobalSearchBody, CampaignGlobalSearchProvider } from '@/components/campaign/dashboard/CampaignGlobalSearchMount' +import { + CampaignGlobalSearchBody, + CampaignGlobalSearchProvider, +} from '@/components/campaign/dashboard/CampaignGlobalSearchMount' import { CampaignHomeLayout } from '@/components/campaign/dashboard/CampaignHomeLayout' import { CampaignHomeSearch } from '@/components/campaign/dashboard/CampaignHomeSearch' import { useHomeSearch } from '@/components/campaign/dashboard/HomeSearchContext' diff --git a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx index 06ca3723..c5af7831 100644 --- a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx +++ b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx @@ -2,8 +2,8 @@ import { useCallback, useState } from 'react' -import { CampaignHomeActionStrip } from '@/components/campaign/dashboard/CampaignHomeActionStrip' import { CampaignGlobalSearchMount } from '@/components/campaign/dashboard/CampaignGlobalSearchMount' +import { CampaignHomeActionStrip } from '@/components/campaign/dashboard/CampaignHomeActionStrip' import { Drawer, DrawerContent, DrawerTitle } from '@/components/ui/Drawer' import { QUICK_ACTIONS_SNAP_COLLAPSED, 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,