add pagination on news

This commit is contained in:
Alexis Gousseau 2023-11-06 17:37:25 +01:00
parent bdbbb9ff9a
commit fc7dad207e
6 changed files with 193 additions and 93 deletions

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Lib\Helpers\CollectionHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\News;
use App\Models\StaticPage;
@ -22,7 +23,7 @@ class NewsController extends Controller
$staticPageModel = StaticPage::query()->where('type', StaticPageType::news)->firstOrFail();
/** @var \App\Models\News $newsModels */
$newsModels = News::query()->where('published', true)->orderBy('publish_at', 'desc')->get();
$newsModels = News::query()->where('published', true)->orderBy('publish_at', 'desc')->paginate(12);
return view('fo.pages.news.index', \compact('staticPageModel', 'newsModels'));
}
@ -61,6 +62,7 @@ class NewsController extends Controller
})
->orderBy('publish_at', 'desc')
->get();
$newsModels = CollectionHelper::paginateManually($newsModels, 12, request()->page);
return response()->json($newsModels);
}
}

View file

@ -16,13 +16,6 @@
&-content {
width: auto;
}
&-news-container{
background-image: url('@/images/global/bg-white-desktop.png');
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
&-category-arrow{

View file

@ -38,6 +38,7 @@
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/offcanvas";
@import "bootstrap/scss/card";
@import "bootstrap/scss/placeholders";
// 6. Overload bootstrap classes
@import "./../utilities/bootstrap-custom";

View file

@ -1,5 +1,6 @@
<template>
<div class="news-list container">
<!-- TITLE & SELECT -->
<div class="row pt-5">
<div class="col-12 col-lg-6">
<div class="ranges-title w-100 w-lg-fit">
@ -16,7 +17,7 @@
<select
class="form-control highlight yellow"
role="button"
@change="updateNews($event)"
@change="updateNews(1)"
:disabled="intLoading"
>
<option
@ -37,9 +38,16 @@
</div>
</div>
</div>
<div class="news-slider d-block d-lg-none bg-agis-white pb-5">
<!-- MOBILE: SLIDER -->
<div
class="news-slider d-block d-lg-none bg-agis-white pb-5"
:class="(newsModels.length) ? '' : 'd-none'"
>
<div class="swiper swiper-news overflow-hidden">
<div class="swiper-wrapper">
<div
class="swiper-wrapper"
@touchend="checkActiveSlide()"
>
<div
class="swiper-slide swiper-slide-content"
v-for="(news, id) in newsModels"
@ -62,7 +70,7 @@
</p>
<a
class="card-text m-0"
:href="showNews(news.slug as string)"
:href="getShowNewsRoute(news.slug as string)"
>
{{ __('> Lire la suite') }}
</a>
@ -74,28 +82,75 @@
<div class="d-flex justify-content-around">
<div class="swiper-button-prev mx-3">
<img
@click="checkActiveSlide()"
src="@/images/global/btn-arrow.png"
title="{{ __('Navigation du slider (gauche)') }}"
alt="{{ __('flèche vers la gauche') }}"
:title="__('Navigation du slider (gauche)')"
:alt="__('flèche vers la gauche')"
role="button"
>
</div>
<div class="swiper-button-next mx-3">
<button
v-if="intLoadingMore"
class="btn btn-primary"
@click="updateNews(page + 1, true)"
>
{{ __('> Charger plus') }}
</button>
<div
class="swiper-button-next mx-3"
:class="(intLoadingMore) ? 'd-none': ''"
>
<img
@click="checkActiveSlide()"
src="@/images/global/btn-arrow.png"
title="{{ __('Navigation du slider (droite)') }}"
alt="{{ __('flèche vers la droite') }}"
:title="__('Navigation du slider (droite)')"
:alt="__('flèche vers la droite')"
role="button"
>
</div>
</div>
</div>
</div>
<div class="d-none d-lg-flex flex-wrap justify-content-between my-4">
<!-- DESKTOP: FLEX WRAP -->
<div class="position-relative d-none d-lg-flex row my-4">
<template v-if="intLoading">
<div class="position-absolute top-0 left-0 bg-secondary w-100 h-100 d-flex justify-content-center align-items-center bg-opacity-25 z-1">
<div
class="spinner-border"
role="status"
>
<span class="visually-hidden">{{ __('Loading...') }}</span>
</div>
</div>
<div
v-for="index in paginator.per_page"
:key="index"
class="cards-container col-4"
>
<div
class="bg-agis-white d-flex flex-column p-4 my-2"
:class="((index % 3 === 0) ? 'ms-auto' : (((index % 3 === 1)) ? 'me-auto' : 'mx-auto'))"
>
<div class="placeholder-glow">
<span class="placeholder placeholder-image" />
<span class="placeholder col-12" />
<span class="placeholder col-12" />
<span class="placeholder col-12" />
<span class="placeholder col-12" />
</div>
</div>
</div>
</template>
<div
v-else
v-for="(news, id) in newsModels"
:key="id"
class="cards-container"
class="cards-container col-4"
>
<div class="bg-agis-white d-flex flex-column p-4">
<div
class="bg-agis-white d-flex flex-column p-4 my-2"
:class="((id % 3 === 0) ? 'me-auto' : (((id % 3 === 1)) ? 'mx-auto' : 'ms-auto'))"
>
<div class="text-center">
<img
class="img-fluid w-auto"
@ -113,7 +168,7 @@
</p>
<a
class="card-text m-0"
:href="showNews(news.slug as string)"
:href="getShowNewsRoute(news.slug as string)"
>
{{ __('> Lire la suite') }}
</a>
@ -132,6 +187,44 @@
</div>
</div>
</div>
<!-- PAGINATION -->
<div
v-if="paginator.total > paginator.per_page"
class="d-none d-lg-flex row w-100 mx-auto"
>
<nav
class="py-4"
:aria-label="__('Navigation des produits par gamme')"
>
<ul
class="pagination d-flex justify-content-center align-items-center list-unstyled d-flex mb-0"
>
<template v-for="(link, linkIndex) of paginator.links">
<li
:key="linkIndex"
v-if="link.url"
:class="`page-item d-flex ff-poppins-bold fs-md ${
link.active ? 'active' : ''
}`"
>
<a
@click.prevent="navigateOnPage(String(link.url))"
class="page-link d-flex align-items-center"
:href="link.url"
v-html="String(link.label)"
/>
</li>
</template>
</ul>
</nav>
</div>
<!-- NO NEWS -->
<p
v-if="!intLoading"
:class="(newsModels.length <= 0) ? '' : 'd-none'"
>
{{ __('Aucune actualité n\'est disponible') }}
</p>
</div>
</template>
@ -157,6 +250,10 @@ export default defineComponent({
selectedValue: number,
swiper: Swiper|null,
intLoading: boolean,
intLoadingMore: boolean,
concatNewsFiltered: boolean,
page: number;
paginator: LaravelPaginator;
} {
return {
newsModels: [],
@ -165,34 +262,39 @@ export default defineComponent({
selectedValue: 0,
swiper: null,
intLoading: false,
intLoadingMore: false,
concatNewsFiltered: false,
page: 1,
paginator: {
current_page: 1,
first_page_url: "",
from: 1,
last_page: 1,
last_page_url: "",
links: [],
next_page_url: null,
path: "",
per_page: 12,
prev_page_url: null,
to: 1,
total: 1,
},
};
},
computed: {},
mounted() {
const json = String(this.$attrs.json ?? "{}"),
data = JSON.parse(json);
this.newsModels = data.newsModels;
this.newsModels = data.newsModels.data;
this.paginator = 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>;
/** Init select ::after animation */
initSelect(): void {
const contactFormSelectList = document.querySelectorAll(".news-list .select-form select") as NodeListOf<HTMLSelectElement>;
document.body.addEventListener("click", function (e: MouseEvent) {
const target = e.target as HTMLElement | null;
contactFormSelectList.forEach(select => {
@ -205,7 +307,7 @@ export default defineComponent({
});
},
/** Init news slider */
initSwiper() {
initSwiper(): void {
this.swiper = new Swiper(".swiper-news", {
modules: [Navigation],
grabCursor: true,
@ -218,8 +320,8 @@ export default defineComponent({
});
},
/** Get selected value */
getSelectedValue(e: Event): number {
let target = e.target as HTMLSelectElement | null;
getSelectedValue(): number {
let target = document.querySelector(".news-list .select-form select") as HTMLSelectElement | null;
return Number(target?.value);
},
/** Get filtered news route */
@ -228,23 +330,39 @@ export default defineComponent({
if (!route) {
throw new Error("Undefined route fo.news.filtered");
}
return route + "?category=" + this.selectedValue;
return route + "?page=" + this.page + "&category=" + this.selectedValue;
},
/** Get show news route */
getShowNewsRoute(slug: string): string {
const route = this.route("fo.news.show", {
SLUG: slug,
});
if (!route) {
throw new Error("Undefined route fo.news.show");
}
return route;
},
/** Get filtered news */
getFilteredNews() {
getFilteredNews(): void {
window.axios
.get(this.newsFilteredRoute)
.then((reponse) => {
this.newsModels = reponse.data;
.then((response) => {
if (this.intLoadingMore && this.concatNewsFiltered) {
this.newsModels = this.newsModels.concat(Object.values(response.data.data));
} else {
this.newsModels = Object.values(response.data.data);
}
this.paginator = response.data;
this.intLoading = false;
this.intLoadingMore = false;
this.$nextTick(() => {
this.swiper?.update();
this.intLoading = false;
});
})
.catch(this.ajaxErrorHandler);
},
/** Return error message after the ajax call */
ajaxErrorHandler(e: any) {
ajaxErrorHandler(e: any): void {
this.intLoading = false;
let message = this.__("Une erreur est survenue");
if (e.response.status === 422) {
@ -271,20 +389,40 @@ export default defineComponent({
console.log(message);
},
/** Update news with filtered news */
updateNews(e: Event) {
updateNews(page: number, concat = false as boolean): void {
this.concatNewsFiltered = concat;
this.page = page;
this.intLoading = true;
this.selectedValue = this.getSelectedValue(e);
this.selectedValue = this.getSelectedValue();
this.newsFilteredRoute = this.getFilteredNewsRoute();
this.getFilteredNews();
},
/** Get all links from the paginator */
navigateOnPage(pageUrl: string): void {
new URLSearchParams(pageUrl).get("page");
this.page = Number.parseInt(
String(new URL(pageUrl).searchParams.get("page") ?? 1)
);
this.updateNews(this.page);
},
/** Check if it's the last slide is activated */
checkActiveSlide(): void {
setTimeout(() => {
this.intLoadingMore = (
this.swiper?.activeIndex !== undefined &&
this.paginator.last_page > this.page &&
this.paginator.current_page * this.paginator.per_page <= (this.swiper?.activeIndex + 1)
) ? true : false;
}, 300);
},
},
});
</script>
<style lang="scss">
@import "./../../../../node_modules/bootstrap/scss/functions";
@import "./../../../../node_modules/bootstrap/scss/variables";
@import "./../../../../node_modules/bootstrap/scss/mixins";
@import "./../../../../node_modules/bootstrap/scss/functions";
@import "./../../../../node_modules/bootstrap/scss/variables";
@import "./../../../../node_modules/bootstrap/scss/mixins";
.news-list {
.select-form {
width: 100%;
@ -316,8 +454,13 @@ export default defineComponent({
transform: rotate(270deg);
}
}
.cards-container{
.cards-container .bg-agis-white {
max-width: 314px;
.placeholder-image {
max-width: 265px;
width: 100%;
height: 354px;
}
}
}
</style>

View file

@ -1,39 +0,0 @@
<div class="swiper-slide d-md-block d-lg-none">
<div class="swiper-slide-news-container mx-auto mb-4">
<div class="swiper-slide-content d-flex flex-column justify-content-center align-items-center mx-auto p-4">
<img src="{{ asset($newsModel->picture_thumbnail) }}" title="{{ $newsModel->picture_thumbnail_title }}"
alt="{{ $newsModel->picture_thumbnail_alt }}">
<div
class="d-flex flex-column justify-content-start align-items-start w-75 @if ($key % 2) mb-3 @else mt-3 @endif">
<p class="f-readex-pro m-0">
{{ \Carbon\Carbon::parse($newsModel->publish_at)->locale('fr_FR')->isoFormat('D MMM Y') }}</p>
<p class="m-0 text-start">{{ $newsModel->title }}</p>
<a href="{{ route('fo.news.show', $newsModel->slug) }}">{{ __('> Lire la suite') }}</a>
</div>
</div>
<div class="d-flex justify-content-evenly align-items-center d-lg-none w-100 flex-row pb-3">
<div class="swiper-button-prev rounded-circle" role="button">
<img src="{{ Vite::asset('resources/assets/images/global/btn-arrow.png') }}"
title="{{ __('Navigation du slider (gauche)') }}" alt="{{ __('flèche vers la gauche') }}">
</div>
<div class="swiper-button-next rounded-circle" role="button">
<img src="{{ Vite::asset('resources/assets/images/global/btn-arrow.png') }}"
title="{{ __('Navigation du slider (droite)') }}" alt="{{ __('flèche vers la droite') }}">
</div>
</div>
</div>
</div>
<div class="col-3 card-container d-none d-lg-block m-4">
<div class="d-flex @if ($key % 2 === 1) flex-column-reverse @else flex-column @endif m-4">
<div class="text-center">
<img class="img-fluid w-auto" src="{{ $newsModel->picture_thumbnail }}" title="{{ $newsModel->picture_thumbnail_title }}"
alt="{{ $newsModel->picture_thumbnail_alt }}" />
</div>
<div class="">
<p>{{ \Carbon\Carbon::parse($newsModel->publish_at)->locale('fr_FR')->isoFormat('D MMM Y') }}</p>
<h6>{{ $newsModel->title }}</h6>
<a href="{{ route('fo.news.show', $newsModel->slug) }}">{{ __('> Lire la suite') }}</a>
<p>{{ $newsModel->category->label() }}</p>
</div>
</div>
</div>

View file

@ -9,7 +9,7 @@
<section class="news position-relative bg-agis-grey pb-5">
@php
$data = [
'newsModels' => $newsModels,
'newsModels' => json_decode(json_encode($newsModels)),
'categoryEnums' => \App\Enums\News\CategoryEnum::toArray(),
];
@endphp