From d639988b8306dafb199385d27c9dda8c32388140 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 05:42:59 +0000 Subject: [PATCH] =?UTF-8?q?B91=20=E2=80=94=20Global=20search=20in=20mobile?= =?UTF-8?q?=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), + ) + }) + }) +})