Files
mms43-v2/themes/mms43/layouts/_partials/search-block.html
Arnaud Delcasse 2837630e53
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 2m15s
search
2026-03-27 14:50:41 +01:00

215 lines
8.0 KiB
HTML

{{/*
Partial: search-block
Paramètres:
- showTitle: bool (afficher le titre, défaut: true)
- action: string (URL de soumission du formulaire)
- method: string (méthode du formulaire, défaut: GET)
*/}}
{{ $showTitle := default true .showTitle }}
{{ $action := .action }}
{{ $method := default "GET" .method }}
{{ $iconLocation := resources.Get "images/picto/location_on_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg" }}
{{ $iconCalendar := resources.Get "images/picto/calendar_today_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg" }}
{{ $iconSchedule := resources.Get "images/picto/schedule_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg" }}
{{ $iconSearch := resources.Get "images/picto/search_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg" }}
{{ $iconSwap := resources.Get "images/picto/sync_alt_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg" }}
<section class="search-block" x-data="searchBlock()">
{{ if $showTitle }}
<h2 class="search-title">{{ site.Params.search.title }}</h2>
{{ end }}
<form class="search-form"{{ with $action }} action="{{ . }}"{{ end }}{{ with $method }} method="{{ . }}"{{ end }} @submit="validateForm($event)">
<div class="form-group-locations">
<div class="form-group autocomplete-container">
<label for="departure-input">
{{ if $iconLocation }}<img src="{{ $iconLocation.RelPermalink }}" alt="" class="label-icon" />{{ end }}
{{ site.Params.search.labelDepart }}
</label>
<input type="hidden" name="departure" x-model="departure.address" />
<input type="text" id="departure-input" x-ref="departure"
x-model="departure.input"
@input.debounce.300ms="autocomplete('departure')"
@focus="departure.showResults = departure.results.length > 0"
@click.outside="departure.showResults = false"
autocomplete="off"
placeholder="{{ site.Params.search.placeholderDepart }}"
:class="{ 'input-error': errors.departure }"
required />
<ul class="autocomplete-results" x-show="departure.showResults && departure.results.length > 0" x-cloak>
<template x-for="item in departure.results" :key="item.properties.id">
<li @click="selectAddress('departure', item)" x-text="item.properties.label"></li>
</template>
</ul>
</div>
<button type="button" class="swap-btn" @click="swapLocations()" aria-label="Inverser départ et destination">
{{ if $iconSwap }}<img src="{{ $iconSwap.RelPermalink }}" alt="" />{{ end }}
</button>
<div class="form-group autocomplete-container">
<label for="destination-input">
{{ if $iconLocation }}<img src="{{ $iconLocation.RelPermalink }}" alt="" class="label-icon" />{{ end }}
{{ site.Params.search.labelDestination }}
</label>
<input type="hidden" name="destination" x-model="destination.address" />
<input type="text" id="destination-input" x-ref="destination"
x-model="destination.input"
@input.debounce.300ms="autocomplete('destination')"
@focus="destination.showResults = destination.results.length > 0"
@click.outside="destination.showResults = false"
autocomplete="off"
placeholder="{{ site.Params.search.placeholderDestination }}"
:class="{ 'input-error': errors.destination }"
required />
<ul class="autocomplete-results" x-show="destination.showResults && destination.results.length > 0" x-cloak>
<template x-for="item in destination.results" :key="item.properties.id">
<li @click="selectAddress('destination', item)" x-text="item.properties.label"></li>
</template>
</ul>
</div>
</div>
<div class="form-group-row">
<div class="form-group">
<label for="departuredate">
{{ if $iconCalendar }}<img src="{{ $iconCalendar.RelPermalink }}" alt="" class="label-icon" />{{ end }}
{{ site.Params.search.labelDate }}
</label>
<input type="date" id="departuredate" name="departuredate" x-model="departuredate"
:class="{ 'input-error': errors.departuredate }"
required />
</div>
<div class="form-group">
<label for="departuretime">
{{ if $iconSchedule }}<img src="{{ $iconSchedule.RelPermalink }}" alt="" class="label-icon" />{{ end }}
{{ site.Params.search.labelHeure }}
</label>
<input type="time" id="departuretime" name="departuretime" x-model="departuretime"
:class="{ 'input-error': errors.departuretime }"
required />
</div>
</div>
<button type="submit" class="search-btn">
{{ if $iconSearch }}<img src="{{ $iconSearch.RelPermalink }}" alt="" class="btn-icon" />{{ end }}
{{ site.Params.search.buttonText }}
</button>
</form>
</section>
<script>
function searchBlock() {
const data = window.__PARCOURSMOB_DATA__ || {};
return {
departure: {
input: data.departure?.properties?.label || '',
address: data.departure ? JSON.stringify(data.departure) : '',
results: [],
showResults: false
},
destination: {
input: data.destination?.properties?.label || '',
address: data.destination ? JSON.stringify(data.destination) : '',
results: [],
showResults: false
},
departuredate: data.departure_date || '',
departuretime: data.departure_time || '',
errors: {
departure: false,
destination: false,
departuredate: false,
departuretime: false
},
validateForm(event) {
// Reset errors
this.errors.departure = false;
this.errors.destination = false;
this.errors.departuredate = false;
this.errors.departuretime = false;
let isValid = true;
// Vérifier que le départ a été sélectionné (pas juste du texte tapé)
if (!this.departure.address) {
this.errors.departure = true;
isValid = false;
}
// Vérifier que la destination a été sélectionnée
if (!this.destination.address) {
this.errors.destination = true;
isValid = false;
}
// Vérifier la date
if (!this.departuredate) {
this.errors.departuredate = true;
isValid = false;
}
// Vérifier l'heure
if (!this.departuretime) {
this.errors.departuretime = true;
isValid = false;
}
if (!isValid) {
event.preventDefault();
}
},
async autocomplete(field) {
const fieldData = this[field];
// Réinitialiser l'adresse si l'utilisateur modifie le texte
fieldData.address = '';
this.errors[field] = false;
if (!fieldData.input || fieldData.input.length < 3) {
fieldData.results = [];
fieldData.showResults = false;
return;
}
try {
const response = await fetch(`https://data.geopf.fr/geocodage/search/?q=${encodeURIComponent(fieldData.input)}&limit=5`);
const json = await response.json();
fieldData.results = json.features || [];
fieldData.showResults = fieldData.results.length > 0;
} catch (e) {
console.error('Erreur autocomplétion:', e);
fieldData.results = [];
fieldData.showResults = false;
}
},
selectAddress(field, item) {
const fieldData = this[field];
fieldData.input = item.properties.label;
fieldData.address = JSON.stringify(item);
fieldData.results = [];
fieldData.showResults = false;
this.errors[field] = false;
},
swapLocations() {
const tempInput = this.departure.input;
const tempAddress = this.departure.address;
this.departure.input = this.destination.input;
this.departure.address = this.destination.address;
this.destination.input = tempInput;
this.destination.address = tempAddress;
}
};
}
</script>