961 lines
34 KiB
Vue
961 lines
34 KiB
Vue
<template>
|
|
<div
|
|
ref="searchModel"
|
|
class="search-model-component row w-100 mx-auto gx-5"
|
|
>
|
|
<!-- TEXTS MOBILE -->
|
|
<div class="d-md-none col-12 col-md-7 col-xl-8 text-center">
|
|
<h2 ref="modelSubtitleMobile">
|
|
{{ textsParameters.subtitle }}
|
|
</h2>
|
|
<p
|
|
ref="modelDescriptionMobile"
|
|
v-html="textsParameters.description"
|
|
/>
|
|
</div>
|
|
<!-- FILTERS -->
|
|
<div class="col-12 col-md-5 col-xl-4">
|
|
<div
|
|
class="row filters-list"
|
|
ref="filtersList"
|
|
>
|
|
<!-- TEXT FILTER -->
|
|
<div class="col-12 mb-3 z-1">
|
|
<div class="search-custom bottom-left-shadow">
|
|
<SearchBelongsToDropdownComponent
|
|
name="words"
|
|
:multiple="true"
|
|
:fieldname="searchParameters.fieldName"
|
|
:modeltarget="searchParameters.modelTarget"
|
|
:modellistpaginate="searchParameters.modelListPaginate"
|
|
:modelselected="searchParameters.selectedList"
|
|
:routename="searchParameters.routeList"
|
|
@selected-value-changed="getModelByText"
|
|
:placeholder="searchParameters.placeholder"
|
|
:disabled="loading || paginationParameters.loading"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<!-- FILTERS LIST -->
|
|
<div class="col-12">
|
|
<button
|
|
class="d-md-none btn btn-secondary w-100 mb-3"
|
|
type="button"
|
|
:title="__('Ouvrir le panneau des filtres')"
|
|
data-bs-toggle="offcanvas"
|
|
data-bs-target="#offcanvasRecipesFilters"
|
|
aria-controls="offcanvasRecipesFilters"
|
|
>
|
|
<span class="pe-2">
|
|
{{ __('Filtres') }}
|
|
</span>
|
|
<img
|
|
src="@/images/recipes/filter.svg"
|
|
:alt="__('Icône de filtres')"
|
|
>
|
|
</button>
|
|
<div
|
|
class="offcanvas-md offcanvas-start"
|
|
tabindex="-1"
|
|
id="offcanvasRecipesFilters"
|
|
aria-labelledby="offcanvasRecipesFiltersLabel"
|
|
>
|
|
<div class="offcanvas-header">
|
|
<button
|
|
type="button"
|
|
class="btn btn-link text-decoration-none f-catamaran fw-semibold"
|
|
data-bs-dismiss="offcanvas"
|
|
data-bs-toggle="offcanvas"
|
|
data-bs-target="#offcanvasRecipesFilters"
|
|
:aria-label="__('Fermer le menu')"
|
|
>
|
|
<FontAwesomeIcon
|
|
icon="fa-solid fa-xmark"
|
|
class="me-2"
|
|
/>
|
|
{{ __('Fermer') }}
|
|
</button>
|
|
</div>
|
|
<div class="offcanvas-body py-0">
|
|
<div class="row w-100 mx-auto">
|
|
<div
|
|
class="col-12 bottom-left-shadow p-0"
|
|
v-for="(filter, filterIndex) in filters"
|
|
:key="filterIndex"
|
|
>
|
|
<div class="line-border-small mb-3">
|
|
<button
|
|
class="btn btn-transparent d-flex justify-content-between align-items-center fs-4 text-start border-0 shadow-none w-100"
|
|
type="button"
|
|
data-bs-toggle="collapse"
|
|
:data-bs-target="'#' + slugify(filter.title, slugifyOptions)"
|
|
aria-expanded="true"
|
|
:aria-controls="slugify(filter.title, slugifyOptions)"
|
|
>
|
|
{{ filter.title }}
|
|
<span class="f-bourton-base">></span>
|
|
</button>
|
|
<div
|
|
class="collapse show"
|
|
:id="slugify(filter.title, slugifyOptions)"
|
|
>
|
|
<fieldset
|
|
:inert="loading || paginationParameters.loading"
|
|
class="d-flex justify-content-start align-items-center flex-wrap px-4 py-2"
|
|
>
|
|
<div
|
|
class="w-fit"
|
|
v-for="(model, modelIndex) in filter.models"
|
|
:key="modelIndex"
|
|
>
|
|
<input
|
|
class="d-none"
|
|
type="checkbox"
|
|
:name="slugify(filter.title, slugifyOptions)"
|
|
:id="`${slugify(filter.title, slugifyOptions)}-${ modelIndex }`"
|
|
@click="getModelByFilters(filter, model, $event)"
|
|
:checked="filtersDetailsParameters.selectedFilter === slugify(String(model.label ?? model.title), slugifyOptions)"
|
|
:disabled="loading || paginationParameters.loading"
|
|
>
|
|
<label
|
|
:class="[
|
|
'form-check-label btn position-relative border-0 mb-3 me-3 px-2 py-1',
|
|
filtersDetailsParameters.selectedFilter === slugify(String(model.label ?? model.title), slugifyOptions) ? 'btn-secondary' : 'btn-tertiary',
|
|
]"
|
|
:style="[filtersDetailsParameters.selectedFilter === slugify(String(model.label ?? model.title), slugifyOptions) && model.color !== undefined ? `background-color:${model.color}!important;` : '']"
|
|
:for="`${slugify(filter.title, slugifyOptions)}-${ modelIndex }`"
|
|
data-bs-dismiss="offcanvas"
|
|
data-bs-target="#offcanvasRecipesFilters"
|
|
>
|
|
{{ model.label ?? model.title }}
|
|
<span
|
|
v-if="filtersDetailsParameters.selectedFilter === slugify(String(model.label ?? model.title), slugifyOptions)"
|
|
:class="[
|
|
'filters-btn d-flex justify-content-center align-items-center position-absolute',
|
|
{'bg-secondary':filtersDetailsParameters.selectedFilter === slugify(String(model.label ?? model.title), slugifyOptions)}
|
|
]"
|
|
:style="`background-color:${model.color}!important;`"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-xmark" />
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- MODELS LIST -->
|
|
<div class="col-12 col-md-7 col-xl-8">
|
|
<div class="row gx-5">
|
|
<!-- TEXTS DESKTOP -->
|
|
<div class="d-none d-md-block col-12 text-start">
|
|
<h2 ref="modelSubtitle">
|
|
{{ textsParameters.subtitle }}
|
|
</h2>
|
|
<p
|
|
ref="modelDescription"
|
|
v-html="textsParameters.description"
|
|
/>
|
|
</div>
|
|
<!-- MODELS LIST -->
|
|
<template v-if="!loading">
|
|
<div
|
|
v-if="filtersDetailsParameters.selectedFilter.length && filters[getFilterById(filtersDetailsParameters.selectedFilterId)].children?.models.length"
|
|
ref="childrenList"
|
|
class="col-12"
|
|
>
|
|
<div class="children-list position-relative px-5 mb-3">
|
|
<div class="swiper swiper-children overflow-hidden position-relative p-2 z-1">
|
|
<div class="swiper-wrapper w-fit">
|
|
<div
|
|
class="swiper-slide w-fit"
|
|
v-for="(model, modelIndex) in filters[getFilterById(filtersDetailsParameters.selectedFilterId)].children?.models"
|
|
:key="modelIndex"
|
|
>
|
|
<input
|
|
class="d-none"
|
|
type="checkbox"
|
|
name="child"
|
|
@click="getModelByChildren(filters[getFilterById(filtersDetailsParameters.selectedFilterId)].children!, model, $event)"
|
|
:id="`child-${modelIndex}`"
|
|
:checked="filtersDetailsParameters.selectedChildFilter === slugify(String(model.label ?? model.title), slugifyOptions)"
|
|
>
|
|
<label
|
|
:class="[
|
|
'form-check-label btn position-relative border-0 px-2 py-1',
|
|
filtersDetailsParameters.selectedChildFilter === slugify(String(model.label ?? model.title), slugifyOptions) ? 'btn-secondary' : 'btn-white'
|
|
]"
|
|
:for="`child-${modelIndex}`"
|
|
>
|
|
{{ model.label ?? model.title }}
|
|
<span
|
|
v-if="filtersDetailsParameters.selectedChildFilter === slugify(String(model.label ?? model.title), slugifyOptions)"
|
|
class="filters-btn d-flex justify-content-center align-items-center position-absolute bg-secondary"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-xmark" />
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="swiper-btns swiper-btn-prev border-0 d-flex justify-content-center align-items-center position-absolute top-50 z-1 bg-white rounded-pill">
|
|
<img
|
|
src="@/images/common/arrow-brown.svg"
|
|
:title="__('Navigation du slider (gauche)')"
|
|
:alt="__('flèche vers la gauche')"
|
|
role="button"
|
|
>
|
|
</button>
|
|
<button class="swiper-btns swiper-btn-next border-0 d-flex justify-content-center align-items-center position-absolute top-50 translate-middle-y z-1 bg-white rounded-pill">
|
|
<img
|
|
src="@/images/common/arrow-brown.svg"
|
|
:title="__('Navigation du slider (droite)')"
|
|
:alt="__('flèche vers la droite')"
|
|
role="button"
|
|
>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- MODELS LIST -->
|
|
<template v-if="filtersDetailsParameters.viewsPaginate.length">
|
|
<div
|
|
v-for="(view, viewIndex) in filtersDetailsParameters.viewsPaginate"
|
|
:key="viewIndex"
|
|
class="col-12 col-lg-6 py-4"
|
|
v-html="view"
|
|
/>
|
|
<!-- INFORMATION -->
|
|
<p
|
|
v-if="paginationParameters.page === paginationParameters.lastPage"
|
|
class="text-center mt-5"
|
|
>
|
|
{{ __('Tous les éléments ont été chargés') }}
|
|
</p>
|
|
</template>
|
|
<!-- NO RESULT -->
|
|
<div
|
|
v-else
|
|
class="col-12 d-flex flex-column justify-content-center align-items-center"
|
|
>
|
|
<span class="no-result-icon">
|
|
<FontAwesomeIcon
|
|
icon="fa-solid fa-triangle-exclamation"
|
|
class="w-100 h-100"
|
|
/>
|
|
</span>
|
|
<p class="text-center m-0 pt-2">
|
|
{{ __("Aucun résultat ne correspond à votre recherche,") }}
|
|
</p>
|
|
<p class="text-center m-0 pb-3">
|
|
{{ __("essayez un autre paramètre de recherche ou réinitialisez-la.") }}
|
|
</p>
|
|
<button
|
|
@click="resetList(true)"
|
|
class="btn btn-primary"
|
|
type="button"
|
|
>
|
|
{{ __("Ré-initialiser la recherche") }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
<!-- PLACEHOLDERS -->
|
|
<div
|
|
v-if="loading || paginationParameters.loading"
|
|
class="col-12 col-md-6 recipe-card placeholder-glow bottom-left-shadow"
|
|
>
|
|
<div class="position-relative d-flex justify-content-center">
|
|
<span class="placeholder ratio ratio-16x9 line-border-medium w-100" />
|
|
</div>
|
|
<div class="line-border-medium position-relative d-flex flex-column align-items-center recipe-card-bottom-part bg-white px-3 pt-5">
|
|
<p class="placeholder placeholder-title col-7" />
|
|
<div class="d-flex flex-wrap justify-content-around align-items-center w-100">
|
|
<p class="placeholder placeholder-time col-3" />
|
|
<p class="placeholder placeholder-time col-3" />
|
|
<p class="placeholder placeholder-time col-3" />
|
|
</div>
|
|
<div class="d-flex flex-wrap justify-content-around align-items-center w-100">
|
|
<p class="placeholder placeholder-tag col-3" />
|
|
<p class="placeholder placeholder-tag col-3" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
import { __ } from "@modules/trans.js";
|
|
import slugify from "slugify";
|
|
import Swiper from "swiper";
|
|
import { Navigation } from "swiper/modules";
|
|
import type { App } from "vue";
|
|
import { createApp, nextTick, onMounted, reactive, ref, useAttrs } from "vue";
|
|
import axios from "~/axios/index.js";
|
|
import SearchBelongsToDropdownComponent from "./../../bo/components/SearchBelongsToDropdownComponent.vue";
|
|
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
|
|
import route from "./../../modules/route";
|
|
import BtnClubFavoriteComponent from "./BtnClubFavoriteComponent.vue";
|
|
|
|
defineOptions({
|
|
name: "SearchModelComponent"
|
|
});
|
|
|
|
/** Slugify options */
|
|
const slugifyOptions = { lower: true, strict:true, trim: true };
|
|
|
|
// * ATTRIBUTES
|
|
const attrs = useAttrs();
|
|
|
|
// * REFS
|
|
const searchModel = ref<HTMLDivElement|null>(null);
|
|
const filtersList = ref<HTMLDivElement|null>(null);
|
|
const childrenList = ref<HTMLDivElement|null>(null);
|
|
const modelSubtitle = ref<HTMLTitleElement|null>(null);
|
|
const modelDescription = ref<HTMLParagraphElement|null>(null);
|
|
const modelSubtitleMobile = ref<HTMLTitleElement|null>(null);
|
|
const modelDescriptionMobile = ref<HTMLParagraphElement|null>(null);
|
|
|
|
// * DATA
|
|
|
|
/** Texts parameters. */
|
|
const textsParameters = reactive<{
|
|
title: string,
|
|
subtitle: string,
|
|
description: string,
|
|
}>({
|
|
title: "",
|
|
subtitle: "",
|
|
description: "",
|
|
});
|
|
|
|
/** Filters parameters */
|
|
const filters = ref<Array<{
|
|
id: string,
|
|
title: string,
|
|
models: LaravelModelList,
|
|
route: string,
|
|
children: {
|
|
models: LaravelModelList,
|
|
route: string,
|
|
}|null
|
|
}>>([]);
|
|
|
|
/** Filters details parameters. */
|
|
const filtersDetailsParameters = reactive<{
|
|
viewsPaginate: Array<string>,
|
|
defaultPath: string,
|
|
selectedModel: LaravelModel|null,
|
|
selectedChildModel: LaravelModel|null,
|
|
selectedFilter: string,
|
|
selectedChildFilter: string,
|
|
selectedFilterId: string,
|
|
}>({
|
|
viewsPaginate: [],
|
|
defaultPath: "",
|
|
selectedModel: null,
|
|
selectedChildModel: null,
|
|
selectedFilter: "",
|
|
selectedChildFilter: "",
|
|
selectedFilterId: "",
|
|
});
|
|
|
|
/** Search parameters */
|
|
const searchParameters = reactive<{
|
|
modelTarget: string,
|
|
modelListPaginate: LaravelPaginator|undefined,
|
|
routeWords: string,
|
|
routeList: string,
|
|
fieldName: string,
|
|
placeholder: string,
|
|
selectedList: VselectOption[]|null,
|
|
}>({
|
|
modelTarget: "",
|
|
modelListPaginate: undefined,
|
|
routeWords: "",
|
|
routeList: "",
|
|
fieldName: "",
|
|
placeholder: "",
|
|
selectedList: null,
|
|
});
|
|
|
|
/** Pagination parameters. */
|
|
const paginationParameters = reactive<{
|
|
page: number,
|
|
itemPerPage: number,
|
|
lastPage: number,
|
|
loading: boolean,
|
|
}>({
|
|
page: 1,
|
|
itemPerPage: 1,
|
|
lastPage: 1,
|
|
loading: false,
|
|
});
|
|
|
|
/** This is the 'mise en avant' field for recipes */
|
|
const highlightModel = ref<LaravelModel|null>();
|
|
|
|
const swiper = ref<Swiper|null>(null);
|
|
const loading = ref<boolean>(false);
|
|
|
|
// * LIFECYCLE
|
|
onMounted(() => {
|
|
const json = String(attrs.json ?? "{}"),
|
|
data = JSON.parse(json);
|
|
|
|
highlightModel.value = data.highlightModel ?? null;
|
|
|
|
/** Texts parameters. */
|
|
textsParameters.title = data.texts.title;
|
|
textsParameters.subtitle = data.texts.subtitle;
|
|
textsParameters.description = data.texts.description;
|
|
|
|
/** Filters parameters */
|
|
filters.value = data.filters;
|
|
|
|
/** Filters details parameters. */
|
|
filtersDetailsParameters.viewsPaginate = data.filtersDetails.viewsPaginate.data;
|
|
filtersDetailsParameters.defaultPath = data.filtersDetails.defaultPath;
|
|
filtersDetailsParameters.selectedFilter = data.filtersDetails.selectedFilter;
|
|
filtersDetailsParameters.selectedChildFilter = data.filtersDetails.selectedChildFilter;
|
|
|
|
/** Pagination parameters. */
|
|
paginationParameters.page = data.filtersDetails.viewsPaginate.current_page;
|
|
paginationParameters.itemPerPage = data.filtersDetails.viewsPaginate.per_page;
|
|
paginationParameters.lastPage = data.filtersDetails.viewsPaginate.last_page;
|
|
|
|
/** Search parameters. */
|
|
searchParameters.modelTarget = data.search.modelTarget;
|
|
searchParameters.modelListPaginate = data.search.modelListPaginate;
|
|
searchParameters.routeWords = data.search.routeWords;
|
|
searchParameters.routeList = data.search.routeList;
|
|
searchParameters.fieldName = data.search.fieldName;
|
|
searchParameters.placeholder = data.search.placeholder;
|
|
|
|
checkScroll();
|
|
nextTick(() => {
|
|
filtersDetailsParameters.selectedFilterId = String(getFilterId());
|
|
initChildrenSwiper();
|
|
updateTexts();
|
|
mountCardComponents();
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Update texts (title/subtitle/description) with the model selected
|
|
* or default texts if there aren't any.
|
|
* @param string|null newTitle
|
|
*/
|
|
function updateTexts(newTitle: string|null = null): void {
|
|
getCurrentModels();
|
|
const currentModelSelected = (filtersDetailsParameters.selectedChildModel !== null)
|
|
? filtersDetailsParameters.selectedChildModel
|
|
: filtersDetailsParameters.selectedModel;
|
|
|
|
modelSubtitle.value!.textContent = String(((currentModelSelected !== null) ? currentModelSelected : textsParameters).subtitle);
|
|
modelDescription.value!.innerHTML = String(((currentModelSelected !== null) ? currentModelSelected : textsParameters).description);
|
|
modelSubtitleMobile.value!.textContent = String(((currentModelSelected !== null) ? currentModelSelected : textsParameters).subtitle);
|
|
modelDescriptionMobile.value!.innerHTML = String(((currentModelSelected !== null) ? currentModelSelected : textsParameters).description);
|
|
|
|
const parentTitle = (filtersDetailsParameters.selectedChildModel !== null)
|
|
? filtersDetailsParameters.selectedModel?.label + ", "
|
|
: "";
|
|
|
|
const currentFilter = parentTitle + (
|
|
newTitle !== null
|
|
? newTitle
|
|
: (currentModelSelected !== null
|
|
? (currentModelSelected?.title) ? currentModelSelected?.title : currentModelSelected?.label
|
|
: "")
|
|
);
|
|
|
|
document.querySelector(".current-filter")!.textContent = filtersDetailsParameters.selectedModel !== null ? " : " + currentFilter : "";
|
|
|
|
updateImage(currentModelSelected);
|
|
}
|
|
|
|
/**
|
|
* Update header image with current filter model selected,
|
|
* or with an image from a random filter model.
|
|
* @param LaravelModel|null currentModelSelected
|
|
*/
|
|
function updateImage(currentModelSelected: LaravelModel|null): void {
|
|
if (currentModelSelected === null) {
|
|
if(highlightModel.value) {
|
|
currentModelSelected = highlightModel.value;
|
|
} else {
|
|
let models = [] as LaravelModelList;
|
|
filters.value.map(function(filter) {
|
|
models = models.concat(filter.models);
|
|
if (filter.children)
|
|
models = models.concat(filter.children.models);
|
|
});
|
|
currentModelSelected = models[Math.floor(Math.random() * models.length)];
|
|
}
|
|
|
|
}
|
|
const image = document.querySelector("#header-image") as HTMLImageElement|null;
|
|
image!.src = "/" + String(currentModelSelected.picture);
|
|
image?.setAttribute("alt", String(currentModelSelected.picture_alt));
|
|
image?.setAttribute("title", String(currentModelSelected.picture_title));
|
|
}
|
|
|
|
/**
|
|
* Get models selected if there are any.
|
|
* @return void
|
|
*/
|
|
function getCurrentModels(): void {
|
|
getCurrentModelSelected();
|
|
getCurrentChildModelSelected();
|
|
}
|
|
|
|
/**
|
|
* Get parent model selected if there is one.
|
|
* @return void
|
|
*/
|
|
function getCurrentModelSelected(): void {
|
|
filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].models!.find(model => {
|
|
if (model.slug === filtersDetailsParameters.selectedFilter)
|
|
filtersDetailsParameters.selectedModel = model;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get child model selected if there is one.
|
|
* @return void
|
|
*/
|
|
function getCurrentChildModelSelected(): void {
|
|
filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].children?.models!.find(model => {
|
|
if (model.slug === filtersDetailsParameters.selectedChildFilter)
|
|
filtersDetailsParameters.selectedChildModel = model;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Init children slider.
|
|
* @return void
|
|
*/
|
|
function initChildrenSwiper(): void {
|
|
nextTick(() => {
|
|
swiper.value = new Swiper(".swiper-children", {
|
|
modules: [Navigation],
|
|
grabCursor: true,
|
|
slidesPerView: "auto",
|
|
spaceBetween: 20,
|
|
navigation: {
|
|
nextEl: ".children-list .swiper-btn-next",
|
|
prevEl: ".children-list .swiper-btn-prev",
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Choose between "sub" or root route.
|
|
* e.g fo.search.subrange or fo.search.range
|
|
* @return string
|
|
*/
|
|
const routeOrSubRouteSearch = () => {
|
|
if(filtersDetailsParameters.selectedChildModel) {
|
|
return filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].children?.route ?? filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].route;
|
|
}
|
|
return filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].route;
|
|
};
|
|
/**
|
|
* Check if all models are loaded,
|
|
* if not, get next models.
|
|
* @return void
|
|
*/
|
|
function checkScroll(): void {
|
|
window.addEventListener("scroll", () => {
|
|
if (
|
|
(window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 800)
|
|
&& !loading.value
|
|
&& !paginationParameters.loading
|
|
&& paginationParameters.page < paginationParameters.lastPage
|
|
) {
|
|
paginationParameters.page++;
|
|
paginationParameters.loading = true;
|
|
|
|
|
|
const routeCustom = (searchParameters.selectedList?.length)
|
|
? (getRouteFilter(searchParameters.routeWords) + `?page=${paginationParameters.page}&words=${searchParameters.selectedList.join(",")}`)
|
|
: getRouteFilter(
|
|
routeOrSubRouteSearch(),
|
|
(filtersDetailsParameters.selectedChildFilter.length)
|
|
? { model_slug: filtersDetailsParameters.selectedFilter, child_slug: filtersDetailsParameters.selectedChildFilter, page: paginationParameters.page }
|
|
: (filtersDetailsParameters.selectedFilter.length)
|
|
? { model_slug: filtersDetailsParameters.selectedFilter, page: paginationParameters.page }
|
|
: { page: paginationParameters.page }
|
|
);
|
|
|
|
const url = (filtersDetailsParameters.selectedChildFilter.length)
|
|
? filtersDetailsParameters.selectedChildFilter
|
|
: ((filtersDetailsParameters.selectedFilter.length)
|
|
? filtersDetailsParameters.selectedFilter
|
|
: "");
|
|
const children = filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].children ? true : false;
|
|
ajaxCall(routeCustom, url, children, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get models by search words.
|
|
* @return void
|
|
*/
|
|
function getModelByText(elements: VselectOption|VselectOption[]|null) {
|
|
if (elements !== null){
|
|
filtersDetailsParameters.selectedModel = filtersDetailsParameters.selectedChildModel = null;
|
|
searchParameters.selectedList = (!Array.isArray(elements)) ? [elements] : elements;
|
|
resetList();
|
|
ajaxCall(
|
|
getRouteFilter(searchParameters.routeWords) + `?words=${searchParameters.selectedList.join(",")}`,
|
|
`${location.origin}/${filtersDetailsParameters.defaultPath}`,
|
|
);
|
|
updateTexts(searchParameters.selectedList.join(", "));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get models by filter checked.
|
|
* @param {
|
|
title: string,
|
|
models: LaravelModelList,
|
|
route: string,
|
|
} filter
|
|
* @param LaravelModel|null model
|
|
* @param Event|null event
|
|
* @return void
|
|
*/
|
|
function getModelByFilters(filter: {
|
|
title: string,
|
|
models: LaravelModelList,
|
|
route: string,
|
|
}, model: LaravelModel|null = null, event: Event|null = null): void {
|
|
const target = getTarget(event);
|
|
searchParameters.selectedList = filtersDetailsParameters.selectedModel = filtersDetailsParameters.selectedChildModel = null;
|
|
filtersDetailsParameters.selectedFilter = filtersDetailsParameters.selectedChildFilter = "";
|
|
resetFilters(filtersList.value, target?.checked ? target?.id : null);
|
|
filtersDetailsParameters.selectedFilterId = String(getFilterId());
|
|
filtersDetailsParameters.selectedFilter = target?.checked ? slugify(String(model?.label ?? model?.title), slugifyOptions) : "";
|
|
paginationParameters.page = 1;
|
|
ajaxCall(
|
|
getRouteFilter(filter.route, target?.checked ? { model_slug: String(model?.slug), page: paginationParameters.page } : { page: paginationParameters.page }),
|
|
target?.checked ? getRouteFilter(filter.route).split("MODELSLUG")[0] + String(model?.slug) : `${location.origin}/${filtersDetailsParameters.defaultPath}`,
|
|
filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].children ? true : false,
|
|
);
|
|
updateTexts();
|
|
}
|
|
|
|
/**
|
|
* Get models by child checked.
|
|
* @param {
|
|
models: LaravelModelList,
|
|
route: string,
|
|
} children
|
|
* @param LaravelModel|null model
|
|
* @param Event|null event
|
|
* @return void
|
|
*/
|
|
function getModelByChildren(children: {
|
|
models: LaravelModelList,
|
|
route: string,
|
|
}, model: LaravelModel|null = null, event: Event|null = null): void {
|
|
const target = getTarget(event);
|
|
searchParameters.selectedList = filtersDetailsParameters.selectedChildModel = null;
|
|
filtersDetailsParameters.selectedChildFilter = "";
|
|
resetFilters(childrenList.value, target?.checked ? target?.id : null);
|
|
filtersDetailsParameters.selectedChildFilter = target?.checked ? slugify(String(model?.label ?? model?.title), slugifyOptions) : "";
|
|
paginationParameters.page = 1;
|
|
ajaxCall(
|
|
getRouteFilter(target?.checked ? children.route : filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].route, target?.checked ? { model_slug: filtersDetailsParameters.selectedFilter, child_slug: String(model?.slug), page: paginationParameters.page } : { model_slug: filtersDetailsParameters.selectedFilter, page: paginationParameters.page }),
|
|
target?.checked ? `${getRouteFilter(children.route).split("MODELSLUG")[0]}${filtersDetailsParameters.selectedFilter}/${String(model?.slug)}` : getRouteFilter(children.route).split("MODELSLUG")[0] + filtersDetailsParameters.selectedFilter,
|
|
);
|
|
updateTexts();
|
|
}
|
|
|
|
/**
|
|
* Ajax call template to get models views list.
|
|
* @param string customRoute
|
|
* @param string url
|
|
* @param boolean updateChildren
|
|
* @param boolean concatResult
|
|
* @return void
|
|
*/
|
|
function ajaxCall(
|
|
customRoute: string,
|
|
url: string,
|
|
updateChildren: boolean = false,
|
|
concatResult: boolean = false
|
|
): void {
|
|
let newViewsPaginate = [] as string[];
|
|
if (concatResult)
|
|
paginationParameters.loading = true;
|
|
else
|
|
loading.value = true;
|
|
window.axios
|
|
.get(customRoute)
|
|
.then((reponse) => {
|
|
newViewsPaginate = reponse.data.viewsPaginate.data;
|
|
paginationParameters.page = reponse.data.viewsPaginate.current_page;
|
|
paginationParameters.itemPerPage = reponse.data.viewsPaginate.per_page;
|
|
paginationParameters.lastPage = reponse.data.viewsPaginate.last_page;
|
|
filtersDetailsParameters.viewsPaginate = (concatResult)
|
|
? filtersDetailsParameters.viewsPaginate.concat(newViewsPaginate)
|
|
: newViewsPaginate;
|
|
|
|
if (updateChildren) {
|
|
filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)].children!.models = reponse.data.children;
|
|
}
|
|
updateURL(url);
|
|
if (!concatResult)
|
|
searchModel.value?.scrollIntoView();
|
|
})
|
|
.then(() => {
|
|
if (concatResult)
|
|
paginationParameters.loading = false;
|
|
else loading.value = false;
|
|
setTimeout(() => {
|
|
initChildrenSwiper();
|
|
mountCardComponents();
|
|
}, 10);
|
|
})
|
|
.catch(ajaxErrorHandler);
|
|
}
|
|
|
|
/**
|
|
* Get route filter.
|
|
* @param string routeName
|
|
* @param Record<string, string|number|(string|number)[]> parameters
|
|
* @return string
|
|
*/
|
|
function getRouteFilter(routeName: string, parameters: Record<string, string|number|(string|number)[]> = {}): string {
|
|
const routeFilter = route(routeName, parameters);
|
|
if (!routeFilter) {
|
|
throw new Error("Undefined route " + routeName);
|
|
}
|
|
return routeFilter;
|
|
}
|
|
|
|
/**
|
|
* Update url of the page.
|
|
* @param string newURL
|
|
* @return void
|
|
*/
|
|
function updateURL(newURL: string): void {
|
|
history.replaceState("Object", "", newURL);
|
|
}
|
|
|
|
/**
|
|
* Get checked filter id.
|
|
* @return string|void
|
|
*/
|
|
function getFilterId(): string|void {
|
|
const inputs = searchModel.value?.querySelectorAll("input[type='checkbox']") as NodeListOf<HTMLInputElement>;
|
|
for (let index = 0; index < inputs.length; index++) {
|
|
if (inputs[index].checked) {
|
|
return inputs[index].name;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get filter index by id.
|
|
* @param string id
|
|
* @return number
|
|
*/
|
|
function getFilterById(id: string): number {
|
|
for (let index = 0; index < filters.value.length; index++) {
|
|
if (filters.value[index].id === id) {
|
|
return index;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get the target of a click event.
|
|
* @param event Event|null
|
|
* @return HTMLInputElement|undefined
|
|
*/
|
|
function getTarget(event: Event|null): HTMLInputElement|undefined {
|
|
let target = undefined as HTMLInputElement|undefined;
|
|
if (event && event.target) {
|
|
target = event.target as HTMLInputElement;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* Reset the models list and all filters.
|
|
* @return void
|
|
*/
|
|
function resetList(getModels: boolean = false): void {
|
|
filtersDetailsParameters.selectedFilter = "";
|
|
filtersDetailsParameters.selectedChildFilter = "";
|
|
resetFilters(searchModel.value);
|
|
if (getModels)
|
|
getModelByFilters(filters.value[getFilterById(filtersDetailsParameters.selectedFilterId)]);
|
|
}
|
|
|
|
/**
|
|
* Reset all filters.
|
|
* @param HTMLDivElement nodeElement
|
|
* @param string|null inputId
|
|
* @return void
|
|
*/
|
|
function resetFilters(nodeElement: HTMLDivElement|null, inputId: string|null = null): void {
|
|
const inputs = nodeElement?.querySelectorAll("input[type='checkbox']") as NodeListOf<HTMLInputElement>;
|
|
inputs.forEach(input => {
|
|
input.checked = (inputId === input.id) ? true : false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mount components in render cards.
|
|
* @param string[] data
|
|
* @return void
|
|
*/
|
|
function mountCardComponents(): void {
|
|
const setupApp = (app: App<Element>) => {
|
|
app.config.globalProperties.window = window;
|
|
app.config.globalProperties.document = document;
|
|
app.component("FontAwesomeIcon", FontAwesomeIcon);
|
|
};
|
|
const buttons = document.querySelectorAll(".btn-club-favorite");
|
|
const newButtons = [].slice.call(buttons, paginationParameters.itemPerPage * (paginationParameters.page - 1)) as Array<HTMLDivElement>;
|
|
newButtons.forEach((card, cardIndex) => {
|
|
const app = createApp(BtnClubFavoriteComponent, JSON.parse(String(newButtons[cardIndex].getAttribute("data-json"))));
|
|
setupApp(app);
|
|
app.mount(newButtons[cardIndex] ?? "");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Return error message after the ajax call.
|
|
* @param e any
|
|
* @return void
|
|
*/
|
|
function ajaxErrorHandler(e: unknown): void {
|
|
let message = __("Une erreur est survenue");
|
|
|
|
if (axios.isAxiosError<LaravelValidationErrorResponse>(e)) {
|
|
if (e.response) {
|
|
if (e.response.status === 422) {
|
|
message = parseLaravelErrorMessages.methods.parseValidationErrors(
|
|
e.response?.data?.errors ?? {}
|
|
) || message;
|
|
if (window.vueDebug) {
|
|
console.warn(e.response.data.errors);
|
|
}
|
|
} else if (e.response.status === 419) {
|
|
console.log(__("Votre session a expiré, la page va être rechargée"));
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else if (e.response.status === 403) {
|
|
console.log(__("Vous n'êtes pas autorisé a effectuer cette action"));
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else {
|
|
if (window.vueDebug) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
} else if (e.request) {
|
|
console.error("Aucune réponse reçue :", e.request);
|
|
} else {
|
|
console.error("Erreur de configuration de la requête :", e.message);
|
|
}
|
|
}
|
|
console.log(message);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scopped>
|
|
@import "bootstrap/scss/functions";
|
|
@import "bootstrap/scss/variables";
|
|
@import "bootstrap/scss/mixins";
|
|
@import "swiper/css";
|
|
@import "swiper/css/navigation";
|
|
|
|
.search-model-component {
|
|
scroll-margin-top: 7rem;
|
|
@include media-breakpoint-up(md) {
|
|
.offcanvas-md {
|
|
.line-border-small {
|
|
background-color: rgb(var(--bs-white-rgb)) !important;
|
|
}
|
|
}
|
|
}
|
|
.filters-list {
|
|
position: sticky;
|
|
top: 7rem;
|
|
z-index: 2;
|
|
}
|
|
.filters-list:has(.offcanvas-md.show, .offcanvas-md.showing) {
|
|
z-index: 1050;
|
|
}
|
|
.placeholder-title {
|
|
height: 1.76rem;
|
|
}
|
|
.placeholder-time {
|
|
height: 1.3rem;
|
|
}
|
|
.placeholder-tag {
|
|
height: 1.1rem;
|
|
}
|
|
.no-result-icon {
|
|
width: 4rem;
|
|
height: 4rem;
|
|
}
|
|
.form-check-label .filters-btn {
|
|
top: -.5rem;
|
|
right: -.7rem;
|
|
width: 1.2rem;
|
|
height: 1.2rem;
|
|
border-radius: 100%;
|
|
padding: .7rem;
|
|
}
|
|
button[data-bs-toggle="collapse"] span {
|
|
rotate: -90deg;
|
|
transition: .3s;
|
|
}
|
|
button[data-bs-toggle="collapse"].collapsed span {
|
|
rotate: 90deg !important;
|
|
}
|
|
.swiper-btns {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
}
|
|
.swiper-btn-prev {
|
|
left: 0;
|
|
transform: translateY(-50%) rotate(180deg);
|
|
}
|
|
.swiper-btn-next {
|
|
right: 0;
|
|
}
|
|
.search-custom {
|
|
.vs__dropdown-toggle {
|
|
border: 0;
|
|
border-radius: 0;
|
|
padding: 0.75rem 1.12rem !important;
|
|
background-color: var(--bs-white) !important;
|
|
}
|
|
.vs__dropdown-menu {
|
|
background-color: var(--bs-white);
|
|
}
|
|
.vs--multiple .vs__selected {
|
|
margin: .375rem .5rem .375rem 0 !important;
|
|
}
|
|
.vs__search {
|
|
margin: 0 !important;
|
|
}
|
|
}
|
|
}
|
|
</style>
|