feat: ajout de la pagination et des filtres dans le store d'activités, mise à jour des composants pour gérer l'affichage des jobs, et amélioration de la gestion des états des jobs. Intégration d'une nouvelle composante de pagination pour une navigation optimisée.
This commit is contained in:
parent
b456f9304d
commit
b4bfa48d00
@@ -8,8 +8,16 @@ export const useActivityStore = defineStore('activity', {
|
||||
jobs: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
// Pagination
|
||||
currentPage: 1,
|
||||
totalPages: 0,
|
||||
total: 0,
|
||||
limit: 20,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
// Filtres
|
||||
filter: {
|
||||
status: ['pending', 'in_progress', 'failed'],
|
||||
status: ['pending', 'in_progress'], // Par défaut, ne montrer que les actifs
|
||||
sortBy: 'createdAt',
|
||||
sortOrder: 'DESC'
|
||||
}
|
||||
@@ -20,28 +28,57 @@ export const useActivityStore = defineStore('activity', {
|
||||
completedJobs: state => state.jobs.filter(job => job.isCompleted()),
|
||||
failedJobs: state => state.jobs.filter(job => job.hasError()),
|
||||
isLoading: state => state.loading,
|
||||
hasError: state => !!state.error
|
||||
hasError: state => !!state.error,
|
||||
// Getters pour la pagination
|
||||
paginationInfo: state => ({
|
||||
currentPage: state.currentPage,
|
||||
totalPages: state.totalPages,
|
||||
total: state.total,
|
||||
limit: state.limit,
|
||||
hasNextPage: state.hasNextPage,
|
||||
hasPreviousPage: state.hasPreviousPage
|
||||
})
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Charge la liste des jobs selon les filtres actuels
|
||||
* @param {number} page - Numéro de page optionnel
|
||||
*/
|
||||
async loadJobs() {
|
||||
async loadJobs(page = null) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const options = {
|
||||
page: 1, // Page fixée à 1
|
||||
limit: 100, // Limite augmentée pour récupérer plus de jobs
|
||||
page: page || this.currentPage,
|
||||
limit: this.limit,
|
||||
sortBy: this.filter.sortBy,
|
||||
sortOrder: this.filter.sortOrder,
|
||||
status: this.filter.status
|
||||
};
|
||||
|
||||
const jobCollection = await jobRepository.getJobs(options);
|
||||
|
||||
// Mettre à jour les données
|
||||
this.jobs = jobCollection.items;
|
||||
this.currentPage = jobCollection.page;
|
||||
this.total = jobCollection.total;
|
||||
this.hasNextPage = jobCollection.hasNextPage;
|
||||
this.hasPreviousPage = jobCollection.hasPreviousPage;
|
||||
|
||||
// Calculer le nombre total de pages
|
||||
this.totalPages = Math.ceil(this.total / this.limit);
|
||||
|
||||
console.log('Store updated with:', {
|
||||
jobs: this.jobs.length,
|
||||
currentPage: this.currentPage,
|
||||
total: this.total,
|
||||
limit: this.limit,
|
||||
totalPages: this.totalPages,
|
||||
hasNextPage: this.hasNextPage,
|
||||
hasPreviousPage: this.hasPreviousPage
|
||||
});
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
console.error('Error loading jobs:', error);
|
||||
@@ -50,13 +87,35 @@ export const useActivityStore = defineStore('activity', {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Va à une page spécifique
|
||||
* @param {number} page
|
||||
*/
|
||||
async goToPage(page) {
|
||||
if (page >= 1 && page <= this.totalPages && page !== this.currentPage) {
|
||||
this.currentPage = page;
|
||||
await this.loadJobs(page);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Met à jour les filtres et recharge la liste
|
||||
* @param {Object} filter
|
||||
*/
|
||||
async updateFilter(filter) {
|
||||
this.filter = { ...this.filter, ...filter };
|
||||
await this.loadJobs();
|
||||
this.currentPage = 1; // Retourner à la première page lors du changement de filtre
|
||||
await this.loadJobs(1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Met à jour la limite par page
|
||||
* @param {number} limit
|
||||
*/
|
||||
async updateLimit(limit) {
|
||||
this.limit = limit;
|
||||
this.currentPage = 1; // Retourner à la première page
|
||||
await this.loadJobs(1);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -71,6 +130,8 @@ export const useActivityStore = defineStore('activity', {
|
||||
await jobRepository.deleteJob(id);
|
||||
// Supprimer le job de la liste locale
|
||||
this.jobs = this.jobs.filter(job => job.id !== id);
|
||||
// Recharger la page courante pour avoir les bons totaux
|
||||
await this.loadJobs(this.currentPage);
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
console.error('Error deleting job:', error);
|
||||
@@ -90,7 +151,7 @@ export const useActivityStore = defineStore('activity', {
|
||||
try {
|
||||
const deleted = await jobRepository.deleteJobs(criteria);
|
||||
// Recharger la liste après suppression
|
||||
await this.loadJobs();
|
||||
await this.loadJobs(this.currentPage);
|
||||
return deleted;
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
|
||||
@@ -26,15 +26,15 @@ export class Job {
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return ['PENDING', 'IN_PROGRESS'].includes(this.status);
|
||||
return ['pending', 'in_progress'].includes(this.status);
|
||||
}
|
||||
|
||||
hasError() {
|
||||
return this.status === 'ERROR';
|
||||
return this.status === 'failed';
|
||||
}
|
||||
|
||||
isCompleted() {
|
||||
return this.status === 'COMPLETED';
|
||||
return this.status === 'completed';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ export class ApiJobRepository extends JobRepositoryInterface {
|
||||
url += `&status=${status.join(',')}`;
|
||||
}
|
||||
|
||||
console.log('Fetching jobs from URL:', url);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -30,14 +32,53 @@ export class ApiJobRepository extends JobRepositoryInterface {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('API Response:', data);
|
||||
|
||||
// Gérer différents formats de réponse API
|
||||
let jobs, total, currentPage, limit_returned, hasNext, hasPrev;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// Si l'API retourne directement un tableau
|
||||
jobs = data;
|
||||
total = data.length;
|
||||
currentPage = page;
|
||||
limit_returned = limit;
|
||||
hasNext = false;
|
||||
hasPrev = false;
|
||||
} else if (data.items || data.data) {
|
||||
// Si l'API retourne un objet avec les données dans items ou data
|
||||
jobs = data.items || data.data || [];
|
||||
total = data.total || data.totalCount || jobs.length;
|
||||
currentPage = data.page || data.currentPage || page;
|
||||
limit_returned = data.limit || data.perPage || limit;
|
||||
hasNext = data.hasNextPage || data.hasNext || (currentPage * limit_returned < total);
|
||||
hasPrev = data.hasPreviousPage || data.hasPrev || currentPage > 1;
|
||||
} else {
|
||||
// Format par défaut
|
||||
jobs = data || [];
|
||||
total = data.total || 0;
|
||||
currentPage = data.page || 1;
|
||||
limit_returned = data.limit || limit;
|
||||
hasNext = !!data.hasNextPage;
|
||||
hasPrev = !!data.hasPreviousPage;
|
||||
}
|
||||
|
||||
console.log('Processed data:', {
|
||||
jobs: jobs.length,
|
||||
total,
|
||||
currentPage,
|
||||
limit_returned,
|
||||
hasNext,
|
||||
hasPrev
|
||||
});
|
||||
|
||||
return new JobCollection(
|
||||
data || [],
|
||||
data.total || 0,
|
||||
data.page || 1,
|
||||
data.limit || limit,
|
||||
!!data.hasNextPage,
|
||||
!!data.hasPreviousPage
|
||||
jobs,
|
||||
total,
|
||||
currentPage,
|
||||
limit_returned,
|
||||
hasNext,
|
||||
hasPrev
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<tr
|
||||
class="border-b border-gray-200 hover:bg-gray-50 transition duration-150 ease-in-out"
|
||||
:class="{
|
||||
'bg-yellow-50': job.status === 'PENDING',
|
||||
'bg-blue-50': job.status === 'IN_PROGRESS',
|
||||
'bg-green-50': job.status === 'COMPLETED',
|
||||
'bg-red-50': job.status === 'ERROR'
|
||||
'bg-yellow-50': job.status === 'pending',
|
||||
'bg-blue-50': job.status === 'in_progress',
|
||||
'bg-green-50': job.status === 'completed',
|
||||
'bg-red-50': job.status === 'failed'
|
||||
}">
|
||||
<td class="py-4 px-4 text-center">
|
||||
<input type="checkbox" class="form-checkbox h-5 w-5 text-green-600" />
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-4">
|
||||
<div v-if="job.status === 'IN_PROGRESS'" class="mt-2">
|
||||
<div v-if="job.status === 'in_progress'" class="mt-2">
|
||||
<div class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-green-400 transition-all duration-300 ease-out"
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="job.status === 'COMPLETED'" class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div v-else-if="job.status === 'completed'" class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-green-400 transition-all duration-300 ease-out"
|
||||
style="width: 100%"></div>
|
||||
@@ -50,7 +50,7 @@
|
||||
100%
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="job.status === 'ERROR'" class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div v-else-if="job.status === 'failed'" class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-red-400 transition-all duration-300 ease-out"
|
||||
style="width: 100%"></div>
|
||||
@@ -79,8 +79,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { TrashIcon } from '@heroicons/vue/24/outline';
|
||||
import { defineEmits, defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
job: {
|
||||
|
||||
@@ -11,7 +11,17 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="container mx-auto p-2">
|
||||
<div class="bg-white overflow-hidden">
|
||||
<!-- Debug pagination - À supprimer plus tard -->
|
||||
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded mb-4" v-if="true">
|
||||
<strong>Debug Pagination:</strong>
|
||||
Total: {{ activityStore.total }},
|
||||
Limit: {{ activityStore.limit }},
|
||||
Pages: {{ activityStore.totalPages }},
|
||||
Page courante: {{ activityStore.currentPage }},
|
||||
Condition: {{ activityStore.total > activityStore.limit }}
|
||||
</div>
|
||||
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white">
|
||||
<thead>
|
||||
@@ -32,9 +42,13 @@
|
||||
<tbody class="text-gray-700">
|
||||
<template v-if="activityStore.jobs.length === 0">
|
||||
<tr>
|
||||
<td colspan="6" class="py-4 px-4 text-center text-gray-500"
|
||||
>Aucune activité trouvée avec les filtres actuels.</td
|
||||
>
|
||||
<td colspan="6" class="py-8 px-4 text-center text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<ClockIcon class="h-12 w-12 text-gray-300 mb-4" />
|
||||
<p class="text-lg font-medium">Aucune activité trouvée</p>
|
||||
<p class="text-sm">Aucune activité ne correspond aux filtres actuels.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -47,33 +61,47 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<Pagination
|
||||
v-if="activityStore.total > activityStore.limit"
|
||||
:current-page="activityStore.currentPage"
|
||||
:total-pages="activityStore.totalPages"
|
||||
:total="activityStore.total"
|
||||
:limit="activityStore.limit"
|
||||
:has-next-page="activityStore.hasNextPage"
|
||||
:has-previous-page="activityStore.hasPreviousPage"
|
||||
@page-change="changePage" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useActivityStore } from '../../application/store/activityStore';
|
||||
import JobItem from '../components/JobItem.vue';
|
||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||
import { ArrowPathIcon, TrashIcon, FunnelIcon } from '@heroicons/vue/24/outline';
|
||||
import { ArrowPathIcon, ClockIcon, FunnelIcon, TrashIcon } from '@heroicons/vue/24/outline';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import Pagination from '../../../../shared/components/ui/Pagination.vue';
|
||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||
import { useActivityStore } from '../../application/store/activityStore';
|
||||
import JobItem from '../components/JobItem.vue';
|
||||
|
||||
const activityStore = useActivityStore();
|
||||
const selectedAll = ref(false);
|
||||
|
||||
// Statuts disponibles pour le filtre
|
||||
const statusOptions = [
|
||||
{ value: ['PENDING', 'IN_PROGRESS'], label: 'Actifs' },
|
||||
{ value: ['PENDING', 'IN_PROGRESS', 'COMPLETED', 'ERROR'], label: 'Tous' },
|
||||
{ value: ['COMPLETED'], label: 'Terminés' },
|
||||
{ value: ['ERROR'], label: 'En erreur' }
|
||||
{ value: ['pending', 'in_progress'], label: 'Actifs' },
|
||||
{ value: ['pending', 'in_progress', 'completed', 'failed'], label: 'Tous' },
|
||||
{ value: ['completed'], label: 'Terminés' },
|
||||
{ value: ['failed'], label: 'En erreur' },
|
||||
{ value: ['pending'], label: 'En attente' },
|
||||
{ value: ['in_progress'], label: 'En cours' }
|
||||
];
|
||||
|
||||
// Index du statut actif
|
||||
// Index du statut actif (par défaut "Actifs")
|
||||
const activeStatusIndex = ref(0);
|
||||
|
||||
// Rendre toolbarConfig computed pour que le label du dropdown soit réactif
|
||||
// Configuration de la toolbar réactive
|
||||
const toolbarConfig = computed(() => ({
|
||||
leftSection: [
|
||||
{
|
||||
@@ -98,7 +126,7 @@
|
||||
{
|
||||
icon: TrashIcon,
|
||||
type: 'button',
|
||||
label: 'Supprimer',
|
||||
label: 'Supprimer visibles',
|
||||
onClick: deleteVisibleJobs
|
||||
}
|
||||
]
|
||||
@@ -116,6 +144,10 @@
|
||||
loadJobs();
|
||||
}
|
||||
|
||||
function changePage(page) {
|
||||
activityStore.goToPage(page);
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
selectedAll.value = !selectedAll.value;
|
||||
// La logique pour sélectionner tous les jobs serait ajoutée ici
|
||||
@@ -135,7 +167,12 @@
|
||||
}
|
||||
|
||||
function deleteVisibleJobs() {
|
||||
if (confirm('Voulez-vous vraiment supprimer tous les jobs affichés ?')) {
|
||||
if (activityStore.jobs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statusLabel = statusOptions[activeStatusIndex.value].label.toLowerCase();
|
||||
if (confirm(`Voulez-vous vraiment supprimer tous les jobs ${statusLabel} ?`)) {
|
||||
activityStore.deleteJobs({ status: activityStore.filter.status });
|
||||
}
|
||||
}
|
||||
|
||||
202
assets/vue/app/shared/components/ui/Pagination.vue
Normal file
202
assets/vue/app/shared/components/ui/Pagination.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user