mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
Add wizard-municipality-suggest mode to home-search with cold-first ranking (signal → E14 → trend → E10 class → 2022 votes). Wizard municipality step now fetches suggestions when query is idle. Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
152 lines
5.3 KiB
TypeScript
152 lines
5.3 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef, useState } from 'react'
|
|
|
|
import { HomeSearchHitRow } from '@/components/campaign/dashboard/HomeSearchHitRow'
|
|
import { HomeSearchMunicipalityVoteTrailing } from '@/components/campaign/dashboard/HomeSearchMunicipalityVoteTrailing'
|
|
import { useHomeSearchQuery } from '@/components/campaign/dashboard/useHomeSearchQuery'
|
|
import { CampaignSearchInput } from '@/components/campaign/shared/CampaignSearchInput'
|
|
import { CampaignWizardShell } from '@/components/campaign/shared/CampaignWizardShell'
|
|
import { wizardActionHref } from '@/lib/campaignActionRoutes'
|
|
import type {
|
|
HomeSearchMunicipalityHit,
|
|
WizardMunicipalitySearchSuccessResponse,
|
|
} from '@/lib/campaignHomeSearchHits'
|
|
import { HOME_SEARCH_GENERIC_ERROR_MESSAGE } from '@/lib/campaignHomeSearchMessages'
|
|
import { postCampaignJson } from '@/lib/campaignJsonRequest'
|
|
import {
|
|
WIZARD_MUNICIPALITY_SEARCH_EMPTY,
|
|
WIZARD_MUNICIPALITY_SEARCH_LABEL,
|
|
WIZARD_MUNICIPALITY_SEARCH_PLACEHOLDER,
|
|
WIZARD_MUNICIPALITY_STEP_TITLE,
|
|
wizardFlowTitleForSlug,
|
|
} from '@/lib/campaignWizardCopy'
|
|
import { HOME_SEARCH_QUERY_MAX_LENGTH } from '@/lib/schemas/homeSearch'
|
|
|
|
const HOME_SEARCH_ROUTE = '/campanha/home-search'
|
|
const WIZARD_MUNICIPALITY_SEARCH_INPUT_ID = 'wizardMunicipalitySearchQuery'
|
|
|
|
type WizardSearchResultsState =
|
|
| { status: 'idle' }
|
|
| { status: 'loading' }
|
|
| { status: 'success'; municipalities: HomeSearchMunicipalityHit[] }
|
|
| { status: 'error'; message: string }
|
|
|
|
type WizardMunicipalitySearchStepProps = {
|
|
actionSlug: string
|
|
previousHref: string
|
|
}
|
|
|
|
export const WizardMunicipalitySearchStep = ({
|
|
actionSlug,
|
|
previousHref,
|
|
}: WizardMunicipalitySearchStepProps) => {
|
|
const { query, setRaw, isDebouncing } = useHomeSearchQuery()
|
|
const [results, setResults] = useState<WizardSearchResultsState>({ status: 'idle' })
|
|
const requestSeq = useRef(0)
|
|
const suggestMode = !query.isActive
|
|
const searchMode = query.isActive
|
|
const resultsBusy = isDebouncing || results.status === 'loading'
|
|
|
|
useEffect(() => {
|
|
if (!suggestMode && !searchMode) {
|
|
requestSeq.current += 1
|
|
setResults({ status: 'idle' })
|
|
return
|
|
}
|
|
|
|
const seq = ++requestSeq.current
|
|
const controller = new AbortController()
|
|
setResults({ status: 'loading' })
|
|
|
|
const body = suggestMode
|
|
? ({ mode: 'wizard-municipality-suggest' } as const)
|
|
: ({ mode: 'wizard-municipality', query: query.debounced } as const)
|
|
|
|
void (async () => {
|
|
const { ok, payload } = await postCampaignJson<WizardMunicipalitySearchSuccessResponse>(
|
|
HOME_SEARCH_ROUTE,
|
|
body,
|
|
controller.signal,
|
|
)
|
|
|
|
if (seq !== requestSeq.current) return
|
|
|
|
if (!ok || payload.status !== 'success') {
|
|
setResults({ status: 'error', message: HOME_SEARCH_GENERIC_ERROR_MESSAGE })
|
|
return
|
|
}
|
|
|
|
setResults({ status: 'success', municipalities: payload.municipalities })
|
|
})().catch((error: unknown) => {
|
|
if (seq !== requestSeq.current) return
|
|
if (error instanceof DOMException && error.name === 'AbortError') return
|
|
setResults({ status: 'error', message: HOME_SEARCH_GENERIC_ERROR_MESSAGE })
|
|
})
|
|
|
|
return () => {
|
|
controller.abort()
|
|
}
|
|
}, [query.debounced, searchMode, suggestMode])
|
|
|
|
const showEmpty =
|
|
query.isActive && results.status === 'success' && results.municipalities.length === 0
|
|
|
|
return (
|
|
<CampaignWizardShell
|
|
flowTitle={wizardFlowTitleForSlug(actionSlug)}
|
|
stepTitle={WIZARD_MUNICIPALITY_STEP_TITLE}
|
|
isEntryStep
|
|
previousHref={previousHref}
|
|
dismissHref={previousHref}
|
|
>
|
|
<div className="flex flex-col gap-4">
|
|
<CampaignSearchInput
|
|
id={WIZARD_MUNICIPALITY_SEARCH_INPUT_ID}
|
|
label={WIZARD_MUNICIPALITY_SEARCH_LABEL}
|
|
placeholder={WIZARD_MUNICIPALITY_SEARCH_PLACEHOLDER}
|
|
value={query.raw}
|
|
onChange={(event) => setRaw(event.target.value)}
|
|
autoComplete="off"
|
|
enterKeyHint="search"
|
|
aria-busy={resultsBusy || undefined}
|
|
maxLength={HOME_SEARCH_QUERY_MAX_LENGTH}
|
|
/>
|
|
<div
|
|
role="region"
|
|
aria-live="polite"
|
|
aria-label="Resultados da busca"
|
|
aria-busy={resultsBusy || undefined}
|
|
className="min-w-0"
|
|
>
|
|
{results.status === 'error' ? (
|
|
<p className="text-sm text-destructive" role="alert">
|
|
{results.message}
|
|
</p>
|
|
) : null}
|
|
{results.status === 'success' && results.municipalities.length > 0 ? (
|
|
<ul className="flex flex-col">
|
|
{results.municipalities.map((hit) => (
|
|
<li key={hit.slug}>
|
|
<HomeSearchHitRow
|
|
href={wizardActionHref(actionSlug, hit.slug)}
|
|
primary={hit.name}
|
|
secondary={hit.region}
|
|
showPriority={hit.priority === 'alta'}
|
|
wizardNavigation
|
|
trailing={
|
|
<HomeSearchMunicipalityVoteTrailing position={hit.votePosition2022} />
|
|
}
|
|
/>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : null}
|
|
{showEmpty ? (
|
|
<p className="text-sm text-muted-foreground">{WIZARD_MUNICIPALITY_SEARCH_EMPTY}</p>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</CampaignWizardShell>
|
|
)
|
|
}
|