367 lines
12 KiB
Vue
367 lines
12 KiB
Vue
<template>
|
|
<div :class="`bg-tertiary line-border-large pt-2 pb-5 ${wrapperClass}`">
|
|
<div class="container row mx-auto">
|
|
<!-- RATE -->
|
|
<div class="col-12 col-md-6 col-xl-3 text-center mb-3 mb-md-0 mx-auto">
|
|
<h5 class="h3 f-bourton-base text-uppercase">
|
|
{{ __('Vos avis comptent') }}
|
|
</h5>
|
|
<template v-if="rating && rating > 2">
|
|
<p class="h2 m-0">
|
|
{{ rating }}/5
|
|
</p>
|
|
<p>
|
|
{{ __('Note globale sur :number avis', { number: String(ratingModelsCount) }) }}
|
|
</p>
|
|
</template>
|
|
<div
|
|
v-if="customerKey"
|
|
class="bottom-left-shadow d-flex justify-content-center align-items-center mx-auto"
|
|
>
|
|
<div class="bg-white line-border-small w-fit p-1">
|
|
<p class="f-bakerie-rough fs-4 fw-bold mb-0 mx-auto px-2">
|
|
{{ getTranslationButton }}
|
|
</p>
|
|
<div class="d-flex justify-content-center align-items-center">
|
|
<RatingStarsComponent
|
|
wrapper-class="mx-auto"
|
|
:rating="customerRating ? Number(customerRating.rate) : 0"
|
|
:read-only="false"
|
|
@update:rating="sendRating"
|
|
/>
|
|
<button
|
|
v-if="customerRating"
|
|
class="btn-remove-rating btn btn-sm btn-light ms-1 my-1 p-2"
|
|
:title="__('Supprimer votre note')"
|
|
@click="sendRating(0)"
|
|
type="button"
|
|
>
|
|
<svg
|
|
width="8"
|
|
height="8"
|
|
viewBox="0 0 8 8"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
clip-rule="evenodd"
|
|
d="M7.4462 0.397052L7.5303 0.469673L7.6101 0.563622C7.799 0.827012 7.7966 1.18522 7.6029 1.44621L7.5303 1.53033L5.061 4L7.5303 6.4697L7.6101 6.5636C7.799 6.827 7.7966 7.1852 7.6029 7.4462L7.5303 7.5303L7.4364 7.6101C7.173 7.799 6.8148 7.7966 6.5538 7.6029L6.4697 7.5303L4 5.061L1.53033 7.5303L1.43638 7.6101C1.17299 7.799 0.814783 7.7966 0.553792 7.6029L0.469673 7.5303L0.389883 7.4364C0.201013 7.173 0.203402 6.8148 0.397052 6.5538L0.469673 6.4697L2.939 4L0.469673 1.53033L0.389883 1.43638C0.201013 1.17299 0.203402 0.814783 0.397052 0.553792L0.469673 0.469673L0.563622 0.389883C0.827012 0.201013 1.18522 0.203402 1.44621 0.397052L1.53033 0.469673L4 2.939L6.4697 0.469673L6.5636 0.389883C6.827 0.201013 7.1852 0.203402 7.4462 0.397052Z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else
|
|
class="my-3"
|
|
>
|
|
<a
|
|
:href="String(route('club.login'))"
|
|
class="d-flex justify-content-center align-items-center btn btn-primary btn-club rounded-pill"
|
|
>
|
|
<img
|
|
class="img-fluid me-2"
|
|
src="@/images/common/club-logo.png"
|
|
:title="__('Se connecter au club')"
|
|
:alt="__('Logo du club Le Gaulois')"
|
|
>
|
|
{{ __('Veuillez vous connecter au club des gaulois pour noter !') }}
|
|
</a>
|
|
</div>
|
|
<!-- ERRORS MESSAGES -->
|
|
<Transition name="fade">
|
|
<div
|
|
v-if="errorMessages && errorMessages.length"
|
|
class="col-12"
|
|
>
|
|
<p
|
|
v-for="(message, messageIndex) in errorMessages"
|
|
:key="messageIndex"
|
|
class="text-danger m-0"
|
|
>
|
|
{{ message }}
|
|
</p>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
<!-- RATINGS SLIDER -->
|
|
<div
|
|
v-if="rating && rating > 2 && ratingModels.length"
|
|
class="col-12 col-md-6 col-xl-9 position-relative d-flex justify-content-center align-items-center px-sm-5"
|
|
>
|
|
<div class="w-100">
|
|
<div
|
|
:id="`swiper-ratings`"
|
|
class="swiper swiper-ratings overflow-hidden px-3 pt-1 pb-4 pb-sm-3"
|
|
>
|
|
<div class="swiper-wrapper align-items-stretch w-fit">
|
|
<div
|
|
v-for="(model, modelKey) in ratingModels"
|
|
:key="modelKey"
|
|
class="swiper-slide position-relative bottom-left-shadow"
|
|
>
|
|
<div class="customer-icon-wrapper position-absolute top-0 start-50 translate-middle-x d-flex justify-content-center align-items-center bg-primary rounded-circle p-2 z-1">
|
|
<img
|
|
src="@/images/ratings/customer-icon.png"
|
|
:title="getTranslationButton"
|
|
:alt="__('Bulle de discussion')"
|
|
class="img-fluid"
|
|
>
|
|
</div>
|
|
<div class="line-border-small bg-white text-center h-auto mt-4 p-4 w-fit mx-auto">
|
|
<p class="fw-bold mt-2 mb-1">
|
|
{{ model.customer_username }}
|
|
</p>
|
|
<p class="mb-1">
|
|
{{ format(parseISO(String(model.created_at)), 'dd/MM/yyyy') }}
|
|
</p>
|
|
<div class="d-flex justify-content-center align-items-center">
|
|
<RatingStarsComponent :rating="Number(model.rate)" />
|
|
<p class="h5 m-0">
|
|
{{ model.rate }}/5
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
class="swiper-button swiper-ratings-button-prev swiper-button-prev-custom btn btn-white position-absolute rounded-circle border-0 p-1 z-2"
|
|
:title="__('Suivant')"
|
|
:aria-label="__('Suivant')"
|
|
type="button"
|
|
>
|
|
<img
|
|
src="@/images/common/arrow-brown.svg"
|
|
title="{{ __('Navigation du slider (gauche)') }}"
|
|
:alt="__('flèche vers la gauche')"
|
|
>
|
|
</button>
|
|
<button
|
|
class="swiper-button swiper-ratings-button-next swiper-button-next-custom btn btn-white position-absolute rounded-circle border-0 p-1 z-2"
|
|
:title="__('Précédent')"
|
|
:aria-label="__('Précédent')"
|
|
type="button"
|
|
>
|
|
<img
|
|
src="@/images/common/arrow-brown.svg"
|
|
:title="__('Navigation du slider (droite)')"
|
|
:alt="__('flèche vers la droite')"
|
|
>
|
|
</button>
|
|
<div
|
|
v-if="ratingModels.length > 1"
|
|
class="swiper-pagination swiper-ratings-pagination position-relative pb-3 pb-sm-0 z-1"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import parseLaravelErrorMessages from "@modules/parseLaravelErrorMessages";
|
|
import { notify } from "@modules/toast";
|
|
import { __ } from "@modules/trans";
|
|
import { format, parseISO } from "date-fns";
|
|
import Swiper from "swiper";
|
|
import { Navigation, Pagination } from "swiper/modules";
|
|
import { computed, nextTick, onMounted, ref, useAttrs } from "vue";
|
|
import axios from "~/axios";
|
|
import route from "../../modules/route";
|
|
import RatingStarsComponent from "./RatingStarsComponent.vue";
|
|
|
|
defineOptions({
|
|
name: "RatingSectionComponent"
|
|
});
|
|
|
|
// * ATTRIBUTES
|
|
const attrs = useAttrs();
|
|
|
|
// * DATA
|
|
const currentRatedModel = ref<LaravelModel|null>(null);
|
|
const customerKey = ref<number|null>(null);
|
|
const ratingModels = ref<Array<LaravelModel>>([]);
|
|
const ratingModelsCount = ref<number>(0);
|
|
const rating = ref<number|null>(null);
|
|
const ratingType = ref<string>("");
|
|
const swiper = ref<Swiper|null>(null);
|
|
const wrapperClass = ref<string>("");
|
|
const customerRating = ref<LaravelModel|null>(null);
|
|
const errorMessages = ref<Array<string>>([]);
|
|
|
|
// COMPUTED
|
|
const getTranslationButton = computed(() => {
|
|
return __(customerRating.value ? "Votre note" : "Donnez votre note !");
|
|
});
|
|
|
|
// * LIFECYCLE
|
|
onMounted(() => {
|
|
const json = String(attrs.json ?? "{}"),
|
|
data = JSON.parse(json);
|
|
currentRatedModel.value = data.currentRatedModel;
|
|
customerKey.value = data.customerKey;
|
|
wrapperClass.value = data.wrapperClass;
|
|
ratingModels.value = data.ratingsData.ratings;
|
|
ratingModelsCount.value = data.ratingsData.total;
|
|
ratingType.value = data.ratingsData.ratingType;
|
|
rating.value = data.ratingsData.totalRate;
|
|
customerRating.value = data.ratingsData.customerRating;
|
|
nextTick(() => {
|
|
setSwiper();
|
|
});
|
|
});
|
|
|
|
// * METHODS
|
|
|
|
function sendRating(value: number) {
|
|
const getRatingStoreRoute = route("fo.ratings.store");
|
|
if (!getRatingStoreRoute) {
|
|
throw new Error("Undefined route " + getRatingStoreRoute);
|
|
}
|
|
window.axios
|
|
.post(getRatingStoreRoute, {
|
|
rate: value,
|
|
rating_id: currentRatedModel.value?.id,
|
|
rating_type: ratingType.value,
|
|
customer_id: customerKey.value,
|
|
})
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
.then((response: { data: any; }) => {
|
|
ratingModels.value = response.data?.data.ratings;
|
|
ratingModelsCount.value = response.data?.data.total;
|
|
ratingType.value = response.data?.data.ratingType;
|
|
rating.value = response.data?.data.totalRate;
|
|
customerRating.value = response.data?.data.customerRating;
|
|
notify(response.data.message);
|
|
})
|
|
.finally(() => {
|
|
nextTick(() => {
|
|
setSwiper();
|
|
});
|
|
})
|
|
.catch(ajaxErrorHandler);
|
|
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/** Init ratings slider. */
|
|
function setSwiper(): void {
|
|
if (swiper.value) {
|
|
swiper.value.destroy(true, true);
|
|
swiper.value = null;
|
|
}
|
|
|
|
if (ratingModels.value.length === 0) return;
|
|
|
|
const isSmall = ratingModels.value.length <= 2;
|
|
|
|
const customOptions = {
|
|
modules: [Navigation, Pagination],
|
|
grabCursor: true,
|
|
slidesPerView: 1,
|
|
slidesPerGroup: 1,
|
|
centeredSlides: true,
|
|
spaceBetween: 10,
|
|
navigation: {
|
|
nextEl: ".swiper-ratings-button-next",
|
|
prevEl: ".swiper-ratings-button-prev",
|
|
},
|
|
breakpoints: {
|
|
1200: {
|
|
slidesPerView: isSmall ? ratingModels.value.length : 3,
|
|
slidesPerGroup: isSmall ? ratingModels.value.length : 3,
|
|
centeredSlides: false,
|
|
},
|
|
1400: {
|
|
slidesPerView: isSmall ? ratingModels.value.length : 3,
|
|
slidesPerGroup: isSmall ? ratingModels.value.length : 3,
|
|
centeredSlides: false,
|
|
spaceBetween: 35,
|
|
}
|
|
},
|
|
pagination: {
|
|
el: ".swiper-ratings-pagination",
|
|
clickable: true,
|
|
renderBullet: function (index: number, className: string) {
|
|
return "<button class=\"" + className + "\" title=\"Aller à la slide " + (index + 1) + "\"></button>";
|
|
},
|
|
},
|
|
|
|
};
|
|
swiper.value = new Swiper(".swiper-ratings", customOptions);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scopped>
|
|
@import "bootstrap/scss/functions";
|
|
@import "../../../sass/fo/utilities/variables";
|
|
@import "bootstrap/scss/variables";
|
|
@import "bootstrap/scss/mixins";
|
|
|
|
.btn-club img {
|
|
width: 4rem;
|
|
}
|
|
.btn-remove-rating {
|
|
fill: $color-secondary;
|
|
}
|
|
.swiper-ratings-button-prev,
|
|
.swiper-ratings-button-next {
|
|
top: auto;
|
|
bottom: 0;
|
|
@include media-breakpoint-up(sm) {
|
|
top: 50%;
|
|
bottom: auto;
|
|
}
|
|
}
|
|
.customer-icon-wrapper {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
}
|
|
.fade-enter-active, .fade-leave-active {
|
|
transition: opacity 0.5s;
|
|
}
|
|
.fade-enter-from, .fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
.fade-enter-to, .fade-leave-from {
|
|
opacity: 1;
|
|
}
|
|
</style>
|