ldc-legaulois/resources/ts/fo/components/RatingSectionComponent.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>