import React from 'react'; import { QueryClientProvider } from 'react-query'; import { matchSorter } from 'match-sorter'; import ComboSearch, { ComboSearchProps } from './ComboSearch'; import { queryClient } from './shared/queryClient'; import { ComboDepartementsSearch } from './ComboDepartementsSearch'; import { useHiddenField, groupId } from './shared/hooks'; type CommuneResult = { code: string; nom: string; codesPostaux: string[] }; // Avoid hiding similar matches for precise queries (like "Sainte Marie") function searchResultsLimit(term: string) { return term.length > 5 ? 10 : 5; } function expandResultsWithMultiplePostalCodes(term: string, result: unknown) { const results = result as CommuneResult[]; // A single result may have several associated postal codes. // To make the search results more precise, we want to generate // an actual result for each postal code. const expandedResults = results.flatMap((result) => result.codesPostaux.map((codePostal) => ({ ...result, codesPostaux: [codePostal] })) ); // Some very large cities (like Paris) have A LOT of associated postal codes. // As we generated one result per postal code, we now have a lot of results // for the same city. If the number of results is above the threshold, we use // local search to narrow the results. const limit = searchResultsLimit(term); if (expandedResults.length > limit) { return matchSorter(expandedResults, term, { keys: [(item) => `${item.nom} (${item.codesPostaux[0]})`, 'code'], sorter: (rankedItems) => rankedItems }).slice(0, limit + 1); } return expandedResults; } const placeholderDepartements = [ ['63 – Puy-de-Dôme', 'Clermont-Ferrand'], ['77 – Seine-et-Marne', 'Melun'], ['22 – Côtes d’Armor', 'Saint-Brieuc'], ['47 – Lot-et-Garonne', 'Agen'] ] as const; const [placeholderDepartement, placeholderCommune] = placeholderDepartements[ Math.floor(Math.random() * (placeholderDepartements.length - 1)) ]; export default function ComboCommunesSearch({ id, ...props }: ComboSearchProps<CommuneResult> & { id: string }) { const group = groupId(id); const [departementValue, setDepartementValue] = useHiddenField( group, 'departement' ); const [codeDepartement, setCodeDepartement] = useHiddenField( group, 'code_departement' ); const departementDescribedBy = `${id}_departement_notice`; const communeDescribedBy = `${id}_commune_notice`; return ( <QueryClientProvider client={queryClient}> <div> <div className="notice" id={departementDescribedBy}> <p> Choisissez le département dans lequel se situe la commune. Vous pouvez entrer le nom ou le code. </p> </div> <ComboDepartementsSearch {...props} id={!codeDepartement ? id : undefined} describedby={departementDescribedBy} placeholder={placeholderDepartement} addForeignDepartement={false} value={departementValue} onChange={(_, result) => { setDepartementValue(result?.nom ?? ''); setCodeDepartement(result?.code ?? ''); }} /> </div> {codeDepartement ? ( <div> <div className="notice" id={communeDescribedBy}> <p> Choisissez la commune. Vous pouvez entrer le nom ou le code postal. </p> </div> <ComboSearch {...props} id={id} describedby={communeDescribedBy} placeholder={placeholderCommune} scope="communes" scopeExtra={codeDepartement} minimumInputLength={2} transformResult={({ code, nom, codesPostaux }) => [ code, `${nom} (${codesPostaux[0]})` ]} transformResults={expandResultsWithMultiplePostalCodes} /> </div> ) : null} </QueryClientProvider> ); }