Files
Mangarr/assets/vue/app/shared/components/ui/Pagination.vue

203 lines
7.0 KiB
Vue

<template>
<div v-if="totalPages > 1" class="flex items-center justify-between px-4 py-3 bg-white border-t border-gray-200">
<!-- Informations de pagination -->
<div class="flex items-center text-sm text-gray-700">
<span>
Affichage de
<span class="font-medium">{{ startItem }}</span>
à
<span class="font-medium">{{ endItem }}</span>
sur
<span class="font-medium">{{ total }}</span>
résultats
</span>
</div>
<!-- Contrôles de pagination -->
<div class="flex items-center space-x-2">
<!-- Bouton précédent -->
<button
@click="goToPage(currentPage - 1)"
:disabled="!hasPreviousPage"
:class="[
'relative inline-flex items-center px-2 py-2 text-sm font-medium rounded-md',
hasPreviousPage
? 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
: 'text-gray-300 bg-gray-100 border border-gray-200 cursor-not-allowed'
]">
<span class="sr-only">Précédent</span>
<ChevronLeftIcon class="h-5 w-5" />
</button>
<!-- Numéros de page -->
<div class="hidden md:flex space-x-1">
<!-- Première page -->
<button
v-if="showFirstPage"
@click="goToPage(1)"
:class="[
'relative inline-flex items-center px-3 py-2 text-sm font-medium rounded-md',
currentPage === 1
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
]">
1
</button>
<!-- Points de suspension gauche -->
<span v-if="showLeftDots" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700">
...
</span>
<!-- Pages visibles -->
<button
v-for="page in visiblePages"
:key="page"
@click="goToPage(page)"
:class="[
'relative inline-flex items-center px-3 py-2 text-sm font-medium rounded-md',
currentPage === page
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
]">
{{ page }}
</button>
<!-- Points de suspension droite -->
<span v-if="showRightDots" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700">
...
</span>
<!-- Dernière page -->
<button
v-if="showLastPage"
@click="goToPage(totalPages)"
:class="[
'relative inline-flex items-center px-3 py-2 text-sm font-medium rounded-md',
currentPage === totalPages
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
]">
{{ totalPages }}
</button>
</div>
<!-- Pagination mobile -->
<div class="md:hidden flex items-center space-x-2">
<span class="text-sm text-gray-700">
{{ currentPage }} / {{ totalPages }}
</span>
</div>
<!-- Bouton suivant -->
<button
@click="goToPage(currentPage + 1)"
:disabled="!hasNextPage"
:class="[
'relative inline-flex items-center px-2 py-2 text-sm font-medium rounded-md',
hasNextPage
? 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
: 'text-gray-300 bg-gray-100 border border-gray-200 cursor-not-allowed'
]">
<span class="sr-only">Suivant</span>
<ChevronRightIcon class="h-5 w-5" />
</button>
</div>
</div>
</template>
<script setup>
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/outline';
import { computed } from 'vue';
const props = defineProps({
currentPage: {
type: Number,
required: true
},
totalPages: {
type: Number,
required: true
},
total: {
type: Number,
required: true
},
limit: {
type: Number,
required: true
},
hasNextPage: {
type: Boolean,
default: false
},
hasPreviousPage: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['page-change']);
// Calculs pour l'affichage des informations
const startItem = computed(() => {
return Math.max(1, (props.currentPage - 1) * props.limit + 1);
});
const endItem = computed(() => {
return Math.min(props.total, props.currentPage * props.limit);
});
// Logique pour afficher les numéros de page
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5; // Nombre maximum de pages visibles
const half = Math.floor(maxVisible / 2);
let start = Math.max(1, props.currentPage - half);
let end = Math.min(props.totalPages, start + maxVisible - 1);
// Ajuster le début si on est près de la fin
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
// Ne pas inclure la première et dernière page dans visiblePages
// si elles sont déjà affichées séparément
const actualStart = start === 1 && props.totalPages > maxVisible ? 2 : start;
const actualEnd = end === props.totalPages && props.totalPages > maxVisible ? props.totalPages - 1 : end;
for (let i = actualStart; i <= actualEnd; i++) {
if (i !== 1 && i !== props.totalPages) {
pages.push(i);
} else if (props.totalPages <= maxVisible) {
pages.push(i);
}
}
return pages;
});
const showFirstPage = computed(() => {
return props.totalPages > 5 && props.currentPage > 3;
});
const showLastPage = computed(() => {
return props.totalPages > 5 && props.currentPage < props.totalPages - 2;
});
const showLeftDots = computed(() => {
return props.totalPages > 5 && props.currentPage > 4;
});
const showRightDots = computed(() => {
return props.totalPages > 5 && props.currentPage < props.totalPages - 3;
});
const goToPage = (page) => {
if (page >= 1 && page <= props.totalPages && page !== props.currentPage) {
emit('page-change', page);
}
};
</script>