diff --git a/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx b/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx index 9efd7ab19..b2967c58d 100644 --- a/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx @@ -9,6 +9,8 @@ import Badge from '@/components/ui/Badge'; import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { agentsApi, apiClient } from '@/lib/api'; +import Pagination from '@/components/ui/Pagination'; +import { usePagination } from '@/lib/use-pagination'; import { formatCurrency, formatDateTime } from '@/lib/utils'; import { useAuthStore } from '@/lib/auth-store'; @@ -90,6 +92,9 @@ export default function AgentsPage() { queryFn: () => agentsApi.getAll(filters), }); + const allAgents = data?.items || []; + const { paged: pagedAgents, page, totalPages, setPage } = usePagination(allAgents, 20); + const columns = [ { key: 'agentCode', @@ -182,12 +187,13 @@ export default function AgentsPage() { + = { passenger: 'edr-badge-info', sleeper: 'edr-badge-warning', @@ -551,11 +556,12 @@ export default function CoachesPage() { + )} @@ -574,11 +580,12 @@ export default function CoachesPage() { + )} diff --git a/apps/edr-passenger-web/backoffice/src/app/payments/page.tsx b/apps/edr-passenger-web/backoffice/src/app/payments/page.tsx index b9c81a183..9cbfdd4e3 100644 --- a/apps/edr-passenger-web/backoffice/src/app/payments/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/payments/page.tsx @@ -9,6 +9,8 @@ import ActionButton from '@/components/ui/ActionButton'; import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { paymentsApi, apiClient } from '@/lib/api'; +import Pagination from '@/components/ui/Pagination'; +import { usePagination } from '@/lib/use-pagination'; import { formatDateTime, formatCurrency } from '@/lib/utils'; import SupplementaryChargesModal from './SupplementaryChargesModal'; import { @@ -109,6 +111,9 @@ export default function PaymentsPage() { }), }); + const allPayments = (data as any)?.items || (Array.isArray(data) ? data : []); + const { paged: pagedPayments, page: paymentsPage, totalPages: paymentsTotalPages, setPage: setPaymentsPage } = usePagination(allPayments, 20); + const PAYMENT_COLS = [ { key: 'reference', label: 'Reference' }, { key: 'booking', label: 'Booking Reference' }, @@ -311,12 +316,13 @@ export default function PaymentsPage() { + {/* Payment Details Modal */} setSelectedPayment(null)} title="Payment Details" size="xl"> diff --git a/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx b/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx index d4d78ab01..35b6d6cc7 100644 --- a/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx @@ -6,6 +6,8 @@ import { Users, Armchair, BarChart3, Train, Download } from "lucide-react"; import { apiClient } from "@/lib/api-client"; import { formatDateTime } from "@/lib/utils"; import ActionButton from "@/components/ui/ActionButton"; +import Pagination from "@/components/ui/Pagination"; +import { usePagination } from "@/lib/use-pagination"; interface ScheduleOption { id: string; @@ -127,6 +129,8 @@ export default function PassengersReportPage() { }) .sort((a, b) => a.bookingRef.localeCompare(b.bookingRef)); + const { paged: pagedList, page: listPage, totalPages: listTotalPages, setPage: setListPage, reset: resetListPage } = usePagination(filteredList, 50); + const downloadCsv = (csv: string, filename: string) => { const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); @@ -457,12 +461,12 @@ export default function PassengersReportPage() { className="input max-w-sm flex-1" placeholder="Search by name or booking ref…" value={listSearch} - onChange={(e) => setListSearch(e.target.value)} + onChange={(e) => { setListSearch(e.target.value); resetListPage(); }} /> setFilterCoachNumber(e.target.value)} + onChange={(e) => { setFilterCoachNumber(e.target.value); resetListPage(); }} > {coachNumberOptions.map((c) => ( @@ -486,7 +490,7 @@ export default function PassengersReportPage() { setStatusFilter(e.target.value as "ALL" | "PAID" | "UNPAID")} + onChange={(e) => { setStatusFilter(e.target.value as "ALL" | "PAID" | "UNPAID"); resetSeatsPage(); }} > @@ -280,7 +285,7 @@ export default function SeatStatusReportPage() { - {filtered.map((row, i) => { + {pagedSeats.map((row, i) => { const isPaid = row.bookingStatus === "CONFIRMED" || row.bookingStatus === "BOARDED"; return ( @@ -306,7 +311,7 @@ export default function SeatStatusReportPage() { ); })} - {filtered.length === 0 && ( + {pagedSeats.length === 0 && ( No seats found @@ -316,6 +321,7 @@ export default function SeatStatusReportPage() { + } {/* Blocked Seats Tab */} @@ -338,12 +344,12 @@ export default function SeatStatusReportPage() { - {data.blockedSeats.length === 0 && ( + {pagedBlocked.length === 0 && ( No blocked seats )} - {data.blockedSeats.map((b) => ( + {pagedBlocked.map((b) => ( {b.seatClassName ?? "—"} @@ -365,6 +371,7 @@ export default function SeatStatusReportPage() { + )} diff --git a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx index 4ef9b97ed..b9b2765a7 100644 --- a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx @@ -9,6 +9,8 @@ import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { apiClient } from '@/lib/api-client'; import { routeCoachTemplatesApi } from '@/lib/api'; +import Pagination from '@/components/ui/Pagination'; +import { usePagination } from '@/lib/use-pagination'; import { formatDateTime } from '@/lib/utils'; interface Schedule { @@ -368,6 +370,8 @@ export default function SchedulesPage() { ); }); + const { paged: pagedSchedules, page: schedulePage, totalPages: scheduleTotalPages, setPage: setSchedulePage } = usePagination(filteredSchedules as Schedule[], 20); + const statusMap: Record = { SCHEDULED: 'edr-badge-info', BOARDING: 'edr-badge-warning', @@ -621,13 +625,16 @@ export default function SchedulesPage() { No schedules found. {filters.search && 'Try adjusting your search.'} ) : ( - + <> + + + )} diff --git a/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx b/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx index 063f4cab8..032a978a4 100644 --- a/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx @@ -9,6 +9,8 @@ import ActionButton from '@/components/ui/ActionButton'; import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { stationsApi } from '@/lib/api'; +import Pagination from '@/components/ui/Pagination'; +import { usePagination } from '@/lib/use-pagination'; export default function StationsPage() { const [filters, setFilters] = useState({ search: '', country: '', operational: '' }); @@ -90,6 +92,9 @@ export default function StationsPage() { } }; + const stationItems = data?.items || []; + const { paged: pagedStations, page, totalPages, setPage } = usePagination(stationItems, 20); + const handleDelete = (station: any) => { setDeleteConfirm({ isOpen: true, station, error: undefined }); }; @@ -231,12 +236,13 @@ export default function StationsPage() { {/* Stations Table */} + {/* Delete Confirmation */} setTicketPage(1); + const PAGE_SIZE = 50; const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [ticketToDelete, setTicketToDelete] = useState(null); const [deleteError, setDeleteError] = useState(null); @@ -65,7 +69,7 @@ export default function TicketsPage() { const queryClient = useQueryClient(); const { data, isLoading, error } = useQuery({ - queryKey: ['tickets', filters], + queryKey: ['tickets', filters, ticketPage], queryFn: () => ticketsApi.getAll({ search: filters.search || undefined, status: filters.status || undefined, @@ -76,11 +80,14 @@ export default function TicketsPage() { dateFrom: filters.dateFrom || undefined, dateTo: filters.dateTo || undefined, coachId: filters.coachId || undefined, - skip: 0, - take: 50, + skip: (ticketPage - 1) * PAGE_SIZE, + take: PAGE_SIZE, }), }); + const ticketMeta = (data as any)?.meta; + const ticketTotalPages = ticketMeta ? ticketMeta.totalPages : Math.max(1, Math.ceil(((data as any)?.total ?? (data?.items?.length ?? 0)) / PAGE_SIZE)); + const { data: stationsData } = useQuery({ queryKey: ['stations'], queryFn: () => stationsApi.getAll(), @@ -562,7 +569,7 @@ export default function TicketsPage() { placeholder="Search by ticket number..." className="input" value={filters.search} - onChange={(e) => setFilters({ ...filters, search: e.target.value })} + onChange={(e) => { resetTicketPage(); setFilters({ ...filters, search: e.target.value }); }} />
@@ -570,7 +577,7 @@ export default function TicketsPage() { setFilters({ ...filters, destinationStationId: e.target.value })} + onChange={(e) => { resetTicketPage(); setFilters({ ...filters, destinationStationId: e.target.value }); }} > {stations.map((station: any) => ( @@ -597,7 +604,7 @@ export default function TicketsPage() { type="date" className="input" value={filters.departureDate} - onChange={(e) => setFilters({ ...filters, departureDate: e.target.value })} + onChange={(e) => { resetTicketPage(); setFilters({ ...filters, departureDate: e.target.value }); }} />
@@ -605,7 +612,7 @@ export default function TicketsPage() { setFilters({ ...filters, status: e.target.value })} + onChange={(e) => { resetTicketPage(); setFilters({ ...filters, status: e.target.value }); }} > @@ -638,12 +645,12 @@ export default function TicketsPage() {
setFilters({ ...filters, dateFrom: e.target.value })} /> + onChange={(e) => { resetTicketPage(); setFilters({ ...filters, dateFrom: e.target.value }); }} />
setFilters({ ...filters, dateTo: e.target.value })} /> + onChange={(e) => { resetTicketPage(); setFilters({ ...filters, dateTo: e.target.value }); }} />
)} @@ -657,6 +664,7 @@ export default function TicketsPage() { loading={isLoading} emptyMessage="No tickets found" /> + {/* Board Confirmation Modal */} + {/* Delete Confirmation */} (items: T[], pageSize = 20) { + const [page, setPage] = useState(1); + + const totalPages = Math.max(1, Math.ceil(items.length / pageSize)); + const safePage = Math.min(page, totalPages); + + const paged = useMemo( + () => items.slice((safePage - 1) * pageSize, safePage * pageSize), + [items, safePage, pageSize], + ); + + // Reset to page 1 whenever the source list changes length (e.g. after a filter) + const reset = () => setPage(1); + + return { paged, page: safePage, totalPages, setPage, reset }; +}