Add search and PARCOURSMOB integration
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 40s
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 40s
This commit is contained in:
214
themes/mms43/layouts/_partials/search-block.html
Normal file
214
themes/mms43/layouts/_partials/search-block.html
Normal file
@@ -0,0 +1,214 @@
|
||||
{{/*
|
||||
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=""
|
||||
: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=""
|
||||
: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://api-adresse.data.gouv.fr/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>
|
||||
Reference in New Issue
Block a user