Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backoffice] Update in page pagination UIs vs functionalities #1269

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions backoffice/common/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import ReactPaginate from 'react-paginate';
import { Form } from 'react-bootstrap';
import { PAGE_SIZE_OPTION } from '@commonServices/PaginationService';

interface PaginationControls {
itemsPerPage?: {
value: number;
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
};
goToPage?: {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: () => void;
};
}

interface PaginationProps {
pageNo: number;
totalPage: number;
paginationControls?: PaginationControls;
onPageChange: (selectedItem: { selected: number }) => void;
pageSizeOption?: number[];
}

function Pagination({
pageNo,
totalPage,
paginationControls,
onPageChange,
pageSizeOption = PAGE_SIZE_OPTION,
}: Readonly<PaginationProps>) {
return (
<div>
<ReactPaginate
forcePage={pageNo}
previousLabel="Previous"
nextLabel="Next"
pageCount={totalPage}
onPageChange={onPageChange}
containerClassName="pagination-container"
previousClassName="previous-btn"
nextClassName="next-btn"
disabledClassName="pagination-disabled"
activeClassName="pagination-active"
/>
{paginationControls && (
<div className="pagination-helper mt-3 mb-3">
{paginationControls.goToPage && (
<div className={`pagination-tool ${paginationControls.itemsPerPage ? 'me-5' : ''}`}>
<p>Go to</p>
<Form.Control
type="number"
value={paginationControls.goToPage.value || ''}
onChange={paginationControls.goToPage.onChange}
onKeyDown={(e) => e.key === 'Enter' && paginationControls.goToPage?.onSubmit()}
/>
</div>
)}
{paginationControls.itemsPerPage && (
<div className="pagination-tool">
<p>Show</p>
<Form.Select
value={paginationControls.itemsPerPage.value}
onChange={paginationControls.itemsPerPage.onChange}
>
{pageSizeOption.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</Form.Select>
</div>
)}
</div>
)}
</div>
);
}

export default Pagination;
30 changes: 10 additions & 20 deletions backoffice/common/items/ProductModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Button, Modal, Table } from 'react-bootstrap';
import ReactPaginate from 'react-paginate';
import { useRouter } from 'next/router';

import { Product } from '@catalogModels/Product';
import { getProducts } from '@catalogServices/ProductService';
import { DEFAULT_PAGE_NUMBER, DEFAULT_PRODUCT_PAGE_SIZE } from 'constants/Common';
import Pagination from 'common/components/Pagination';
import usePagination from '@commonServices/PaginationService';

