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 all 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
89 changes: 89 additions & 0 deletions backoffice/common/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import ReactPaginate from 'react-paginate';
import { Form } from 'react-bootstrap';
import { DEFAULT_PAGE_SIZE_OPTION } from '@constants/Common';
import styles from 'styles/Pagination.module.css';

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 = DEFAULT_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={`${styles['pagination-helper']} mt-3 mb-3`}>
{paginationControls.goToPage && (
<div
className={`${styles['pagination-tool']} ${
paginationControls.itemsPerPage ? 'me-5' : ''
}`}
>
<p>Go to</p>
<Form.Control
type="number"
value={paginationControls.goToPage.value || ''}
placeholder=""
aria-label="Go to page number"
onChange={paginationControls.goToPage.onChange}
onKeyDown={(e) => e.key === 'Enter' && paginationControls.goToPage?.onSubmit()}
/>
</div>
)}
{paginationControls.itemsPerPage && (
<div className={styles['pagination-tool']}>
<p>Show</p>
<Form.Select
value={paginationControls.itemsPerPage.value}
onChange={paginationControls.itemsPerPage.onChange}
aria-label="Select items per page"
>
{pageSizeOption.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</Form.Select>
</div>
)}
</div>
)}
</div>
);
}

export default Pagination;
71 changes: 71 additions & 0 deletions backoffice/common/hooks/usePagination.ts
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,
DEFAULT_PAGE_SIZE_OPTION,
} from '@constants/Common';

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

const usePagination = (props: UsePaginationProps = {}) => {
const { initialPageNo = DEFAULT_PAGE_NUMBER, initialItemsPerPage = DEFAULT_PAGE_SIZE } = props;
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,
DEFAULT_PAGE_SIZE_OPTION, // Expose page sizes for reuse
};
};

export default usePagination;
29 changes: 8 additions & 21 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_PRODUCT_PAGE_SIZE } from 'constants/Common';
import Pagination from 'common/components/Pagination';
import usePagination from '@commonHooks/usePagination';

type Props = {
show: boolean;
Expand All @@ -20,11 +22,11 @@ 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();

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 +41,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 @@ -82,19 +80,8 @@ 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'}
/>
{totalPage > 0 && (
<Pagination pageNo={pageNo} totalPage={totalPage} onPageChange={changePage} />
)}
</Modal.Body>
<Modal.Footer>
Expand Down
2 changes: 2 additions & 0 deletions backoffice/constants/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ 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 DEFAULT_PAGE_SIZE_OPTION = [5, 10, 15, 20];
export const FORMAT_DATE_YYYY_MM_DD_HH_MM = 'YYYYMMDDHHmmss';

//Column header to export for product
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
28 changes: 7 additions & 21 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 '@commonHooks/usePagination';

import { Brand } from '@catalogModels/Brand';
import { deleteBrand, getPageableBrands } from '@catalogServices/BrandService';
Expand All @@ -16,8 +17,8 @@ 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();

const handleClose: any = () => setShowModalDelete(false);
const handleDelete: any = () => {
Expand All @@ -29,7 +30,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 +56,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 @@ -120,19 +117,8 @@ const BrandList: NextPage = () => {
handleDelete={handleDelete}
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'}
/>
{totalPage > 0 && (
<Pagination pageNo={pageNo} totalPage={totalPage} onPageChange={changePage} />
)}
</>
);
Expand Down
30 changes: 10 additions & 20 deletions backoffice/pages/catalog/categories/[id]/listProduct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ import {
getProductsByCategory,
getCategory,
} from '../../../../modules/catalog/services/CategoryService';
import ReactPaginate from 'react-paginate';
import Pagination from 'common/components/Pagination';
import usePagination from '@commonHooks/usePagination';
import { 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();

useEffect(() => {
if (id)
getCategory(+id).then((data) => {
Expand All @@ -25,15 +28,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 @@ -57,19 +58,8 @@ 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'}
/>
{totalPage > 0 && (
<Pagination pageNo={pageNo} totalPage={totalPage} onPageChange={changePage} />
)}
</>
);
Expand Down
Loading
Loading