From 29b27b9a86435b97e92a8d388ea183bc4bcd416a Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Wed, 25 Mar 2026 11:53:24 +0100 Subject: [PATCH] feat: add client-side pagination to jobs table (25 per page) Co-Authored-By: Claude Sonnet 4.6 --- apps/backoffice/app/components/JobsList.tsx | 66 ++++++++++++++++++++- apps/backoffice/lib/i18n/en.ts | 2 + apps/backoffice/lib/i18n/fr.ts | 2 + 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/apps/backoffice/app/components/JobsList.tsx b/apps/backoffice/app/components/JobsList.tsx index 91a5c50..24e1074 100644 --- a/apps/backoffice/app/components/JobsList.tsx +++ b/apps/backoffice/app/components/JobsList.tsx @@ -4,6 +4,8 @@ import { useState, useEffect } from "react"; import { useTranslation } from "../../lib/i18n/context"; import { JobRow } from "./JobRow"; +const PAGE_SIZE = 25; + interface Job { id: string; library_id: string | null; @@ -45,6 +47,11 @@ export function JobsList({ initialJobs, libraries, highlightJobId }: JobsListPro const { t, locale } = useTranslation(); const [jobs, setJobs] = useState(initialJobs); + const initialPage = highlightJobId + ? Math.ceil((initialJobs.findIndex(j => j.id === highlightJobId) + 1) / PAGE_SIZE) || 1 + : 1; + const [currentPage, setCurrentPage] = useState(initialPage); + const formatDate = (dateStr: string): string => { const date = new Date(dateStr); if (isNaN(date.getTime())) return dateStr; @@ -58,10 +65,14 @@ export function JobsList({ initialJobs, libraries, highlightJobId }: JobsListPro }); }; + const totalPages = Math.ceil(jobs.length / PAGE_SIZE); + const pageStart = (currentPage - 1) * PAGE_SIZE; + const visibleJobs = jobs.slice(pageStart, pageStart + PAGE_SIZE); + // Refresh jobs list via SSE useEffect(() => { const eventSource = new EventSource("/api/jobs/stream"); - + eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); @@ -132,7 +143,7 @@ export function JobsList({ initialJobs, libraries, highlightJobId }: JobsListPro - {jobs.map((job) => ( + {visibleJobs.map((job) => ( + {totalPages > 1 && ( +
+ + {t("pagination.range", { + start: pageStart + 1, + end: Math.min(pageStart + PAGE_SIZE, jobs.length), + total: jobs.length, + })} + +
+ + {Array.from({ length: totalPages }, (_, i) => i + 1) + .filter(p => p === 1 || p === totalPages || Math.abs(p - currentPage) <= 1) + .reduce<(number | "…")[]>((acc, p, i, arr) => { + if (i > 0 && p - (arr[i - 1] as number) > 1) acc.push("…"); + acc.push(p); + return acc; + }, []) + .map((p, i) => + p === "…" ? ( + + ) : ( + + ) + )} + +
+
+ )} ); } diff --git a/apps/backoffice/lib/i18n/en.ts b/apps/backoffice/lib/i18n/en.ts index a046d90..597e936 100644 --- a/apps/backoffice/lib/i18n/en.ts +++ b/apps/backoffice/lib/i18n/en.ts @@ -708,6 +708,8 @@ const en: Record = { "pagination.show": "Show", "pagination.displaying": "Displaying {{count}} items", "pagination.range": "{{start}}-{{end}} of {{total}}", + "pagination.previous": "Previous", + "pagination.next": "Next", // Book detail "bookDetail.libraries": "Libraries", diff --git a/apps/backoffice/lib/i18n/fr.ts b/apps/backoffice/lib/i18n/fr.ts index f7ce32e..88f2100 100644 --- a/apps/backoffice/lib/i18n/fr.ts +++ b/apps/backoffice/lib/i18n/fr.ts @@ -706,6 +706,8 @@ const fr = { "pagination.show": "Afficher", "pagination.displaying": "Affichage de {{count}} éléments", "pagination.range": "{{start}}-{{end}} sur {{total}}", + "pagination.previous": "Précédent", + "pagination.next": "Suivant", // Book detail "bookDetail.libraries": "Bibliothèques",