From 50f6796ffaa97722a8c1f1808de6d6331ce077ff Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Mon, 20 Oct 2025 20:39:16 -0300 Subject: [PATCH] feat: paginate ticket timeline --- src/components/tickets/ticket-timeline.tsx | 128 ++++++++++++++++++++- src/components/ui/pagination.tsx | 105 +++++++++++++++++ 2 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 src/components/ui/pagination.tsx diff --git a/src/components/tickets/ticket-timeline.tsx b/src/components/tickets/ticket-timeline.tsx index 0c92667..9fd22ee 100644 --- a/src/components/tickets/ticket-timeline.tsx +++ b/src/components/tickets/ticket-timeline.tsx @@ -1,5 +1,5 @@ +import { useEffect, useMemo, useState, type ReactNode, type ComponentType } from "react" import { format } from "date-fns" -import type { ComponentType, ReactNode } from "react" import { ptBR } from "date-fns/locale" import { IconCalendar, @@ -16,6 +16,15 @@ import type { TicketWithDetails } from "@/lib/schemas/ticket" import { Card, CardContent } from "@/components/ui/card" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Separator } from "@/components/ui/separator" +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination" import { TICKET_TIMELINE_LABELS } from "@/lib/ticket-timeline-labels" const timelineIcons: Record> = { @@ -44,6 +53,8 @@ interface TicketTimelineProps { ticket: TicketWithDetails } +const ITEMS_PER_PAGE = 10 + export function TicketTimeline({ ticket }: TicketTimelineProps) { const formatDuration = (durationMs: number) => { if (!durationMs || durationMs <= 0) return "0s" @@ -60,15 +71,122 @@ export function TicketTimeline({ ticket }: TicketTimelineProps) { return `${seconds}s` } + const [page, setPage] = useState(1) + + const totalItems = ticket.timeline.length + const totalPages = Math.max(1, Math.ceil(totalItems / ITEMS_PER_PAGE)) + const currentPage = Math.min(page, totalPages) + const pageOffset = (currentPage - 1) * ITEMS_PER_PAGE + + const currentEvents = useMemo( + () => ticket.timeline.slice(pageOffset, pageOffset + ITEMS_PER_PAGE), + [pageOffset, ticket.timeline] + ) + + const paginationRange = useMemo(() => { + if (totalPages <= 7) { + return Array.from({ length: totalPages }, (_, index) => index + 1) + } + const range: Array = [1] + const left = Math.max(2, currentPage - 1) + const right = Math.min(totalPages - 1, currentPage + 1) + if (left > 2) { + range.push("ellipsis-left") + } + for (let i = left; i <= right; i += 1) { + range.push(i) + } + if (right < totalPages - 1) { + range.push("ellipsis-right") + } + range.push(totalPages) + return range + }, [currentPage, totalPages]) + + const rangeStart = totalItems === 0 ? 0 : pageOffset + 1 + const rangeEnd = totalItems === 0 ? 0 : Math.min(pageOffset + ITEMS_PER_PAGE, totalItems) + + useEffect(() => { + setPage(1) + }, [ticket.id]) + + useEffect(() => { + if (page > totalPages) { + setPage(totalPages) + } + }, [page, totalPages]) + + if (totalItems === 0) { + return ( + + +

+ Nenhum evento registrado neste ticket ainda. +

+
+
+ ) + } + return ( - - {ticket.timeline.map((entry, index) => { + +
+
+

Linha do tempo

+

+ Mostrando {rangeStart}-{rangeEnd} de {totalItems} eventos +

+
+ {totalPages > 1 ? ( + + + + setPage((previous) => Math.max(1, previous - 1))} + /> + + {paginationRange.map((item, index) => { + if (typeof item === "number") { + return ( + + { + event.preventDefault() + setPage(item) + }} + > + {item} + + + ) + } + return ( + + + + ) + })} + + setPage((previous) => Math.min(totalPages, previous + 1))} + /> + + + + ) : null} +
+ + {currentEvents.map((entry, index) => { const Icon = timelineIcons[entry.type] ?? IconClockHour4 - const isLast = index === ticket.timeline.length - 1 + const isLastGlobal = pageOffset + index === totalItems - 1 return (
- {!isLast && ( + {!isLastGlobal && ( )} diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 0000000..913cf2f --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,105 @@ +import Link from "next/link" +import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +type PaginationProps = React.ComponentProps<"nav"> + +export function Pagination({ className, ...props }: PaginationProps) { + return