From ec8a7b9ec214f42c7697ab0f3628a1be32289d20 Mon Sep 17 00:00:00 2001 From: lizzli Date: Mon, 5 Aug 2024 16:25:53 +0300 Subject: [PATCH 1/2] add pagination reduce paginated data calculations resolve pr comments resolve pr comments change page size to 20 re introduce default margin after tables --- src/app/components/AccountHistory.tsx | 85 +++++++++++++-- src/app/components/Pagination.tsx | 147 ++++++++++++++++++++++++++ src/app/hooks.ts | 43 +++++++- src/app/utils.ts | 23 ++++ 4 files changed, 289 insertions(+), 9 deletions(-) create mode 100644 src/app/components/Pagination.tsx diff --git a/src/app/components/AccountHistory.tsx b/src/app/components/AccountHistory.tsx index f26f6ae9..0ead1916 100644 --- a/src/app/components/AccountHistory.tsx +++ b/src/app/components/AccountHistory.tsx @@ -1,5 +1,10 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; -import { useAppDispatch, useAppSelector, useTranslations } from "hooks"; +import { + useAppDispatch, + useAppSelector, + usePagination, + useTranslations, +} from "hooks"; import { DexterToast } from "./DexterToaster"; import { PairInfo } from "alphadex-sdk-js/lib/models/pair-info"; import "../styles/table.css"; @@ -30,6 +35,7 @@ import { import Papa from "papaparse"; import HoverGradientButton from "./HoverGradientButton"; import { twMerge } from "tailwind-merge"; +import Pagination from "./Pagination"; import { setHideOtherPairs, @@ -97,6 +103,7 @@ export function AccountHistory() { const account = useAppSelector( (state) => state.radix?.selectedAccount?.address ); + const pairAddress = useAppSelector((state) => state.pairSelector.address); useEffect(() => { @@ -202,6 +209,10 @@ function ActionButton({ function DisplayTable() { const t = useTranslations(); + const tableContainerRef = useRef(null); + const [paginationLeft, setPaginationLeft] = useState(0); + + const timeoutRef = useRef(0); const selectedTable = useAppSelector( (state) => state.accountHistory.selectedTable ); @@ -214,8 +225,16 @@ function DisplayTable() { const combinedOrderHistory = useAppSelector(selectCombinedOrderHistory); const combinedOpenOrders = useAppSelector(selectCombinedOpenOrders); + const paginationConf = usePagination( + hideOtherPairs ? orderHistory : combinedOrderHistory + ); + + const { paginatedData: filteredRowsForOrderHistory, setCurrentPage } = + paginationConf; + const handleToggleChange = (e: React.ChangeEvent) => { dispatch(setHideOtherPairs(e.target.checked)); + setCurrentPage(0); }; const tableToShow = useMemo(() => { @@ -230,9 +249,6 @@ function DisplayTable() { }; case Tables.ORDER_HISTORY: - const filteredRowsForOrderHistory = hideOtherPairs - ? orderHistory - : combinedOrderHistory; return { headers: headers[Tables.ORDER_HISTORY], rows: , @@ -249,12 +265,51 @@ function DisplayTable() { orderHistory, selectedTable, hideOtherPairs, - combinedOrderHistory, - combinedOpenOrders, + filteredRowsForOrderHistory, ]); + useEffect(() => { + function calcPaginationLeftOffset() { + if (tableContainerRef.current !== null) { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = window.setTimeout(() => { + if (tableContainerRef.current) { + const scrollLeft = tableContainerRef.current.scrollLeft; + const containerWidth = tableContainerRef.current.offsetWidth; + const leftPosition = scrollLeft + containerWidth / 2; + + setPaginationLeft(leftPosition); + } + }, 300); + } + } + + calcPaginationLeftOffset(); + if (tableContainerRef.current) { + tableContainerRef.current.addEventListener( + "scroll", + calcPaginationLeftOffset + ); + } + + window.addEventListener("resize", calcPaginationLeftOffset); + return () => { + if (tableContainerRef.current) { + tableContainerRef.current.removeEventListener( + "scroll", + calcPaginationLeftOffset + ); + } + + window.removeEventListener("resize", calcPaginationLeftOffset); + }; + }, []); + return ( -
+
- + +
{tableToShow.headers.map((header, i) => ( @@ -298,6 +354,7 @@ function DisplayTable() { {tableToShow.rows} + {selectedTable === Tables.ORDER_HISTORY && (
@@ -313,6 +370,18 @@ function DisplayTable() { )}
+
+ {selectedTable === Tables.ORDER_HISTORY && ( +
+
+ +
+
+ )} +
); } diff --git a/src/app/components/Pagination.tsx b/src/app/components/Pagination.tsx new file mode 100644 index 00000000..16bb2560 --- /dev/null +++ b/src/app/components/Pagination.tsx @@ -0,0 +1,147 @@ +import { useEffect, useRef, useState } from "react"; + +interface PaginationProps { + currentPage: number; + pageSize: number; + setPageSize: (idx: number) => void; + setCurrentPage: (idx: number) => void; + totalDataLength: number; +} + +const MAX_BUTTONS = 6; +const PAGINATION_OPTIONS = [10, 20, 50, 100]; + +const Pagination: React.FC = ({ + currentPage, + totalDataLength, + setCurrentPage, + setPageSize, + pageSize, +}) => { + const totalPages = Math.ceil(totalDataLength / pageSize); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + const dropdownRef = useRef(null); + + useEffect(() => { + if (isDropdownOpen) { + const detectClose = (e: MouseEvent) => { + if ( + isDropdownOpen && + dropdownRef.current && + !dropdownRef.current.contains(e.target as Node) + ) { + setIsDropdownOpen(false); + } + }; + window.addEventListener("mousedown", detectClose); + return () => window.removeEventListener("mousedown", detectClose); + } + return () => {}; + }, [isDropdownOpen]); + + function renderPaginationButton(idx: number) { + return ( +
setCurrentPage(idx)} + role="button" + className={`${ + currentPage === idx ? "bg-dexter-green rounded-full" : "" + } text-white hover:bg-dexter-green p-1 hover:rounded-full h-6 w-6 text-center opacity-90 hover:opacity-100`} + > + {idx + 1} +
+ ); + } + + const renderPaginationButtons = () => { + if (totalPages <= MAX_BUTTONS) { + return Array.from({ length: totalPages }, (_, i) => + renderPaginationButton(i) + ); + } + + const pages = []; + const siblingCount = Math.max(1, Math.floor((MAX_BUTTONS - 3) / 2)); + const showLeftEllipsis = currentPage > siblingCount + 1; + const showRightEllipsis = currentPage < totalPages - siblingCount - 1; + + pages.push(renderPaginationButton(0)); // Always show the first page + + if (showLeftEllipsis) { + pages.push("..."); + } + + const leftSibling = Math.max(1, currentPage - siblingCount); + const rightSibling = Math.min(totalPages - 1, currentPage + siblingCount); + for (let i = leftSibling; i <= rightSibling; i++) { + pages.push(renderPaginationButton(i)); + } + + if (showRightEllipsis) { + pages.push("..."); + } + + pages.push(renderPaginationButton(totalPages)); // Always show the last page + + return pages; + }; + + if (totalPages === 0) return null; + + return ( +
+
+ {/* !TODO: Uncomment after design iteration */} + {/* */} + {isDropdownOpen && ( +
+ {PAGINATION_OPTIONS.map((item, idx) => ( +
{ + const target = e.target as HTMLElement; + setIsDropdownOpen(false); + setPageSize(Number(target.innerText ?? 0)); + setCurrentPage(0); + }} + className={`${ + pageSize === item + ? "bg-dexter-green opacity-90 text-white " + : "" + } hover:bg-dexter-green cursor-pointer px-4 py-2 hover:opacity-100 opacity-90 text-center text-xs`} + key={`${idx}-${item}`} + > + {item} +
+ ))} +
+ )} +
+
+ {renderPaginationButtons()} +
+
+ ); +}; + +export default Pagination; diff --git a/src/app/hooks.ts b/src/app/hooks.ts index 6f8f90a6..79cee60e 100644 --- a/src/app/hooks.ts +++ b/src/app/hooks.ts @@ -1,7 +1,11 @@ import { useDispatch, useSelector } from "react-redux"; import type { TypedUseSelectorHook } from "react-redux"; import type { RootState, AppDispatch } from "./state/store"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + getLocalStoragePaginationValue, + setLocalStoragePaginationValue, +} from "utils"; // https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks export const useAppDispatch: () => AppDispatch = useDispatch; @@ -30,3 +34,40 @@ export const useHydrationErrorFix = () => { return isClient; }; + +export function usePagination(data: T[], paginationId?: string) { + const [currentPage, setCurrentPage] = useState(0); + + const [pageSize, setPageSize] = useState( + getLocalStoragePaginationValue() ?? 20 + ); + const totalDataLength = useMemo(() => data.length, [data]); + + const startIndex = useMemo( + () => currentPage * pageSize, + [currentPage, pageSize] + ); + const endIndex = useMemo(() => startIndex + pageSize, [startIndex, pageSize]); + + const paginatedData = useMemo( + () => data.slice(startIndex, endIndex), + [data, currentPage] + ); + + const updatePsize = useCallback( + (psize: number) => { + setLocalStoragePaginationValue(psize, paginationId); + setPageSize(psize); + }, + [paginationId, setPageSize] + ); + + return { + currentPage, + setCurrentPage, + pageSize, + setPageSize: updatePsize, + paginatedData, + totalDataLength, + }; +} diff --git a/src/app/utils.ts b/src/app/utils.ts index 8ee43ea1..e540f6a9 100644 --- a/src/app/utils.ts +++ b/src/app/utils.ts @@ -517,3 +517,26 @@ export function shortenWalletAddress(address: string): string { const lastPart = address.slice(-20); return `${firstPart}...${lastPart}`; } + +export function setLocalStoragePaginationValue(pageSize: number, id?: string) { + if (typeof window === "undefined") return undefined; + + window.localStorage.setItem( + `pagination:${id ?? window.location.pathname}`, + String(pageSize) + ); +} + +export function getLocalStoragePaginationValue(id?: string) { + if (typeof window === "undefined") return undefined; + + const existingValue = window.localStorage.getItem( + `pagination:${id ?? window.location.pathname}` + ); + if (existingValue !== null) { + const pageNumber = Number(existingValue); + return pageNumber < 1 ? 10 : pageNumber; + } + + return undefined; +} From 9f3ca6434bf60abe542b4e17589e5b9cdfc88528 Mon Sep 17 00:00:00 2001 From: lizzli Date: Wed, 14 Aug 2024 12:19:26 +0300 Subject: [PATCH 2/2] pagination comment fixes --- src/app/components/AccountHistory.tsx | 20 ++++++++------------ src/app/components/Pagination.tsx | 4 ++-- src/app/hooks.ts | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/app/components/AccountHistory.tsx b/src/app/components/AccountHistory.tsx index 0ead1916..df57b9dc 100644 --- a/src/app/components/AccountHistory.tsx +++ b/src/app/components/AccountHistory.tsx @@ -262,15 +262,17 @@ function DisplayTable() { } }, [ openOrders, - orderHistory, selectedTable, hideOtherPairs, filteredRowsForOrderHistory, + combinedOpenOrders, ]); useEffect(() => { + const tableRefNode = tableContainerRef.current; + function calcPaginationLeftOffset() { - if (tableContainerRef.current !== null) { + if (tableRefNode !== null) { if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); } @@ -288,20 +290,14 @@ function DisplayTable() { } calcPaginationLeftOffset(); - if (tableContainerRef.current) { - tableContainerRef.current.addEventListener( - "scroll", - calcPaginationLeftOffset - ); + if (tableRefNode) { + tableRefNode.addEventListener("scroll", calcPaginationLeftOffset); } window.addEventListener("resize", calcPaginationLeftOffset); return () => { - if (tableContainerRef.current) { - tableContainerRef.current.removeEventListener( - "scroll", - calcPaginationLeftOffset - ); + if (tableRefNode) { + tableRefNode.removeEventListener("scroll", calcPaginationLeftOffset); } window.removeEventListener("resize", calcPaginationLeftOffset); diff --git a/src/app/components/Pagination.tsx b/src/app/components/Pagination.tsx index 16bb2560..dec1f9d8 100644 --- a/src/app/components/Pagination.tsx +++ b/src/app/components/Pagination.tsx @@ -47,8 +47,8 @@ const Pagination: React.FC = ({ onClick={() => setCurrentPage(idx)} role="button" className={`${ - currentPage === idx ? "bg-dexter-green rounded-full" : "" - } text-white hover:bg-dexter-green p-1 hover:rounded-full h-6 w-6 text-center opacity-90 hover:opacity-100`} + currentPage === idx ? "bg-dexter-green rounded-full text-black" : "" + } hover:bg-dexter-green hover:text-black p-1 hover:rounded-full h-6 w-6 text-center opacity-90 hover:opacity-100`} > {idx + 1}
diff --git a/src/app/hooks.ts b/src/app/hooks.ts index 79cee60e..bce59f37 100644 --- a/src/app/hooks.ts +++ b/src/app/hooks.ts @@ -51,7 +51,7 @@ export function usePagination(data: T[], paginationId?: string) { const paginatedData = useMemo( () => data.slice(startIndex, endIndex), - [data, currentPage] + [data, startIndex, endIndex] ); const updatePsize = useCallback(