mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 02:23:07 +00:00
B91 — Global search in mobile quick-actions drawer
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 <fsolla@users.noreply.github.com>
This commit is contained in:
parent
90d130af33
commit
d639988b83
4 changed files with 128 additions and 21 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,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 (
|
||||
<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} />
|
||||
) : (
|
||||
<CampaignStaffGlobalSearchBody />
|
||||
)
|
||||
}
|
||||
summarySlot={summarySlot}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CampaignHomeStaffChrome = ({
|
||||
actions,
|
||||
searchResults,
|
||||
summarySlot,
|
||||
}: {
|
||||
actions: ReactNode
|
||||
searchResults?: ReactNode
|
||||
summarySlot?: ReactNode
|
||||
}) => (
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignHomeStaffChromeInner
|
||||
actions={actions}
|
||||
searchResults={searchResults}
|
||||
summarySlot={summarySlot}
|
||||
/>
|
||||
</CampaignGlobalSearchProvider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
) : (
|
||||
<p className="text-sm text-muted-foreground">Nenhuma ação rápida nesta página.</p>
|
||||
)}
|
||||
<CampaignGlobalSearchMount />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
|
|
|||
67
tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
Normal file
67
tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
Normal file
|
|
@ -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(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
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(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
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),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue