ldc-agis/resources/ts/fo/components/NewsListComponent.vue
2023-11-06 16:05:48 +01:00

323 lines
10 KiB
Vue

<template>
<div class="news-list container">
<div class="row pt-5">
<div class="col-12 col-lg-6">
<div class="ranges-title w-100 w-lg-fit">
<span class="divide d-block w-100 mt-xl-3 my-2" />
<h1 class="f-sheldon text-uppercase text-lg-start my-3 text-center">
{{ __('nos actualités') }}
</h1>
<span class="divide d-block w-100 mt-xl-3 my-2" />
</div>
</div>
<div class="col-12 col-lg-6">
<div class="d-flex align-items-center h-100 w-100 justify-content-end">
<fieldset class="select-form position-relative my-3 my-lg-0">
<select
class="form-control highlight yellow"
role="button"
@change="updateNews($event)"
:disabled="intLoading"
>
<option
value="0"
selected
>
{{ __('Toutes catégories') }}
</option>
<option
v-for="(categoryEnum, categoryEnumIndex) in categoryEnums"
:key="categoryEnumIndex"
:value="categoryEnum.value"
>
{{ categoryEnum.label }}
</option>
</select>
</fieldset>
</div>
</div>
</div>
<div class="news-slider d-block d-lg-none bg-agis-white pb-5">
<div class="swiper swiper-news overflow-hidden">
<div class="swiper-wrapper">
<div
class="swiper-slide swiper-slide-content"
v-for="(news, id) in newsModels"
:key="id"
>
<div class="swiper-slide-news-container">
<div class="mx-auto p-4">
<img
class="card-img-top"
:src="(news.picture_thumbnail as string)"
:alt="(news.picture_thumbnail_alt as string)"
:title="(news.picture_thumbnail_title as string)"
>
<div class="card-body">
<p class="card-text m-0">
{{ new Date(news.publish_at as string).toLocaleDateString() }}
</p>
<p class="card-text m-0">
{{ news.title }}
</p>
<a
class="card-text m-0"
:href="showNews(news.slug as string)"
>
{{ __('> Lire la suite') }}
</a>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-around">
<div class="swiper-button-prev mx-3">
<img
src="@/images/global/btn-arrow.png"
title="{{ __('Navigation du slider (gauche)') }}"
alt="{{ __('flèche vers la gauche') }}"
>
</div>
<div class="swiper-button-next mx-3">
<img
src="@/images/global/btn-arrow.png"
title="{{ __('Navigation du slider (droite)') }}"
alt="{{ __('flèche vers la droite') }}"
>
</div>
</div>
</div>
</div>
<div class="d-none d-lg-flex flex-wrap justify-content-between my-4">
<div
v-for="(news, id) in newsModels"
:key="id"
class="cards-container"
>
<div class="bg-agis-white d-flex flex-column p-4">
<div class="text-center">
<img
class="img-fluid w-auto"
:src="(news.picture_thumbnail as string)"
:alt="(news.picture_thumbnail_alt as string)"
:title="(news.picture_thumbnail_title as string)"
>
</div>
<div class="card-body">
<p class="card-text m-0">
{{ new Date(news.publish_at as string).toLocaleDateString() }}
</p>
<p class="card-text m-0">
{{ news.title }}
</p>
<a
class="card-text m-0"
:href="showNews(news.slug as string)"
>
{{ __('> Lire la suite') }}
</a>
<template
v-for="(categoryEnum, categoryEnumIndex) in categoryEnums"
:key="categoryEnumIndex"
>
<p
class="card-text m-0"
v-if="news.category === categoryEnum.value"
>
{{ categoryEnum.label }}
</p>
</template>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import Swiper from "swiper";
import { Navigation } from "swiper/modules";
import { defineComponent } from "vue";
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
import route from "./../../modules/route";
import trans from "./../../modules/trans";
export default defineComponent({
name: "NewsListComponent",
mixins: [trans, route, parseLaravelErrorMessages],
data(): {
newsModels: LaravelModelList;
newsFilteredRoute: string;
categoryEnums: {
label: string,
name: string,
value: number,
}[];
selectedValue: number,
swiper: Swiper|null,
intLoading: boolean,
} {
return {
newsModels: [],
newsFilteredRoute: "",
categoryEnums: [],
selectedValue: 0,
swiper: null,
intLoading: false,
};
},
computed: {},
mounted() {
const json = String(this.$attrs.json ?? "{}"),
data = JSON.parse(json);
this.newsModels = data.newsModels;
this.categoryEnums = data.categoryEnums;
this.initSelect();
this.initSwiper();
},
methods: {
showNews (slug: string): string {
const route = this.route("fo.news.show", {
SLUG: slug,
});
if (!route) {
throw new Error("Undefined route fo.news.show");
}
return route;
},
formatDate (formDate: string | Date) {
const date = new Date(formDate);
return date.toLocaleDateString;
},
initSelect() {
// * Toggle la dropdown Arrow du select.
const contactFormSelectList = document.querySelectorAll(".news-list select") as NodeListOf<HTMLSelectElement>;
document.body.addEventListener("click", function (e: MouseEvent) {
const target = e.target as HTMLElement | null;
contactFormSelectList.forEach(select => {
if (select.contains(target)) {
select.parentElement!.classList.toggle("active");
} else {
select.parentElement!.classList.remove("active");
}
});
});
},
/** Init news slider */
initSwiper() {
this.swiper = new Swiper(".swiper-news", {
modules: [Navigation],
grabCursor: true,
slidesPerView: 1,
spaceBetween: 20,
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
},
/** Get selected value */
getSelectedValue(e: Event): number {
let target = e.target as HTMLSelectElement | null;
return Number(target?.value);
},
/** Get filtered news route */
getFilteredNewsRoute(): string {
const route = this.route("fo.news.filtered");
if (!route) {
throw new Error("Undefined route fo.news.filtered");
}
return route + "?category=" + this.selectedValue;
},
/** Get filtered news */
getFilteredNews() {
window.axios
.get(this.newsFilteredRoute)
.then((reponse) => {
this.newsModels = reponse.data;
this.$nextTick(() => {
this.swiper?.update();
this.intLoading = false;
});
})
.catch(this.ajaxErrorHandler);
},
/** Return error message after the ajax call */
ajaxErrorHandler(e: any) {
this.intLoading = false;
let message = this.__("Une erreur est survenue");
if (e.response.status === 422) {
message =
this.parseValidationErrors(
e.response?.data?.errors ?? {}
) || message;
if (window.vueDebug) {
console.warn(e.response.data.errors);
}
} else if (e.response.status === 419) {
// * CSRF TOKEN MISMATCH (On ne pourrais plus faire d'appels AJAX sans recharger la page)
console.log(this.__("Votre session a expiré, la page va être rechargée"));
setTimeout(() => window.location.reload(), 2000);
} else if (e.response.status === 403) {
// * Access denied
console.log(this.__("Vous n'êtes pas autorisé a effectuer cette action"));
setTimeout(() => window.location.reload(), 2000);
} else {
if (window.vueDebug) {
console.error(e);
}
}
console.log(message);
},
/** Update news with filtered news */
updateNews(e: Event) {
this.intLoading = true;
this.selectedValue = this.getSelectedValue(e);
this.newsFilteredRoute = this.getFilteredNewsRoute();
this.getFilteredNews();
},
},
});
</script>
<style lang="scss">
@import "./../../../../node_modules/bootstrap/scss/functions";
@import "./../../../../node_modules/bootstrap/scss/variables";
@import "./../../../../node_modules/bootstrap/scss/mixins";
.news-list {
.select-form {
width: 100%;
@include media-breakpoint-up(lg) {
width: 20rem;
}
&::after {
position: absolute;
right: 1rem;
bottom: 0.3rem;
display: block;
width: 2rem;
height: 2rem;
background: url("@/images/global/btn-arrow.png");
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
content: "";
pointer-events: none;
transform: rotate(180deg);
transition: 0.1s ease-in-out;
}
&.active::after {
background: url("@/images/global/btn-arrow-dark.png");
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
transform: rotate(270deg);
}
}
.cards-container{
max-width: 314px;
}
}
</style>