Merge pull request #85 from fsolla/cursor/pool-i55-655812f2-d74e
B91 — Busca global no bottom drawer (mobile)
This commit is contained in:
commit
03418faa2b
5 changed files with 132 additions and 23 deletions
|
|
@ -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 <HomeSearchProvider value={searchState}>{children}</HomeSearchProvider>
|
||||
}
|
||||
|
||||
/** Input + result groups — requires `CampaignGlobalSearchProvider` unless using the mount. */
|
||||
export const CampaignGlobalSearchBody = CampaignStaffGlobalSearchBody
|
||||
|
||||
/** Provider + default global search body (drawer, smoke tests). */
|
||||
export const CampaignGlobalSearchMount = () => (
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignGlobalSearchBody />
|
||||
</CampaignGlobalSearchProvider>
|
||||
)
|
||||
|
|
@ -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 (
|
||||
<HomeSearchProvider value={searchState}>
|
||||
<div className="h-full min-h-0 w-full" data-home-focused={focused || undefined}>
|
||||
<CampaignHomeLayout
|
||||
actions={actions}
|
||||
focused={focused}
|
||||
searchSlot={
|
||||
searchResults ? (
|
||||
<CampaignHomeStaffSearchSlot searchResults={searchResults} />
|
||||
) : (
|
||||
<CampaignStaffGlobalSearchBody />
|
||||
)
|
||||
}
|
||||
summarySlot={summarySlot}
|
||||
/>
|
||||
</div>
|
||||
</HomeSearchProvider>
|
||||
<div className="h-full min-h-0 w-full" data-home-focused={focused || undefined}>
|
||||
<CampaignHomeLayout
|
||||
actions={actions}
|
||||
focused={focused}
|
||||
searchSlot={
|
||||
searchResults ? (
|
||||
<CampaignHomeStaffSearchSlot searchResults={searchResults} />
|
||||
) : (
|
||||
<CampaignGlobalSearchBody />
|
||||
)
|
||||
}
|
||||
summarySlot={summarySlot}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CampaignHomeStaffChrome = ({
|
||||
actions,
|
||||
searchResults,
|
||||
summarySlot,
|
||||
}: {
|
||||
actions: ReactNode
|
||||
searchResults?: ReactNode
|
||||
summarySlot?: ReactNode
|
||||
}) => (
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignHomeStaffChromeInner
|
||||
actions={actions}
|
||||
searchResults={searchResults}
|
||||
summarySlot={summarySlot}
|
||||
/>
|
||||
</CampaignGlobalSearchProvider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
) : (
|
||||
<p className="text-sm text-muted-foreground">Nenhuma ação rápida nesta página.</p>
|
||||
)}
|
||||
{expanded ? <CampaignGlobalSearchMount /> : null}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
67
tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
Normal file
67
tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
Normal file
|
|
@ -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(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
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(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
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),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue