Implement sort method

This commit is contained in:
2023-10-30 17:54:40 +07:00
parent b071292da7
commit 23a79657b9
9 changed files with 212 additions and 12 deletions
@@ -0,0 +1,72 @@
import { useSearchParams } from 'react-router-dom';
import styled, { css } from 'styled-components';
const StyledFilter = styled.div`
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 5px;
display: flex;
gap: 10px;
`;
interface IFilterBtn {
active: boolean;
}
const FilterButton = styled.button<IFilterBtn>`
border: none;
${(props) =>
props.active &&
css`
background-color: var(--primary-color);
color: var(--light-text);
`}
border-radius: var(--radius-sm);
font-weight: 500;
font-size: var(--fs-sm-x);
padding: 5px 10px;
transition: all 0.3s;
&:hover:not(:disabled) {
background-color: var(--hover-dark-background-color);
}
`;
interface IFilterProps {
options: {
value: string;
label: string;
}[];
}
function OrderBy({ options }: IFilterProps) {
const field = 'orderBy';
const [searchParams, setSearchParams] = useSearchParams();
const currentFilter = searchParams.get(field) || options[0].value;
const handleClick = (value: string) => {
searchParams.set(field, value);
setSearchParams(searchParams);
};
return (
<StyledFilter>
{options.map((option) => (
<FilterButton
key={option.value}
onClick={() => handleClick(option.value)}
active={option.value === currentFilter}
disabled={option.value === currentFilter}
>
{option.label}
</FilterButton>
))}
</StyledFilter>
);
}
export default OrderBy;
@@ -0,0 +1,32 @@
import styled from 'styled-components';
interface ISelect {
options: {
value: string;
label: string;
}[];
value: string;
onChange: React.ChangeEventHandler<HTMLSelectElement>;
}
const StyledSelect = styled.select`
font-size: var(--fs-sm-x);
padding: 5px 10px;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
font-weight: 500;
`;
function Select({ options, value, onChange }: ISelect) {
return (
<StyledSelect value={value} onChange={onChange}>
{options.map((option) => (
<option value={option.value} key={option.value}>
{option.label}
</option>
))}
</StyledSelect>
);
}
export default Select;
@@ -0,0 +1,30 @@
import { useSearchParams } from 'react-router-dom';
import Select from './Select';
interface ISortByProps {
options: {
value: string;
label: string;
}[];
}
const SortBy = ({ options }: ISortByProps) => {
const [searchParams, setSearchParams] = useSearchParams();
const sortBy = searchParams.get('sortBy') || '';
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
searchParams.set('sortBy', event.target.value);
setSearchParams(searchParams);
};
// prettier-ignore
return (
<Select
options={options}
value={sortBy}
onChange={handleChange}
/>
);
};
export default SortBy;
@@ -1,4 +1,4 @@
import { ReactNode, useContext } from 'react'; import { ReactNode, memo, useContext } from 'react';
// Components // Components
import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled'; import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled';
@@ -19,10 +19,10 @@ const Table = ({ columns, children }: ITable) => {
); );
}; };
const Header = ({ children }: ITable) => { const Header = memo(({ children }: ITable) => {
const columns = useContext(TableContext); const columns = useContext(TableContext);
return <StyledHeader columns={columns}>{children}</StyledHeader>; return <StyledHeader columns={columns}>{children}</StyledHeader>;
}; });
const Body = <T,>({ data, render }: ITableBody<T>) => { const Body = <T,>({ data, render }: ITableBody<T>) => {
return ( return (
+23
View File
@@ -55,12 +55,35 @@ const getValueUser = (user: TUser | null = null, prop: string): string => {
return ''; return '';
}; };
const searchQuery = (phone: string, sort: string, order: string) => {
const phoneParams = phone ? 'phone_like=' + phone : '';
const sortParams = sort ? '_sort=' + sort : '_sort=id';
const orderParams = order ? '_order=' + order : '_order=asc';
const finalParam = [phoneParams, sortParams, orderParams];
let query = '';
let isFirstParam = true;
finalParam.forEach((param) => {
if (param) {
if (isFirstParam) {
query = query.concat('', param);
isFirstParam = false;
} else {
query = query.concat('&', param);
}
}
});
return query;
};
export { export {
isObject, isObject,
isRequired, isRequired,
getPropValues, getPropValues,
getValueUser, getValueUser,
addValidator, addValidator,
searchQuery,
VALUE, VALUE,
ERROR, ERROR,
REQUIRED_FIELD_ERROR, REQUIRED_FIELD_ERROR,
+11 -7
View File
@@ -2,8 +2,15 @@ import { useEffect, useState } from 'react';
// Constants // Constants
import { BASE_URL } from '../constants/path'; import { BASE_URL } from '../constants/path';
import { searchQuery } from '../helpers/utils';
export const useFetch = (path: string, phoneNum: string, reload?: boolean) => { export const useFetch = (
path: string,
phoneNum: string,
sortBy: string,
orderBy: string,
reload?: boolean,
) => {
const [data, setData] = useState(null); const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false); const [isPending, setIsPending] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null); const [errorMsg, setErrorMsg] = useState<string | null>(null);
@@ -15,13 +22,10 @@ export const useFetch = (path: string, phoneNum: string, reload?: boolean) => {
const fetchData = async () => { const fetchData = async () => {
setIsPending(true); setIsPending(true);
// prettier-ignore const query = searchQuery(phoneNum, sortBy, orderBy);
const query = phoneNum
? `?phone_like=${phoneNum}`
: '';
try { try {
const response = await fetch(BASE_URL + path + query); const response = await fetch(BASE_URL + path + '?' + query);
const json = await response.json(); const json = await response.json();
if (!response.ok) if (!response.ok)
@@ -39,6 +43,6 @@ export const useFetch = (path: string, phoneNum: string, reload?: boolean) => {
}; };
fetchData(); fetchData();
}, [path, reload, phoneNum]); }, [path, reload, phoneNum, orderBy, sortBy]);
return { data, isPending, errorMsg }; return { data, isPending, errorMsg };
}; };
+37 -1
View File
@@ -25,6 +25,9 @@ import Spinner from '../../commons/styles/Spinner';
// Utils // Utils
import { sendRequest } from '../../helpers/sendRequest'; import { sendRequest } from '../../helpers/sendRequest';
import Search from '../../components/Search'; import Search from '../../components/Search';
import SortBy from '../../components/SortBy';
import OrderBy from '../../components/OrderBy';
import { useSearchParams } from 'react-router-dom';
interface IUserRow { interface IUserRow {
user: TUser; user: TUser;
@@ -106,8 +109,22 @@ const UserTable = ({
setUser, setUser,
}: IUserTable) => { }: IUserTable) => {
const [phoneSearch, setPhoneSearch] = useState(''); const [phoneSearch, setPhoneSearch] = useState('');
const { data, isPending, errorMsg } = useFetch('users', phoneSearch, reload); const [searchParams] = useSearchParams();
const [users, setUsers] = useState<TUser[]>([]); const [users, setUsers] = useState<TUser[]>([]);
const sortByValue = searchParams.get('sortBy')
? searchParams.get('sortBy')!
: '';
const orderByValue = searchParams.get('orderBy')
? searchParams.get('orderBy')!
: '';
const { data, isPending, errorMsg } = useFetch(
'users',
phoneSearch,
sortByValue,
orderByValue,
reload,
);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
@@ -125,6 +142,25 @@ const UserTable = ({
<> <>
<Direction> <Direction>
<StyledOperationTable> <StyledOperationTable>
<OrderBy
options={[
{ value: 'asc', label: 'Ascending' },
{ value: 'desc', label: 'Descending' },
]}
/>
<SortBy
options={[
{ value: 'id', label: 'Sort by id' },
{ value: 'name', label: 'Sort by name' },
{
value: 'identifiedCode',
label: 'Sort by identified code',
},
{ value: 'phone', label: 'Sort by phone' },
{ value: 'room', label: 'Sort by room' },
]}
/>
<Search setPhoneSearch={setPhoneSearch} /> <Search setPhoneSearch={setPhoneSearch} />
</StyledOperationTable> </StyledOperationTable>
+3 -1
View File
@@ -16,7 +16,9 @@ const Title = styled.h2`
`; `;
const StyledOperationTable = styled.div` const StyledOperationTable = styled.div`
text-align: right; display: flex;
gap: 30px;
justify-content: flex-end;
`; `;
export { StyledUser, Title, StyledOperationTable }; export { StyledUser, Title, StyledOperationTable };
@@ -8,6 +8,7 @@
--disabled-btn-color: #7f82a6; --disabled-btn-color: #7f82a6;
--hover-background-color: #f3f4f6; --hover-background-color: #f3f4f6;
--hover-dark-background-color: #d0d0d0;
--light-text: #fff; --light-text: #fff;
--dark-text: #787878; --dark-text: #787878;