Source AdvancedAddressSearch
Source AdvancedAddressSearch
Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.
Fichiers inclus
packages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.declaration.tspackages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.config.tspackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressCommuneCombobox.sveltepackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressNumeroCombobox.sveltepackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressStreetCombobox.svelte
packages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type AdvancedSearchAddressFullConfig, advancedSearchAddressFullConfigSchema,} from './advanced-address-search.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./AdvancedAddressSearch.svelte').then((AdvancedAddressSearch) => widgetFactorySvelte(AdvancedAddressSearch), ), schema: () => advancedSearchAddressFullConfigSchema,} satisfies WidgetDeclaration;
export type AdvancedAddressSearchProps = WidgetProps<AdvancedSearchAddressFullConfig>;packages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n/i18n.schema';import { apiFeatureSymbolsSchema } from '$lib/api/symbol';import { z } from 'zod';
export const searchAddressTranslations = { 'search-municipality-label': { fr: 'Commune / Code postal', nl: 'NL - Commune / Code postal', }, 'search-street-label': { fr: 'Rue', nl: 'NL - Rue', }, 'search-number-label': { fr: 'Numéro', nl: 'NL - Numéro', }, 'i-go': { fr: "J'y vais", nl: 'Ik ga', },};
export const advancedsearchAddressConfigSchema = z .object({ symbolConfig: apiFeatureSymbolsSchema.prefault({}), }) .prefault({});export const advancedSearchAddressConfigSchema = z.object({ config: advancedsearchAddressConfigSchema.prefault({}), i18n: i18nSchemaFrom(searchAddressTranslations),});export const advancedSearchAddressFullConfigSchema = defineWidgetConfig({ title: { fr: 'Rechercher une adresse', nl: 'NL - Rechercher une adresse', }, icon: { lucide: 'MapPinned', }, inToolbar: inToolbarSchemaFrom({ type: 'button', }), i18n: i18nSchemaFrom(searchAddressTranslations), config: advancedsearchAddressConfigSchema,});
export type AdvancedSearchAddressFullConfig = z.infer<typeof advancedSearchAddressFullConfigSchema>;packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressCommuneCombobox.svelte
<script lang="ts"> import { type GeolisterResponsePosition, IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import { debounceState, derivedWithDestroy, queryState } from '$lib/api/utils'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox';
interface CommuneValue { cp?: string; ins?: string; }
interface Props { selectedCommune?: string; }
let { selectedCommune = $bindable() }: Props = $props();
let filterText = $state(''); const debouncedFilter = debounceState(() => filterText, 500); const filter = $derived(debouncedFilter.debounced?.length > 2 ? debouncedFilter.debounced : null);
const restQueryClient = derivedWithDestroy( () => searchCommune(filter), (query) => query?.cancel(), );
const searchQuery = queryState({ queryFn: ({ query }) => query!.then(mapToSelectItem), inputFn: () => ({ query: restQueryClient.value }), disabled: () => !restQueryClient.value, });
$effect(() => { if (!selectedCommune) { filterText = ''; searchQuery.data = []; } });
// We need to be able to search commune based on the INS or the CP. Thus, we need an internal // object to store both those params and filter on them. // As the second endpoint of the AdvancedAddressSearch needs the INS, we set the "external" value to // the INS. let internalSelectedCommune = $state<CommuneValue | undefined>(); $effect(() => { if (!internalSelectedCommune) { selectedCommune = undefined; } if (internalSelectedCommune && internalSelectedCommune.ins) { selectedCommune = internalSelectedCommune.ins; } });
function searchCommune(filter: string | null) { if (!filter) { return null; } // Si l'utilisateur a encodé un code postal, le paramètre à envoyer au service est différent const cp = parseInt(filter); if (isNaN(cp)) { return IcarGeolocalisationService.geolistAsJson({ city: filter }); } else { return IcarGeolocalisationService.geolistAsJson({ zone: filter }); } }
function filterCommunes(filterText: string, options: ComboboxSelectedItem<CommuneValue | undefined>[]) { const searchText = filterText.toLowerCase(); return options.filter((x) => { return ( (x.label && x.label.toLowerCase().indexOf(searchText) > -1) || (x.value && x.value.ins && x.value.ins.indexOf(searchText) > -1) || (x.value && x.value.cp && x.value.cp.indexOf(searchText) > -1) ); }); }
function mapToSelectItem(res: GeolisterResponsePosition) { if (res.candidates) { return res.candidates .filter((x) => x.city != undefined) .reduce( (acc, candidate) => { const zone = candidate.zone; const city = candidate.city;
// As the second endpoint of the AdvancedAddressSearch needs an INS, we filter out all // values that do not provide an ins if (!city || !city.name || !city.ident) return acc;
let name = city.name; const value: CommuneValue = { cp: undefined, ins: undefined, };
// If the candidate as a zone (object where the CP is), we add it to the value if (zone) { value.cp = zone.ident; } value.ins = city.ident;
if (!acc.set.has(value.ins)) { acc.set.add(value.ins); acc.list.push({ label: name!, value, }); } return acc; }, { list: [] as ComboboxSelectedItem<CommuneValue>[], set: new Set<string>() }, ).list; }
return []; }</script>
<ApiCombobox bind:value={internalSelectedCommune} bind:filterText filterFunction={filterCommunes} loading={searchQuery.loading} lazy options={searchQuery.data}/>packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressNumeroCombobox.svelte
<script lang="ts"> import { type GeolisterResponsePosition, IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import { debounceState, derivedWithDestroy, queryState } from '$lib/api/utils'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox';
interface Props { selectedStreet?: string; selectedNumber?: string; }
let { selectedStreet, selectedNumber = $bindable() }: Props = $props();
let filterText = $state(''); const debouncedFilter = debounceState(() => filterText, 500); const filter = $derived.by(() => { const value = debouncedFilter.debounced; return value && value.length > 0 ? value : '*'; }); const disabled = $derived.by(() => !selectedStreet || !searchQuery || !searchQuery.data);
$effect(() => { if (!selectedNumber) { filterText = ''; searchQuery.data = []; } });
$effect(() => { if (selectedStreet || !selectedStreet) { selectedNumber = undefined; } });
const restQueryClient = derivedWithDestroy( () => filter && selectedStreet ? IcarGeolocalisationService.geolistAsJson({ street: selectedStreet, house: filter }) : null, (query) => query?.cancel(), );
const searchQuery = queryState({ queryFn: ({ query }) => query!.then(mapToSelectItem), inputFn: () => ({ query: restQueryClient.value }), disabled: () => !restQueryClient.value, });
function mapToSelectItem(res: GeolisterResponsePosition) { if (res.candidates) { return res.candidates .filter((x) => x.house !== undefined) .reduce( (acc, candidate) => { if (candidate.house) { const { ident, name } = candidate.house; if (ident && !acc.set.has(ident)) { acc.set.add(ident); acc.list.push({ label: name!, value: ident?.toString(), }); } } return acc; }, { list: [] as ComboboxSelectedItem<string>[], set: new Set<number>() }, ).list; } return []; }</script>
<ApiCombobox bind:value={selectedNumber} bind:filterText lazy loading={searchQuery.loading} options={searchQuery.data} {disabled}/>packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressSearch.svelte
<script lang="ts"> import { IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import type { ApiGeoJSON } from '$lib/api/domain/api-geojson.model'; import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import { Button } from '$lib/components/shadcn/ui/button'; import { Label } from '$lib/components/shadcn/ui/label'; import { onDestroy } from 'svelte'; import Loader from '$lib/components/common/Loader.svelte'; import CommuneCombobox from './AdvancedAddressCommuneCombobox.svelte'; import NumeroCombobox from './AdvancedAddressNumeroCombobox.svelte'; import StreetCombobox from './AdvancedAddressStreetCombobox.svelte'; import type { ApiFeature } from '$lib/api/feature'; import type { AdvancedAddressSearchProps } from './advanced-address-search.declaration'; import StringUtils from '$lib/api/utils/string.utils';
let { fullConfig }: AdvancedAddressSearchProps = $props();
const i18n = getI18n(fullConfig.i18n); const mapManager = getMapManager();
let loading = $state(false); let selectedCommune = $state<string | undefined>(undefined); let selectedStreet = $state<string | undefined>(undefined); let selectedNumber = $state<string | undefined>(undefined); let currentFeature: ApiFeature | undefined; let oldFeature: ApiFeature | undefined;
onDestroy(() => { if (oldFeature) { mapManager.tools.highlight.unhighlightFeature(oldFeature); } if (currentFeature) { mapManager.tools.highlight.unhighlightFeature(currentFeature); } });
function search() { loading = true; const number = StringUtils.isNullOrEmpty(selectedNumber) ? undefined : selectedNumber; const street = number != undefined || StringUtils.isNullOrEmpty(selectedStreet) ? undefined : selectedStreet; const commune = number != undefined || street != undefined || StringUtils.isNullOrEmpty(selectedCommune) ? undefined : selectedCommune; IcarGeolocalisationService.geolistAsJson({ city: commune, street: street, house: number, geom: true, }).then( (res) => { oldFeature = currentFeature; if (res.candidates && res.candidates.length > 0) { const bestResult = res.candidates.sort((a, b) => (a.score && b.score ? b.score - a.score : 0))[0]; const bestResultGeometry = bestResult.house?.geometry ?? bestResult.street?.geometry ?? bestResult.city?.geometry; handleHighlight(bestResultGeometry as ApiGeoJSON); } loading = false; }, (err) => { console.error(err); loading = false; }, ); }
function handleHighlight(geoJSONGeometry: ApiGeoJSON | undefined) { oldFeature = currentFeature; if (geoJSONGeometry) { currentFeature = mapManager.tools.featureConverter.geoJSON.fromGeoJSON(geoJSONGeometry)[0]; mapManager.tools.highlight.highlightFeature(currentFeature); } else { currentFeature = undefined; } if (oldFeature) { mapManager.tools.highlight.unhighlightFeature(oldFeature); } if (currentFeature) { mapManager.tools.zoom.zoomToFeature(currentFeature); } }
function reset() { selectedCommune = undefined; selectedStreet = undefined; selectedNumber = undefined; if (currentFeature) { mapManager.tools.highlight.unhighlightFeature(currentFeature); } }</script>
<div class="gv-space-y-3"> <div> <Label class="gv-font-bold">{i18n('search-municipality-label')}</Label> <CommuneCombobox bind:selectedCommune /> </div> <div> <Label class="gv-font-bold">{i18n('search-street-label')}</Label> <StreetCombobox {selectedCommune} bind:selectedStreet /> </div> <div> <Label class="gv-font-bold">{i18n('search-number-label')}</Label> <NumeroCombobox {selectedStreet} bind:selectedNumber /> </div> {#if loading} <div class="gv-p-4 gv-flex gv-justify-center gv-items-center"> <Loader /> </div> {/if} <div class="gv-pt-5"> <Button disabled={!selectedCommune} onclick={() => search()} size="sm">{i18n('i-go')}</Button> <Button variant="secondary" onclick={() => reset()} size="sm">{i18n('common.reset')}</Button> </div></div>packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressStreetCombobox.svelte
<script lang="ts"> import { type GeolisterResponsePosition, IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import { debounceState, derivedWithDestroy, queryState } from '$lib/api/utils'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox';
interface Props { selectedStreet?: string; selectedCommune?: string; }
let { selectedStreet = $bindable(), selectedCommune }: Props = $props();
let filterText = $state(''); const debouncedFilter = debounceState(() => filterText, 500); const filter = $derived(debouncedFilter.debounced?.length > 2 ? debouncedFilter.debounced : null); const disabled = $derived.by(() => !selectedCommune || !searchQuery || !searchQuery.data);
const restQueryClient = derivedWithDestroy( () => (filter ? IcarGeolocalisationService.geolistAsJson({ city: selectedCommune, street: filter }) : null), (query) => query?.cancel(), );
$effect(() => { if (!selectedStreet) { filterText = ''; searchQuery.data = []; } });
// If the commune changes, selected street has to be reset $effect(() => { if (selectedCommune) { selectedStreet = undefined; } });
const searchQuery = queryState({ queryFn: ({ query }) => query!.then(mapToSelectItem), inputFn: () => ({ query: restQueryClient.value }), disabled: () => !restQueryClient.value, });
function mapToSelectItem(res: GeolisterResponsePosition) { if (res.candidates) { return res.candidates .filter((x) => x.street != undefined) .reduce( (acc, candidate) => { if (candidate.street) { const { ident, name } = candidate.street; if (ident && !acc.set.has(ident)) { acc.set.add(ident); acc.list.push({ label: name!, value: ident?.toString(), }); } } return acc; }, { list: [] as ComboboxSelectedItem<string>[], set: new Set<number>() }, ).list; }
return []; }</script>
<ApiCombobox bind:value={selectedStreet} bind:filterText lazy loading={searchQuery.loading} options={searchQuery.data} {disabled}/>