From eacf9312c05944ac12dbe4a10139f5efb3a888a9 Mon Sep 17 00:00:00 2001 From: AKILIMAILI CIZUNGU Innocent <51681130+Innocent-Akim@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:20:15 +0200 Subject: [PATCH] [Feat]: Timesheet-Pagination-Hook-and-Component (#3485) * feat:Add custom hook for paginating grouped timesheet data * feat:Added TimesheetPagination component for managing pagination of timesheet data. * feat:Added TimesheetPagination component for managing pagination of timesheet data. --- .../[memberId]/components/TimesheetIcons.tsx | 21 +++ .../components/TimesheetPagination.tsx | 85 ++++++++++++ .../[locale]/timesheet/[memberId]/page.tsx | 47 ++++++- .../hooks/features/useTimesheetPagination.ts | 123 ++++++++++++++++++ apps/web/components/ui/pagination.tsx | 117 +++++++++++++++++ .../calendar/table-time-sheet.tsx | 2 - 6 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetPagination.tsx create mode 100755 apps/web/app/hooks/features/useTimesheetPagination.ts create mode 100755 apps/web/components/ui/pagination.tsx diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetIcons.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetIcons.tsx index 654782aaa..427bc3625 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetIcons.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetIcons.tsx @@ -157,3 +157,24 @@ export const PlusIcon = () => + + +/** + * ArrowLeftIcon + * + * Renders an arrow pointing left using SVG. + * + * @returns {React.ReactElement} - The rendered arrow left icon component. + */ +export const ArrowLeftIcon = () => + + + + + + + + +export const ArrowRightIcon = () => + + diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetPagination.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetPagination.tsx new file mode 100644 index 000000000..89d3c4720 --- /dev/null +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetPagination.tsx @@ -0,0 +1,85 @@ +import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink } from '@components/ui/pagination' +import React from 'react' +import { MdKeyboardDoubleArrowLeft, MdKeyboardDoubleArrowRight } from 'react-icons/md'; +interface TimesheetPaginationProps { + totalPages?: number; + onPageChange?: (page: number) => void; + nextPage?: () => void; + previousPage?: () => void; + goToPage: (page: number) => void; + currentPage?: number; + getPageNumbers: () => (number | string)[]; + dates?: string[]; + totalGroups?: number + +} + +/** + * A component for paginating timesheet data. + * + * @param {TimesheetPaginationProps} props - The props for the component. + * @param {number} [props.totalPages] - The total number of pages. + * @param {(page: number) => void} [props.onPageChange] - A function to call when the page is changed. + * @param {(page: number) => void} props.goToPage - A function to call when the user navigates to a specific page. + * @param {() => void} props.nextPage - A function to call when the user navigates to the next page. + * @param {() => void} props.previousPage - A function to call when the user navigates to the previous page. + * @param {number} [props.currentPage] - The current page number. + * @param {() => (number | string)[]} props.getPageNumbers - A function to get an array of page numbers. + * + * @returns {React.ReactElement} - The component element. + */ +function TimesheetPagination({ totalPages, onPageChange, goToPage, nextPage, previousPage, currentPage, getPageNumbers, dates, totalGroups }: TimesheetPaginationProps) { + return ( + // totalPages > 1 + <> + {totalPages && totalPages > 1 && ( + + +
+ Page {currentPage} of {totalPages} ({dates?.length} items of {totalGroups}) +
+ + + + + + {getPageNumbers().map((pageNumber, index) => ( + + {pageNumber === '...' ? ( + + ) : ( + goToPage(pageNumber as number)}> + {pageNumber} + + )} + + ))} + + + + +
+ ) + } + + ) + +} + +export default TimesheetPagination diff --git a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx index 0b3a6a9cc..d79febb07 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx @@ -21,9 +21,12 @@ import { differenceBetweenHours, getGreeting, secondsToTime } from '@/app/helper import { useTimesheet } from '@/app/hooks/features/useTimesheet'; import { endOfMonth, startOfMonth } from 'date-fns'; import TimesheetDetailModal from './components/TimesheetDetailModal'; +import { useTimesheetPagination } from '@/app/hooks/features/useTimesheetPagination'; +import TimesheetPagination from './components/TimesheetPagination'; type TimesheetViewMode = 'ListView' | 'CalendarView'; export type TimesheetDetailMode = 'Pending' | 'MenHours' | 'MemberWork'; +const TIMESHEET_PAGE_SIZE = 10; type ViewToggleButtonProps = { mode: TimesheetViewMode; @@ -52,13 +55,30 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb to: endOfMonth(new Date()), }); - const { timesheet: filterDataTimesheet, statusTimesheet, loadingTimesheet, isManage } = useTimesheet({ + const { timesheet: filterDataTimesheet, statusTimesheet, loadingTimesheet, isManage, timesheetGroupByDays } = useTimesheet({ startDate: dateRange.from!, endDate: dateRange.to!, timesheetViewMode: timesheetNavigator, inputSearch: search }); + const { + paginatedGroups, + currentPage, + totalPages, + goToPage, + nextPage, + previousPage, + getPageNumbers, + totalGroups, + dates + } = useTimesheetPagination({ + data: filterDataTimesheet, + pageSize: TIMESHEET_PAGE_SIZE + });; + + + React.useEffect(() => { getOrganizationProjects(); }, [getOrganizationProjects]) @@ -103,6 +123,10 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb ], [activeTeam?.name, currentLocale, t] ); + const shouldRenderPagination = + timesheetNavigator === 'ListView' || + (timesheetGroupByDays === 'Daily' && timesheetNavigator === 'CalendarView'); + return ( <> {isTimesheetDetailOpen @@ -230,16 +254,33 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb {timesheetNavigator === 'ListView' ? ( ) : ( )} + {shouldRenderPagination && ( + + )} diff --git a/apps/web/app/hooks/features/useTimesheetPagination.ts b/apps/web/app/hooks/features/useTimesheetPagination.ts new file mode 100755 index 000000000..031b52558 --- /dev/null +++ b/apps/web/app/hooks/features/useTimesheetPagination.ts @@ -0,0 +1,123 @@ +import { useState, useMemo } from 'react'; +import { TimesheetLog } from '@/app/interfaces'; + +export interface GroupedTimesheet { + date: string; + tasks: TimesheetLog[]; +} + +interface PaginationState { + currentPage: number; + totalPages: number; + totalGroups: number; + totalTasks: number; + dates: string[]; +} + +interface UseTimesheetPaginationProps { + data: GroupedTimesheet[]; + pageSize?: number; +} + +/** + * Custom hook for paginating grouped timesheet data. + * + * @param {GroupedTimesheet[]} data - An array of grouped timesheet data, each group containing a date and associated tasks. + * @param {number} [pageSize=10] - The number of groups to show per page. + * + * @returns {Object} - An object containing pagination details and functions. + * @property {GroupedTimesheet[]} paginatedGroups - The currently visible groups based on pagination. + * @property {number} currentPage - The current page number. + * @property {number} totalPages - The total number of pages available. + * @property {number} totalGroups - The total number of groups in the data. + * @property {number} totalTasks - The total number of tasks across all groups. + * @property {string[]} dates - The dates of the currently visible groups. + * @property {function} goToPage - A function to navigate to a specific page. + * @property {function} nextPage - A function to navigate to the next page. + * @property {function} previousPage - A function to navigate to the previous page. + * @property {function} getPageNumbers - A function to get an array of page numbers for pagination controls. + */ + +export function useTimesheetPagination({ + data, + pageSize = 10, +}: UseTimesheetPaginationProps) { + const [currentPage, setCurrentPage] = useState(1); + + const paginationState = useMemo(() => { + const totalGroups = data.length; + const totalPages = Math.max(1, Math.ceil(totalGroups / pageSize)); + const validCurrentPage = Math.min(currentPage, totalPages); + + const startIndex = (validCurrentPage - 1) * pageSize; + const endIndex = Math.min(startIndex + pageSize, totalGroups); + const paginatedDates = data + .slice(startIndex, endIndex) + .map(group => group.date); + + const totalTasks = data.reduce((sum, group) => sum + group.tasks.length, 0); + + return { + currentPage: validCurrentPage, + totalPages, + totalGroups, + totalTasks, + dates: paginatedDates, + }; + }, [data, pageSize, currentPage]); + + const paginatedGroups = useMemo(() => { + const startIndex = (paginationState.currentPage - 1) * pageSize; + const endIndex = Math.min(startIndex + pageSize, data.length); + return data.slice(startIndex, endIndex); + }, [data, pageSize, paginationState.currentPage]); + + const goToPage = (page: number) => { + setCurrentPage(Math.max(1, Math.min(page, paginationState.totalPages))); + }; + + const nextPage = () => { + if (currentPage < paginationState.totalPages) { + goToPage(currentPage + 1); + } + }; + + const previousPage = () => { + if (currentPage > 1) { + goToPage(currentPage - 1); + } + }; + + const getPageNumbers = (): (number | string)[] => { + const { currentPage, totalPages } = paginationState; + const delta = 2; + const range: (number | string)[] = []; + + for (let i = 1; i <= totalPages; i++) { + if ( + i === 1 || + i === totalPages || + (i >= currentPage - delta && i <= currentPage + delta) + ) { + range.push(i); + } else if (range[range.length - 1] !== '...') { + range.push('...'); + } + } + + return range; + }; + + return { + paginatedGroups, + currentPage: paginationState.currentPage, + totalPages: paginationState.totalPages, + totalGroups: paginationState.totalGroups, + totalTasks: paginationState.totalTasks, + dates: paginationState.dates, + goToPage, + nextPage, + previousPage, + getPageNumbers, + }; +} diff --git a/apps/web/components/ui/pagination.tsx b/apps/web/components/ui/pagination.tsx new file mode 100755 index 000000000..6c1859e4c --- /dev/null +++ b/apps/web/components/ui/pagination.tsx @@ -0,0 +1,117 @@ +import * as React from "react" +import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from 'lib/utils'; +import { ButtonProps, buttonVariants } from "components/ui/button" + +const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +