203 lines
7.0 KiB
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>
|