mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 20:01:59 +00:00
Merge pull request #37 from Nez27/feat/add-pagination-component
Add pagincation components and update code for table, orderBy, modal components
This commit is contained in:
@@ -36,7 +36,7 @@ const Button = styled.button<IButtonStyle>`
|
|||||||
|
|
||||||
&:disabled,
|
&:disabled,
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
cursor: no-drop;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,6 @@ const StyledModal = styled.div`
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
background-color: var(--color-grey-0);
|
|
||||||
|
|
||||||
border-radius: var(--border-radius-lg);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
|
|
||||||
padding: 3.2rem 4rem;
|
|
||||||
|
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const OrderBy = ({ options }: IOrderProps) => {
|
|||||||
<OrderButton
|
<OrderButton
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onClick={() => handleClick(option.value)}
|
onClick={() => handleClick(option.value)}
|
||||||
active={option.value === currentOrder}
|
|
||||||
disabled={option.value === currentOrder}
|
disabled={option.value === currentOrder}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const StyledOrder = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface IOrderBtn {
|
interface IOrderBtn {
|
||||||
active: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderButton = styled.button<IOrderBtn>`
|
const OrderButton = styled.button<IOrderBtn>`
|
||||||
@@ -25,11 +25,11 @@ const OrderButton = styled.button<IOrderBtn>`
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.active &&
|
props.disabled &&
|
||||||
css`
|
css`
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
color: var(--light-text);
|
color: var(--light-text);
|
||||||
cursor: no-drop;
|
cursor: not-allowed;
|
||||||
`}
|
`}
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { DEFAULT_PAGE_SIZE } from '@constant/config';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { Buttons, PaginationBtn, StyledPagination } from './styled';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import { MdKeyboardArrowRight, MdKeyboardArrowLeft } from 'react-icons/md';
|
||||||
|
|
||||||
|
interface IPagination {
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Pagination = ({ count }: IPagination) => {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const currentPage = !searchParams.get('page')
|
||||||
|
? 1
|
||||||
|
: Number(searchParams.get('page'));
|
||||||
|
|
||||||
|
const totalPage = Math.ceil(count / DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
const nextPage = useCallback(() => {
|
||||||
|
const next = currentPage === totalPage
|
||||||
|
? currentPage
|
||||||
|
: currentPage + 1;
|
||||||
|
|
||||||
|
searchParams.set('page', next.toString());
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}, [currentPage, searchParams, totalPage, setSearchParams]);
|
||||||
|
|
||||||
|
const previousPage = useCallback(() => {
|
||||||
|
const previous = currentPage === 1
|
||||||
|
? currentPage
|
||||||
|
: currentPage - 1;
|
||||||
|
|
||||||
|
searchParams.set('page', previous.toString());
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}, [currentPage, searchParams, setSearchParams]);
|
||||||
|
|
||||||
|
const fromIndex = useMemo(
|
||||||
|
() => (currentPage - 1) * DEFAULT_PAGE_SIZE + 1,
|
||||||
|
[currentPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toIndex = useMemo(
|
||||||
|
() => (currentPage === totalPage
|
||||||
|
? count
|
||||||
|
: currentPage * DEFAULT_PAGE_SIZE),
|
||||||
|
[currentPage, count, totalPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
totalPage > 1 && (
|
||||||
|
<StyledPagination>
|
||||||
|
<p>
|
||||||
|
Showing {fromIndex} to {toIndex} of <span>{count}</span> results
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Buttons>
|
||||||
|
<PaginationBtn onClick={previousPage} disabled={currentPage === 1}>
|
||||||
|
<MdKeyboardArrowLeft /> <span>Previous</span>
|
||||||
|
</PaginationBtn>
|
||||||
|
|
||||||
|
<PaginationBtn
|
||||||
|
onClick={nextPage}
|
||||||
|
disabled={currentPage === totalPage}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<MdKeyboardArrowRight />
|
||||||
|
</PaginationBtn>
|
||||||
|
</Buttons>
|
||||||
|
</StyledPagination>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pagination;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
interface IPaginationBtn {
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledPagination = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Buttons = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PaginationBtn = styled.button<IPaginationBtn>`
|
||||||
|
background-color: var(--secondary-btn-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--light-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledPagination, PaginationBtn, Buttons };
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
import { ReactNode, useContext } from 'react';
|
import { ReactNode, useContext } from 'react';
|
||||||
|
|
||||||
// Components
|
// Styled
|
||||||
import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled';
|
import {
|
||||||
|
StyledBody,
|
||||||
|
StyledHeader,
|
||||||
|
StyledRow,
|
||||||
|
StyledTable,
|
||||||
|
StyledFooter,
|
||||||
|
} from './styled';
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
import TableContext from '@context/TableContext';
|
import TableContext from '@context/TableContext';
|
||||||
@@ -33,9 +39,13 @@ const Table = ({ columns, children }: ITable) => {
|
|||||||
const Header = ({ headerColumn }: IHeader) => {
|
const Header = ({ headerColumn }: IHeader) => {
|
||||||
const { columns } = useContext(TableContext);
|
const { columns } = useContext(TableContext);
|
||||||
|
|
||||||
return <StyledHeader columns={columns}>
|
return (
|
||||||
{headerColumn.map((item) => <div key={item}>{item}</div>)}
|
<StyledHeader columns={columns}>
|
||||||
</StyledHeader>;
|
{headerColumn.map((item) => (
|
||||||
|
<div key={item}>{item}</div>
|
||||||
|
))}
|
||||||
|
</StyledHeader>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Body = <T,>({ data, render }: ITableBody<T>) => {
|
const Body = <T,>({ data, render }: ITableBody<T>) => {
|
||||||
@@ -55,5 +65,6 @@ const Row = ({ children }: ITable) => {
|
|||||||
Table.Header = Header;
|
Table.Header = Header;
|
||||||
Table.Body = Body;
|
Table.Body = Body;
|
||||||
Table.Row = Row;
|
Table.Row = Row;
|
||||||
|
Table.Footer = StyledFooter;
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|||||||
@@ -43,4 +43,19 @@ const StyledHeader = styled(CommonRow)`
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledBody, StyledHeader, StyledRow, StyledTable };
|
const StyledFooter = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: var(--footer-table-color);
|
||||||
|
border-bottom-left-radius: var(--radius-md);
|
||||||
|
border-bottom-right-radius: var(--radius-md);
|
||||||
|
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
&:not(:has(*)) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledBody, StyledHeader, StyledRow, StyledTable, StyledFooter };
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
const DEFAULT_SORT_BY = 'id';
|
const DEFAULT_SORT_BY = 'id';
|
||||||
const DEFAULT_ORDER_BY = 'asc';
|
const DEFAULT_ORDER_BY = 'asc';
|
||||||
const supabaseUrl = 'https://pjqujsjzzdrlrgqbxepe.supabase.co'
|
const DEFAULT_PAGE_SIZE = 5;
|
||||||
const supabaseKey = process.env.VITE_SUPABASE_KEY
|
const supabaseUrl = 'https://pjqujsjzzdrlrgqbxepe.supabase.co';
|
||||||
|
const supabaseKey = process.env.VITE_SUPABASE_KEY;
|
||||||
|
|
||||||
export { DEFAULT_SORT_BY, DEFAULT_ORDER_BY, supabaseKey, supabaseUrl };
|
export {
|
||||||
|
DEFAULT_SORT_BY,
|
||||||
|
DEFAULT_ORDER_BY,
|
||||||
|
supabaseKey,
|
||||||
|
supabaseUrl,
|
||||||
|
DEFAULT_PAGE_SIZE,
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user