All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 2m15s
215 lines
8.0 KiB
HTML
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>
|