type Props = {
show: boolean;
Expand All @@ -20,11 +22,14 @@ const ShowProductModel = (props: Props) => {

const [selectedProduct, setSelectedProduct] = useState<Product[]>([]);
const [products, setProducts] = useState<Product[]>([]);
const [pageNo, setPageNo] = useState<number>(0);
const [totalPage, setTotalPage] = useState<number>(0);

const { pageNo, totalPage, setTotalPage, changePage } = usePagination({
initialPageNo: DEFAULT_PAGE_NUMBER,
initialItemsPerPage: DEFAULT_PRODUCT_PAGE_SIZE,
});

useEffect(() => {
getProducts(pageNo, '', '').then((data) => {
getProducts(pageNo, DEFAULT_PRODUCT_PAGE_SIZE, '', '').then((data) => {
if (id) {
let filterProduct = data.productContent.filter((product) => product.id !== +id);
setProducts(filterProduct);
Expand All @@ -39,10 +44,6 @@ const ShowProductModel = (props: Props) => {
setSelectedProduct(props.selectedProduct);
}, [props.selectedProduct]);

const changePage = ({ selected }: any) => {
setPageNo(selected);
};

return (
<Modal show={props.show} size="lg" aria-labelledby="contained-modal-title-vcenter" centered>
<Modal.Header>
Expand Down Expand Up @@ -83,18 +84,7 @@ const ShowProductModel = (props: Props) => {
</tbody>
</Table>
{totalPage > 1 && (
<ReactPaginate
forcePage={pageNo}
previousLabel={'Previous'}
nextLabel={'Next'}
pageCount={totalPage}
onPageChange={changePage}
containerClassName={'pagination-container'}
previousClassName={'previous-btn'}
nextClassName={'next-btn'}
disabledClassName={'pagination-disabled'}
activeClassName={'pagination-active'}
/>
<Pagination pageNo={pageNo} totalPage={totalPage} onPageChange={changePage} />
)}
</Modal.Body>
<Modal.Footer>
Expand Down
71 changes: 71 additions & 0 deletions backoffice/common/services/PaginationService.ts

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should create common/hooks and put this hook function to file usePaganation.ts.
Additionally, update the tsconfig.json to add a path alias for @commonHooks pointing to the common/hooks directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created and started using usePagination.ts instead of PaginationService.ts, and I've also updated tsconfig.json with the @commonHooks path alias.
image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks. Please update optional param for usePagination and we can close this.

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState, useMemo, useCallback } from 'react';
import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE } from '@constants/Common';

export const PAGE_SIZE_OPTION = [5, 10, 15, 20];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can move this code to common const, beside with:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated the code to move PAGE_SIZE_OPTION to the common constants file


interface UsePaginationProps {
initialPageNo?: number;
initialItemsPerPage?: number;
}

const usePagination = ({
initialPageNo = DEFAULT_PAGE_NUMBER,
Copy link

@vietvohoangnashtech vietvohoangnashtech Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make argument optional:
image
so that we can use this hook with default page value:
const { pageNo, totalPage, setTotalPage, changePage } = usePagination(); without

{
    initialPageNo: DEFAULT_PAGE_NUMBER,
    initialItemsPerPage: DEFAULT_PAGE_SIZE,
  }

argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usePagination hook has been updated to make the arguments optional, with default values set for initialPageNo and initialItemsPerPage. Let me know if there’s anything else you’d like to adjust.

initialItemsPerPage = DEFAULT_PAGE_SIZE,
}: UsePaginationProps) => {
const [pageNo, setPageNo] = useState<number>(initialPageNo);
const [totalPage, setTotalPage] = useState<number>(1);
const [itemsPerPage, setItemsPerPage] = useState<number>(initialItemsPerPage);
const [goToPage, setGoToPage] = useState<string>('');

const changePage = useCallback(({ selected }: { selected: number }) => {
setPageNo(selected);
}, []);

const handleItemsPerPageChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
const value = parseInt(e.target.value, 10);
if (!isNaN(value) && value > 0) {
setItemsPerPage(value);
setPageNo(0);
}
}, []);

const handleGoToPageChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setGoToPage(e.target.value);
}, []);

const handleGoToPageSubmit = useCallback(() => {
const page = parseInt(goToPage, 10) - 1; // Convert to zero-based index
if (!isNaN(page) && page >= 0 && page < totalPage) {
setPageNo(page);
setGoToPage('');
} else {
alert(`Invalid page number: "${goToPage}" (Expected: 1 - ${totalPage})`);
}
}, [goToPage, totalPage]);

const paginationControls = useMemo(
() => ({
itemsPerPage: {
value: itemsPerPage,
onChange: handleItemsPerPageChange,
},
goToPage: {
value: goToPage,
onChange: handleGoToPageChange,
onSubmit: handleGoToPageSubmit,
},
}),
[itemsPerPage, goToPage, handleGoToPageChange, handleGoToPageSubmit]
);

return {
pageNo,
totalPage,
setTotalPage,
changePage,
paginationControls,
PAGE_SIZE_OPTION, // Expose page sizes for reuse
};
};

export default usePagination;
1 change: 1 addition & 0 deletions backoffice/constants/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const SYSTEM_SETTINGS = '/system/settings';
export const WEBHOOKS_URL = '/webhook';

export const DEFAULT_PAGE_SIZE = 10;
export const DEFAULT_PRODUCT_PAGE_SIZE = 5;
export const DEFAULT_PAGE_NUMBER = 0;
export const FORMAT_DATE_YYYY_MM_DD_HH_MM = 'YYYYMMDDHHmmss';

Expand Down
3 changes: 2 additions & 1 deletion backoffice/modules/catalog/services/CategoryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ export async function deleteCategory(id: number) {

export async function getProductsByCategory(
pageNo: number,
pageSize: number,
categorySlug: string
): Promise<ProductThumbnails> {
const url = `/api/product/storefront/category/${categorySlug}/products?pageNo=${pageNo}`;
const url = `/api/product/storefront/category/${categorySlug}/products?pageNo=${pageNo}&pageSize=${pageSize}`;
return (await apiClientService.get(url)).json();
}
3 changes: 2 additions & 1 deletion backoffice/modules/catalog/services/ProductService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ const baseUrlOptionValue = '/api/product';

export async function getProducts(
pageNo: number,
pageSize: number,
productName: string,
brandName: string
): Promise<Products> {
const url = `${baseUrl}/products?pageNo=${pageNo}&product-name=${productName}&brand-name=${brandName}`;
const url = `${baseUrl}/products?pageNo=${pageNo}&pageSize=${pageSize}&product-name=${productName}&brand-name=${brandName}`;
return (await apiClientService.get(url)).json();
}

Expand Down
4 changes: 2 additions & 2 deletions backoffice/modules/customer/services/CustomerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import apiClientService from '@commonServices/ApiClientService';

const baseUrl = '/api/customer';

export async function getCustomers(pageNo: number): Promise<Customers> {
const url = `${baseUrl}/backoffice/customers?pageNo=${pageNo}`;
export async function getCustomers(pageNo: number, pageSize: number): Promise<Customers> {
const url = `${baseUrl}/backoffice/customers?pageNo=${pageNo}&pageSize=${pageSize}`;
return (await apiClientService.get(url)).json();
}

Expand Down
29 changes: 9 additions & 20 deletions backoffice/pages/catalog/brands/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { NextPage } from 'next';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { Button, Table } from 'react-bootstrap';
import ReactPaginate from 'react-paginate';
import Pagination from 'common/components/Pagination';
import usePagination from '@commonServices/PaginationService';

import { Brand } from '@catalogModels/Brand';
import { deleteBrand, getPageableBrands } from '@catalogServices/BrandService';
Expand All @@ -16,8 +17,11 @@ const BrandList: NextPage = () => {
const [showModalDelete, setShowModalDelete] = useState<boolean>(false);
const [brands, setBrands] = useState<Brand[]>([]);
const [isLoading, setLoading] = useState(false);
const [pageNo, setPageNo] = useState<number>(DEFAULT_PAGE_NUMBER);
const [totalPage, setTotalPage] = useState<number>(1);

const { pageNo, totalPage, setTotalPage, changePage } = usePagination({
initialPageNo: DEFAULT_PAGE_NUMBER,
initialItemsPerPage: DEFAULT_PAGE_SIZE,
});

const handleClose: any = () => setShowModalDelete(false);
const handleDelete: any = () => {
Expand All @@ -29,7 +33,7 @@ const BrandList: NextPage = () => {
.then((response) => {
setShowModalDelete(false);
handleDeletingResponse(response, brandNameWantToDelete);
setPageNo(DEFAULT_PAGE_NUMBER);
changePage({ selected: DEFAULT_PAGE_NUMBER });
getListBrand();
})
.catch((error) => {
Expand All @@ -55,10 +59,6 @@ const BrandList: NextPage = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageNo]);

const changePage = ({ selected }: any) => {
setPageNo(selected);
};

if (isLoading) return <p>Loading...</p>;

if (!brands) return <p>No brand</p>;
Expand Down Expand Up @@ -121,18 +121,7 @@ const BrandList: NextPage = () => {
action="delete"
/>
{totalPage > 1 && (
<ReactPaginate
forcePage={pageNo}
previousLabel={'Previous'}
nextLabel={'Next'}
pageCount={totalPage}
onPageChange={changePage}
containerClassName={'pagination-container'}
previousClassName={'previous-btn'}
nextClassName={'next-btn'}
disabledClassName={'pagination-disabled'}
activeClassName={'pagination-active'}
/>
<Pagination pageNo={pageNo} totalPage={totalPage} onPageChange={changePage} />
)}
</>
);
Expand Down
31 changes: 12 additions & 19 deletions backoffice/pages/catalog/categories/[id]/listProduct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ import {
getProductsByCategory,
getCategory,
} from '../../../../modules/catalog/services/CategoryService';
import ReactPaginate from 'react-paginate';
import Pagination from 'common/components/Pagination';
import usePagination from '@commonServices/PaginationService';
import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE } from '@constants/Common';

const GetProductsByCategory: NextPage = () => {
const [products, setProducts] = useState<ProductThumbnail[]>([]);
const [isLoading, setLoading] = useState(false);
const [pageNo, setPageNo] = useState<number>(0);
const [totalPage, setTotalPage] = useState<number>(1);
const [categorySlug, setCategorySlug] = useState<string>('');
const router = useRouter();
const { id } = router.query;

const { pageNo, totalPage, setTotalPage, changePage } = usePagination({
initialPageNo: DEFAULT_PAGE_NUMBER,
initialItemsPerPage: DEFAULT_PAGE_SIZE,
});

useEffect(() => {
if (id)
getCategory(+id).then((data) => {
Expand All @@ -25,15 +31,13 @@ const GetProductsByCategory: NextPage = () => {
}, [id]);
useEffect(() => {
setLoading(true);
getProductsByCategory(pageNo, categorySlug).then((data) => {
getProductsByCategory(pageNo, DEFAULT_PAGE_SIZE, categorySlug).then((data) => {
setTotalPage(data.totalPages);
setProducts(data.productContent);
setLoading(false);
});
}, [pageNo, categorySlug]);
const changePage = ({ selected }: any) => {
setPageNo(selected);
};

if (isLoading) return <p>Loading...</p>;
if (!products) return <p>No product</p>;
return (
Expand All @@ -58,18 +62,7 @@ const GetProductsByCategory: NextPage = () => {
</tbody>
</Table>
{totalPage > 1 && (
<ReactPaginate
forcePage={pageNo}
previousLabel={'Previous'}
nextLabel={'Next'}
pageCount={totalPage}
onPageChange={changePage}
containerClassName={'pagination-container'}
previousClassName={'previous-btn'}
nextClassName={'next-btn'}
disabledClassName={'pagination-disabled'}
activeClassName={'pagination-active'}
/>
<Pagination pageNo={pageNo} totalPage={totalPage} onPageChange={changePage} />
)}
</>
);
Expand Down
Loading
Loading