mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Implement sort method
This commit is contained in:
@@ -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
|
||||
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);
|
||||
return <StyledHeader columns={columns}>{children}</StyledHeader>;
|
||||
};
|
||||
});
|
||||
|
||||
const Body = <T,>({ data, render }: ITableBody<T>) => {
|
||||
return (
|
||||
|
||||
@@ -55,12 +55,35 @@ const getValueUser = (user: TUser | null = null, prop: string): string => {
|
||||
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 {
|
||||
isObject,
|
||||
isRequired,
|
||||
getPropValues,
|
||||
getValueUser,
|
||||
addValidator,
|
||||
searchQuery,
|
||||
VALUE,
|
||||
ERROR,
|
||||
REQUIRED_FIELD_ERROR,
|
||||
|
||||
@@ -2,8 +2,15 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
// Constants
|
||||
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 [isPending, setIsPending] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
@@ -15,13 +22,10 @@ export const useFetch = (path: string, phoneNum: string, reload?: boolean) => {
|
||||
const fetchData = async () => {
|
||||
setIsPending(true);
|
||||
|
||||
// prettier-ignore
|
||||
const query = phoneNum
|
||||
? `?phone_like=${phoneNum}`
|
||||
: '';
|
||||
const query = searchQuery(phoneNum, sortBy, orderBy);
|
||||
|
||||
try {
|
||||
const response = await fetch(BASE_URL + path + query);
|
||||
const response = await fetch(BASE_URL + path + '?' + query);
|
||||
const json = await response.json();
|
||||
|
||||
if (!response.ok)
|
||||
@@ -39,6 +43,6 @@ export const useFetch = (path: string, phoneNum: string, reload?: boolean) => {
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [path, reload, phoneNum]);
|
||||
}, [path, reload, phoneNum, orderBy, sortBy]);
|
||||
return { data, isPending, errorMsg };
|
||||
};
|
||||
|
||||
@@ -25,6 +25,9 @@ import Spinner from '../../commons/styles/Spinner';
|
||||
// Utils
|
||||
import { sendRequest } from '../../helpers/sendRequest';
|
||||
import Search from '../../components/Search';
|
||||
import SortBy from '../../components/SortBy';
|
||||
import OrderBy from '../../components/OrderBy';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
interface IUserRow {
|
||||
user: TUser;
|
||||
@@ -106,8 +109,22 @@ const UserTable = ({
|
||||
setUser,
|
||||
}: IUserTable) => {
|
||||
const [phoneSearch, setPhoneSearch] = useState('');
|
||||
const { data, isPending, errorMsg } = useFetch('users', phoneSearch, reload);
|
||||
const [searchParams] = useSearchParams();
|
||||
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(() => {
|
||||
if (data) {
|
||||
@@ -125,6 +142,25 @@ const UserTable = ({
|
||||
<>
|
||||
<Direction>
|
||||
<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} />
|
||||
</StyledOperationTable>
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ const Title = styled.h2`
|
||||
`;
|
||||
|
||||
const StyledOperationTable = styled.div`
|
||||
text-align: right;
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
export { StyledUser, Title, StyledOperationTable };
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
--disabled-btn-color: #7f82a6;
|
||||
|
||||
--hover-background-color: #f3f4f6;
|
||||
--hover-dark-background-color: #d0d0d0;
|
||||
|
||||
--light-text: #fff;
|
||||
--dark-text: #787878;
|
||||
|
||||
Reference in New Issue
Block a user