mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Fix all comments
This commit is contained in:
@@ -11,7 +11,7 @@ const Button = styled.button<IButtonStyle>`
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
const ButtonIcon = styled.button`
|
const ButtonIcon = styled.button`
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
border: none;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { css } from 'styled-components';
|
|||||||
const CommonInput = css`
|
const CommonInput = css`
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
padding: 10px 20px;
|
|
||||||
font-size: var(--fs-sm-x);
|
|
||||||
|
|
||||||
|
padding: 10px 20px;
|
||||||
|
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
|
||||||
width: 200px;
|
width: 200px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const StyledAppLayout = styled.div`
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 300px 1fr;
|
grid-template-columns: 300px 1fr;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
import { forwardRef } from 'react';
|
import { MutableRefObject, ReactNode, forwardRef } from 'react';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { StyledBody, StyledDialog, StyledTitle } from './styled';
|
import { StyledBody, StyledDialog, StyledTitle } from './styled';
|
||||||
|
|
||||||
|
// Type
|
||||||
|
import { Nullable } from '../../globals/types';
|
||||||
|
|
||||||
export interface IDialogProps {
|
export interface IDialogProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
children?: JSX.Element[] | JSX.Element;
|
children?: ReactNode;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
ref?: React.MutableRefObject<HTMLDialogElement | null>;
|
ref?: MutableRefObject<Nullable<HTMLDialogElement>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dialog = forwardRef((props: IDialogProps, ref) => {
|
const Dialog = forwardRef<HTMLDialogElement, IDialogProps>(
|
||||||
const { title, children, onClose } = props;
|
(props: IDialogProps, ref) => {
|
||||||
return (
|
const { title, children, onClose } = props;
|
||||||
<StyledDialog
|
|
||||||
ref={ref as React.LegacyRef<HTMLDialogElement> | undefined}
|
return (
|
||||||
onClose={onClose}
|
<StyledDialog ref={ref} onClose={onClose}>
|
||||||
>
|
<StyledTitle>{title}</StyledTitle>
|
||||||
<StyledTitle>{title}</StyledTitle>
|
<StyledBody>{children}</StyledBody>
|
||||||
<StyledBody>{children}</StyledBody>
|
</StyledDialog>
|
||||||
</StyledDialog>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default Dialog;
|
export default Dialog;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { FormEvent, ReactNode } from 'react';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { StyledActionBtn, StyledForm } from './styled';
|
import { StyledActionBtn, StyledForm } from './styled';
|
||||||
|
|
||||||
interface IFormProps {
|
interface IFormProps {
|
||||||
children: JSX.Element | JSX.Element[];
|
children: ReactNode;
|
||||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
|
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
const StyledHeader = styled.header`
|
const StyledHeader = styled.header`
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { Error, Label, StyledFormRow } from './styled';
|
import { Error, Label, StyledFormRow } from './styled';
|
||||||
|
|
||||||
interface IFormRow {
|
interface IFormRow {
|
||||||
label: string;
|
label: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
children: JSX.Element | JSX.Element[];
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormRow = ({ label, error, children }: IFormRow) => {
|
const FormRow = ({ label, error, children }: IFormRow) => {
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ const Label = styled.label`
|
|||||||
const Error = styled.p`
|
const Error = styled.p`
|
||||||
color: var(--error-text);
|
color: var(--error-text);
|
||||||
font-size: var(--fs-sm-2x);
|
font-size: var(--fs-sm-2x);
|
||||||
|
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
padding-inline: 5px;
|
padding-inline: 5px;
|
||||||
|
|
||||||
width: 220px;
|
width: 220px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { MouseEvent, ReactNode, useContext, useState } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { HiEllipsisVertical } from 'react-icons/hi2';
|
import { HiEllipsisVertical } from 'react-icons/hi2';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
import { useOutsideClick } from '../../hooks/useOutsideClick';
|
import { useOutsideClick } from '../../hooks/useOutsideClick';
|
||||||
|
|
||||||
|
// Styled
|
||||||
import { StyledMenu, StyledButton, StyledList, StyledToggle } from './styled';
|
import { StyledMenu, StyledButton, StyledList, StyledToggle } from './styled';
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
import MenusContext from '../../contexts/MenuContext';
|
import MenusContext from '../../contexts/MenuContext';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Nullable } from '../../globals/types';
|
||||||
|
|
||||||
interface IButton {
|
interface IButton {
|
||||||
children?: string;
|
children?: string;
|
||||||
icon?: JSX.Element;
|
icon?: ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Menus = ({ children }: { children: JSX.Element }) => {
|
const Menus = ({ children }: { children: ReactNode }) => {
|
||||||
const [openId, setOpenId] = useState('');
|
const [openId, setOpenId] = useState('');
|
||||||
|
|
||||||
const close = () => setOpenId('');
|
const close = () => setOpenId('');
|
||||||
const open = setOpenId;
|
const open = setOpenId;
|
||||||
|
|
||||||
@@ -27,13 +33,14 @@ const Menus = ({ children }: { children: JSX.Element }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Toggle = ({ id }: { id: string }): React.JSX.Element => {
|
const Toggle = ({ id }: { id: string }): ReactNode => {
|
||||||
const { openId, close, open } = useContext(MenusContext);
|
const { openId, close, open } = useContext(MenusContext);
|
||||||
|
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
openId === '' || openId !== id ? open!(id) : close!();
|
openId === '' || openId !== id
|
||||||
|
? open!(id)
|
||||||
|
: close!();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,8 +55,8 @@ const List = ({
|
|||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
children: JSX.Element[];
|
children: ReactNode;
|
||||||
}): React.JSX.Element | null => {
|
}): Nullable<ReactNode> => {
|
||||||
const { openId, close } = useContext(MenusContext);
|
const { openId, close } = useContext(MenusContext);
|
||||||
const ref = useOutsideClick(close!, false);
|
const ref = useOutsideClick(close!, false);
|
||||||
|
|
||||||
@@ -58,9 +65,8 @@ const List = ({
|
|||||||
return <StyledList ref={ref}>{children}</StyledList>;
|
return <StyledList ref={ref}>{children}</StyledList>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = ({ children, icon, onClick }: IButton): React.JSX.Element => {
|
const Button = ({ children, icon, onClick }: IButton): ReactNode => {
|
||||||
const { close } = useContext(MenusContext);
|
const { close } = useContext(MenusContext);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onClick?.();
|
onClick?.();
|
||||||
close!();
|
close!();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import styled from 'styled-components';
|
|||||||
const StyledMessage = styled.p`
|
const StyledMessage = styled.p`
|
||||||
font-size: var(--fs-sm);
|
font-size: var(--fs-sm);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ interface IOrderProps {
|
|||||||
|
|
||||||
const OrderBy = memo(({ options }: IOrderProps) => {
|
const OrderBy = memo(({ options }: IOrderProps) => {
|
||||||
const field = 'orderBy';
|
const field = 'orderBy';
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const currentOrder = searchParams.get(field) || options[0].value;
|
const currentOrder = searchParams.get(field) || options[0].value;
|
||||||
|
|
||||||
const handleClick = (value: string) => {
|
const handleClick = (value: string) => {
|
||||||
searchParams.set(field, value);
|
searchParams.set(field, value);
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,16 @@ interface IOrderBtn {
|
|||||||
|
|
||||||
const OrderButton = styled.button<IOrderBtn>`
|
const OrderButton = styled.button<IOrderBtn>`
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
border-radius: var(--radius-sm);
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.active &&
|
props.active &&
|
||||||
css`
|
css`
|
||||||
@@ -24,12 +32,6 @@ const OrderButton = styled.button<IOrderBtn>`
|
|||||||
cursor: no-drop;
|
cursor: no-drop;
|
||||||
`}
|
`}
|
||||||
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: var(--fs-sm-x);
|
|
||||||
padding: 5px 10px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background-color: var(--hover-dark-background-color);
|
background-color: var(--hover-dark-background-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// Styled
|
||||||
import { StyledSearch } from './styled';
|
import { StyledSearch } from './styled';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useDebounce } from '../../hooks/useDebounce';
|
||||||
|
|
||||||
interface ISearch {
|
interface ISearch {
|
||||||
setPlaceHolder: string;
|
setPlaceHolder: string;
|
||||||
setValueSearch: (phone: string) => void;
|
setValueSearch: (phone: string) => void;
|
||||||
@@ -8,14 +13,11 @@ interface ISearch {
|
|||||||
|
|
||||||
const Search = memo(({ setValueSearch, setPlaceHolder }: ISearch) => {
|
const Search = memo(({ setValueSearch, setPlaceHolder }: ISearch) => {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
|
const debounceValue = useDebounce<string>(query, 700);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeOut = setTimeout(() => {
|
setValueSearch(debounceValue);
|
||||||
setValueSearch(query);
|
}, [debounceValue, setValueSearch]);
|
||||||
}, 500);
|
|
||||||
|
|
||||||
return () => clearTimeout(timeOut);
|
|
||||||
}, [setValueSearch, query]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSearch
|
<StyledSearch
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import {
|
|||||||
UseFormRegister,
|
UseFormRegister,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
|
import { ChangeEventHandler, ReactNode } from 'react';
|
||||||
|
|
||||||
|
// Styled
|
||||||
import { StyledSelect } from './styled';
|
import { StyledSelect } from './styled';
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
|
|
||||||
export interface ISelectOptions {
|
export interface ISelectOptions {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -15,7 +17,7 @@ interface ISelect {
|
|||||||
options: ISelectOptions[];
|
options: ISelectOptions[];
|
||||||
optionsConfigForm?: RegisterOptions<FieldValues, string> | undefined;
|
optionsConfigForm?: RegisterOptions<FieldValues, string> | undefined;
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange?: React.ChangeEventHandler<HTMLSelectElement> | undefined;
|
onChange?: ChangeEventHandler<HTMLSelectElement> | undefined;
|
||||||
id?: string;
|
id?: string;
|
||||||
ariaLabel: string;
|
ariaLabel: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const StyledSidebar = styled.aside`
|
|||||||
padding: 50px 20px;
|
padding: 50px 20px;
|
||||||
|
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
|
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ const Heading = styled.h1`
|
|||||||
font-size: var(--fs-md);
|
font-size: var(--fs-md);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { memo } from 'react';
|
import { ChangeEvent, memo } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Select from '../Select';
|
import Select from '../Select';
|
||||||
@@ -14,8 +14,7 @@ interface ISortByProps {
|
|||||||
const SortBy = memo(({ options }: ISortByProps) => {
|
const SortBy = memo(({ options }: ISortByProps) => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const sortBy = searchParams.get('sortBy') || '';
|
const sortBy = searchParams.get('sortBy') || '';
|
||||||
|
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
searchParams.set('sortBy', event.target.value);
|
searchParams.set('sortBy', event.target.value);
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import TableContext from '../../contexts/TableContext';
|
|||||||
|
|
||||||
export interface ITable {
|
export interface ITable {
|
||||||
columns?: string;
|
columns?: string;
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITableBody<T> {
|
interface ITableBody<T> {
|
||||||
data?: T[];
|
data?: T[];
|
||||||
render?: (value: T) => JSX.Element;
|
render?: CallbackMapFunc<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackMapFunc<T> = (value: T, index: number, array: T[]) => ReactNode;
|
type CallbackMapFunc<T> = (value: T, index: number, array: T[]) => ReactNode;
|
||||||
@@ -28,6 +28,7 @@ const Table = ({ columns, children }: ITable) => {
|
|||||||
|
|
||||||
const Header = ({ children }: ITable) => {
|
const Header = ({ children }: ITable) => {
|
||||||
const { columns } = useContext(TableContext);
|
const { columns } = useContext(TableContext);
|
||||||
|
|
||||||
return <StyledHeader columns={columns}>{children}</StyledHeader>;
|
return <StyledHeader columns={columns}>{children}</StyledHeader>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ const Body = <T,>({ data, render }: ITableBody<T>) => {
|
|||||||
|
|
||||||
const Row = ({ children }: ITable) => {
|
const Row = ({ children }: ITable) => {
|
||||||
const { columns } = useContext(TableContext);
|
const { columns } = useContext(TableContext);
|
||||||
|
|
||||||
return <StyledRow columns={columns}>{children}</StyledRow>;
|
return <StyledRow columns={columns}>{children}</StyledRow>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
// Interfaces
|
||||||
import { ITable } from '.';
|
import { ITable } from '.';
|
||||||
|
|
||||||
const StyledTable = styled.div`
|
const StyledTable = styled.div`
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
border-radius: var(--radius-md);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CommonRow = styled.div<ITable>`
|
const CommonRow = styled.div<ITable>`
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// prettier-ignore
|
|
||||||
const USER_PAGE = {
|
const USER_PAGE = {
|
||||||
SORTBY_OPTIONS: [
|
SORTBY_OPTIONS: [
|
||||||
{
|
{
|
||||||
value: 'id',
|
value: 'id',
|
||||||
label: 'Sort by id',
|
label: 'Sort by id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'name',
|
value: 'name',
|
||||||
label: 'Sort by name',
|
label: 'Sort by name',
|
||||||
},
|
},
|
||||||
@@ -13,11 +12,11 @@ const USER_PAGE = {
|
|||||||
value: 'identifiedCode',
|
value: 'identifiedCode',
|
||||||
label: 'Sort by identified code',
|
label: 'Sort by identified code',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'phone',
|
value: 'phone',
|
||||||
label: 'Sort by phone',
|
label: 'Sort by phone',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'roomId',
|
value: 'roomId',
|
||||||
label: 'Sort by room',
|
label: 'Sort by room',
|
||||||
},
|
},
|
||||||
@@ -35,24 +34,22 @@ const ORDERBY_OPTIONS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const ROOM_PAGE = {
|
const ROOM_PAGE = {
|
||||||
SORTBY_OPTIONS: [
|
SORTBY_OPTIONS: [
|
||||||
{
|
{
|
||||||
value: 'id',
|
value: 'id',
|
||||||
label: 'Sort by id',
|
label: 'Sort by id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'name',
|
value: 'name',
|
||||||
label: 'Sort by name',
|
label: 'Sort by name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'price',
|
value: 'finalPrice',
|
||||||
label: 'Sort by price',
|
label: 'Sort by price',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const VALUE = 'value';
|
const VALUE = 'value';
|
||||||
const ERROR = 'error';
|
const ERROR = 'error';
|
||||||
@@ -70,5 +67,5 @@ export {
|
|||||||
VALUE,
|
VALUE,
|
||||||
ERROR,
|
ERROR,
|
||||||
ORDERBY_OPTIONS,
|
ORDERBY_OPTIONS,
|
||||||
INIT_VALUE_USER_FORM
|
INIT_VALUE_USER_FORM,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createContext } from 'react';
|
import { Dispatch, SetStateAction, createContext } from 'react';
|
||||||
|
|
||||||
interface IMenusContext {
|
interface IMenusContext {
|
||||||
openId?: string;
|
openId?: string;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
open?: React.Dispatch<React.SetStateAction<string>>;
|
open?: Dispatch<SetStateAction<string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenusContext = createContext<IMenusContext>({});
|
const MenusContext = createContext<IMenusContext>({});
|
||||||
|
|||||||
@@ -11,49 +11,21 @@ type TRoom = {
|
|||||||
name: string;
|
name: string;
|
||||||
price: number;
|
price: number;
|
||||||
discount: number;
|
discount: number;
|
||||||
|
finalPrice: number;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TStateSchema = {
|
|
||||||
[key: string]: {
|
|
||||||
value?: string;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type TKeyValue = {
|
|
||||||
[key: string]: boolean | undefined | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TKeyString = {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TPropValues = 'value' | 'error' | boolean;
|
|
||||||
|
|
||||||
type TValidator = {
|
|
||||||
[key: string]: {
|
|
||||||
required?: boolean;
|
|
||||||
validator?: {
|
|
||||||
func?: (value: string) => boolean;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type TResponse<T> = {
|
type TResponse<T> = {
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
msg: string;
|
msg: string;
|
||||||
data?: T;
|
data?: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Nullable<T> = T | null;
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
TUser,
|
TUser,
|
||||||
TRoom,
|
TRoom,
|
||||||
TStateSchema,
|
|
||||||
TKeyValue,
|
|
||||||
TKeyString,
|
|
||||||
TPropValues,
|
|
||||||
TValidator,
|
|
||||||
TResponse,
|
TResponse,
|
||||||
|
Nullable,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,26 +13,25 @@ type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|||||||
* @param method HTTP method
|
* @param method HTTP method
|
||||||
* @returns The status code and message from server
|
* @returns The status code and message from server
|
||||||
*/
|
*/
|
||||||
export const sendRequest = async <T,>(
|
export const sendRequest = async <T>(
|
||||||
path: string,
|
path: string,
|
||||||
method: TMethodRequest = 'GET',
|
method: TMethodRequest = 'GET',
|
||||||
body?: BodyInit,
|
body?: BodyInit
|
||||||
): Promise<TResponse<T>> => {
|
): Promise<TResponse<T>> => {
|
||||||
const response = await fetch(BASE_URL + path, {
|
const response = await fetch(BASE_URL + path, {
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
headers: {
|
headers: {
|
||||||
// prettier-ignore
|
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json() as T;
|
const data = (await response.json()) as T;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: response.status,
|
statusCode: response.status,
|
||||||
msg: response.statusText,
|
msg: response.statusText,
|
||||||
data
|
data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,6 @@
|
|||||||
// Constants
|
// Constants
|
||||||
import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage';
|
import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage';
|
||||||
|
|
||||||
// Types
|
|
||||||
import {
|
|
||||||
TKeyString,
|
|
||||||
TKeyValue,
|
|
||||||
TPropValues,
|
|
||||||
TStateSchema,
|
|
||||||
} from '../globals/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The function check value has type boolean or not
|
|
||||||
* @param value The value need to checked
|
|
||||||
* @returns A boolean indicating whether or not the argument has type boolean
|
|
||||||
*/
|
|
||||||
const isBool = (value: unknown) => {
|
|
||||||
return typeof value === 'boolean';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set required error for value
|
* Set required error for value
|
||||||
* @param value The value set required or not
|
* @param value The value set required or not
|
||||||
@@ -29,50 +12,6 @@ const isRequired = (value: string | number, isRequired: unknown) => {
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get values from props
|
|
||||||
* @param stateSchema StateSchema value
|
|
||||||
* @param prop Prop value (Has 3 type: boolean | "value" | "error")
|
|
||||||
* @returns Return value object depend on props
|
|
||||||
*/
|
|
||||||
const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => {
|
|
||||||
return Object.keys(stateSchema).reduce((field, key) => {
|
|
||||||
field[key] = isBool(prop)
|
|
||||||
? prop
|
|
||||||
: stateSchema[key][prop as Exclude<TPropValues, boolean>];
|
|
||||||
|
|
||||||
return field;
|
|
||||||
}, {} as TKeyValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the object contains values of object pass
|
|
||||||
* @param obj Object need to get value
|
|
||||||
* @returns The object contains value of object
|
|
||||||
*/
|
|
||||||
const getValueFromObj = <T>(obj: T | null = null): TKeyString => {
|
|
||||||
let result = {};
|
|
||||||
|
|
||||||
if (obj) {
|
|
||||||
for (const key of Object.keys(obj)) {
|
|
||||||
const tempValue = obj[key as keyof typeof obj];
|
|
||||||
let value: string | boolean = '';
|
|
||||||
|
|
||||||
if (typeof tempValue === 'string' || typeof tempValue === 'boolean') {
|
|
||||||
value = tempValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof tempValue === 'number') {
|
|
||||||
value = tempValue.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
result = { ...result, [`${key}Value`]: value };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create query url for search
|
* Create query url for search
|
||||||
* @param columnSearch Column want to search
|
* @param columnSearch Column want to search
|
||||||
@@ -87,11 +26,15 @@ const searchQuery = (
|
|||||||
sort: string,
|
sort: string,
|
||||||
order: string
|
order: string
|
||||||
) => {
|
) => {
|
||||||
const phoneParams = keySearch ? `${columnSearch}_like=` + keySearch : '';
|
const phoneParams = keySearch
|
||||||
|
? `${columnSearch}_like=` + keySearch
|
||||||
const sortParams = sort ? '_sort=' + sort : '';
|
: '';
|
||||||
|
const sortParams = sort
|
||||||
const orderParams = order ? '_order=' + order : '';
|
? '_sort=' + sort
|
||||||
|
: '';
|
||||||
|
const orderParams = order
|
||||||
|
? '_order=' + order
|
||||||
|
: '';
|
||||||
const finalParam = [phoneParams, sortParams, orderParams];
|
const finalParam = [phoneParams, sortParams, orderParams];
|
||||||
let query = '';
|
let query = '';
|
||||||
let isFirstParam = true;
|
let isFirstParam = true;
|
||||||
@@ -117,11 +60,4 @@ const formatCurrency = (value: number): string => {
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export { isRequired, searchQuery, formatCurrency, REQUIRED_FIELD_ERROR };
|
||||||
isRequired,
|
|
||||||
getPropValues,
|
|
||||||
getValueFromObj,
|
|
||||||
searchQuery,
|
|
||||||
formatCurrency,
|
|
||||||
REQUIRED_FIELD_ERROR,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Check value is valid name or not
|
||||||
|
* @param value Values need to be checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
|
*/
|
||||||
|
const isValidName = (value: string): boolean => {
|
||||||
|
return /^[a-zA-Z]{2,}(?: [a-zA-Z]+){0,2}$/gm.test(value);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check value is valid number or not
|
* Check value is valid number or not
|
||||||
* @param value Value need to be checked
|
* @param value Value need to be checked
|
||||||
* @returns A boolean indicating whether or not the argument has valid
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
*/
|
*/
|
||||||
const isValidNumber = (value: unknown): boolean => {
|
const isValidNumber = (value: string): boolean => {
|
||||||
return /^[0-9]*$/.test(value as string);
|
return /^[0-9]*$/.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,15 +49,21 @@ const isValidString = (value: string): boolean => {
|
|||||||
*/
|
*/
|
||||||
const skipCheck = () => true;
|
const skipCheck = () => true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check object is empty or not
|
||||||
|
* @param obj Object need to be check
|
||||||
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
|
*/
|
||||||
const isEmptyObj = (obj: object) => {
|
const isEmptyObj = (obj: object) => {
|
||||||
return Object.keys(obj).length === 0;
|
return Object.keys(obj).length === 0;
|
||||||
}
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
isValidName,
|
||||||
isValidNumber,
|
isValidNumber,
|
||||||
isValidPhoneNumber,
|
isValidPhoneNumber,
|
||||||
isValidString,
|
isValidString,
|
||||||
skipCheck,
|
skipCheck,
|
||||||
isValidDiscount,
|
isValidDiscount,
|
||||||
isEmptyObj
|
isEmptyObj,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const useDebounce = <T>(value: T, delay: number): T => {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useDebounce };
|
||||||
@@ -4,9 +4,12 @@ import { useEffect, useState } from 'react';
|
|||||||
import { BASE_URL } from '../constants/path';
|
import { BASE_URL } from '../constants/path';
|
||||||
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config';
|
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config';
|
||||||
|
|
||||||
// Utils
|
// Helpers
|
||||||
import { searchQuery } from '../helpers/utils';
|
import { searchQuery } from '../helpers/utils';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Nullable } from '../globals/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function use to fetch data on server API.
|
* The function use to fetch data on server API.
|
||||||
* @param path The path of url
|
* @param path The path of url
|
||||||
@@ -23,11 +26,11 @@ const useFetch = (
|
|||||||
keyWord: string = '',
|
keyWord: string = '',
|
||||||
tempSortBy?: string,
|
tempSortBy?: string,
|
||||||
tempOrderBy?: string,
|
tempOrderBy?: string,
|
||||||
reload?: boolean,
|
reload?: boolean
|
||||||
) => {
|
) => {
|
||||||
const [data, setData] = useState(null);
|
const [data, setData] = useState(null);
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [errorFetchMsg, setErrorFetchMsg] = useState<string | null>(null);
|
const [errorFetchMsg, setErrorFetchMsg] = useState<Nullable<string>>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Clear data
|
// Clear data
|
||||||
@@ -37,12 +40,9 @@ const useFetch = (
|
|||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
|
|
||||||
// Set default value
|
// Set default value
|
||||||
// prettier-ignore
|
|
||||||
const sortBy = tempSortBy
|
const sortBy = tempSortBy
|
||||||
? tempSortBy
|
? tempSortBy
|
||||||
: DEFAULT_SORT_BY;
|
: DEFAULT_SORT_BY;
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const orderBy = tempOrderBy
|
const orderBy = tempOrderBy
|
||||||
? tempOrderBy
|
? tempOrderBy
|
||||||
: DEFAULT_ORDER_BY;
|
: DEFAULT_ORDER_BY;
|
||||||
@@ -56,7 +56,7 @@ const useFetch = (
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error code: ${response.status} \n Messages: ${response.text}`,
|
`Error code: ${response.status} \n Messages: ${response.text}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { ForwardedRef, useEffect, useRef } from 'react';
|
import { ForwardedRef, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Nullable } from '../globals/types';
|
||||||
|
|
||||||
const useForwardRef = <T>(
|
const useForwardRef = <T>(
|
||||||
ref: ForwardedRef<T>,
|
ref: ForwardedRef<T>,
|
||||||
initialValue: T | null = null
|
initialValue: Nullable<T> = null
|
||||||
) => {
|
) => {
|
||||||
const targetRef = useRef<T>(initialValue);
|
const targetRef = useRef<T>(initialValue);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { MutableRefObject, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Nullable } from '../globals/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook to call handler when click outside element
|
* Custom hook to call handler when click outside element
|
||||||
@@ -9,7 +12,7 @@ import { useEffect, useRef } from 'react';
|
|||||||
export const useOutsideClick = (
|
export const useOutsideClick = (
|
||||||
handler: () => void,
|
handler: () => void,
|
||||||
listeningCapturing = true,
|
listeningCapturing = true,
|
||||||
): React.MutableRefObject<HTMLUListElement | null> => {
|
): MutableRefObject<Nullable<HTMLUListElement>> => {
|
||||||
const ref = useRef<HTMLUListElement>(null);
|
const ref = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,60 +1,69 @@
|
|||||||
import { forwardRef, useEffect } from 'react';
|
import {
|
||||||
|
Dispatch,
|
||||||
|
MutableRefObject,
|
||||||
|
SetStateAction,
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Dialog from '../../components/Dialog';
|
import Dialog from '../../components/Dialog';
|
||||||
import RoomForm from './Form';
|
import RoomForm from './Form';
|
||||||
import { TRoom } from '../../globals/types';
|
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Nullable, TRoom } from '../../globals/types';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useForwardRef } from '../../hooks/useForwardRef';
|
||||||
|
|
||||||
interface IRoomDialog {
|
interface IRoomDialog {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
reload?: boolean;
|
reload?: boolean;
|
||||||
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload?: Dispatch<SetStateAction<boolean>>;
|
||||||
ref?: React.MutableRefObject<HTMLDialogElement | null>;
|
ref?: MutableRefObject<Nullable<HTMLDialogElement>>;
|
||||||
room?: TRoom | null;
|
room?: Nullable<TRoom>;
|
||||||
isAdd?: boolean;
|
isAdd?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomDialog = forwardRef((props: IRoomDialog, ref) => {
|
const RoomDialog = forwardRef<HTMLDialogElement, IRoomDialog>(
|
||||||
const dialogRef = ref as React.MutableRefObject<
|
(props: IRoomDialog, ref) => {
|
||||||
HTMLDialogElement | undefined
|
const dialogRef = useForwardRef(ref);
|
||||||
>;
|
const {
|
||||||
// prettier-ignore
|
onClose,
|
||||||
const {
|
setReload,
|
||||||
onClose,
|
reload,
|
||||||
setReload,
|
room,
|
||||||
reload,
|
isAdd
|
||||||
room,
|
} = props;
|
||||||
isAdd
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogRef.current) {
|
if (dialogRef.current) {
|
||||||
dialogRef.current.addEventListener('click', (e: MouseEvent) => {
|
dialogRef.current.addEventListener('click', (e: MouseEvent) => {
|
||||||
const dialogDimensions = dialogRef.current!.getBoundingClientRect();
|
const dialogDimensions = dialogRef.current!.getBoundingClientRect();
|
||||||
if (
|
if (
|
||||||
e.clientX < dialogDimensions.left ||
|
e.clientX < dialogDimensions.left ||
|
||||||
e.clientX > dialogDimensions.right ||
|
e.clientX > dialogDimensions.right ||
|
||||||
e.clientY < dialogDimensions.top ||
|
e.clientY < dialogDimensions.top ||
|
||||||
e.clientY > dialogDimensions.bottom
|
e.clientY > dialogDimensions.bottom
|
||||||
) {
|
) {
|
||||||
dialogRef.current!.close();
|
dialogRef.current!.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [dialogRef]);
|
}, [dialogRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title={'Add room'} onClose={onClose} ref={dialogRef}>
|
<Dialog title={'Add room'} onClose={onClose} ref={dialogRef}>
|
||||||
<RoomForm
|
<RoomForm
|
||||||
onClose={onClose!}
|
onClose={onClose!}
|
||||||
reload={reload!}
|
reload={reload!}
|
||||||
setReload={setReload!}
|
setReload={setReload!}
|
||||||
room={room}
|
room={room}
|
||||||
isAdd={isAdd!}
|
isAdd={isAdd!}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default RoomDialog;
|
export default RoomDialog;
|
||||||
|
|||||||
@@ -1,20 +1,42 @@
|
|||||||
|
import { Dispatch, SetStateAction, useEffect } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
// Styled
|
||||||
import Button from '../../commons/styles/Button.ts';
|
import Button from '../../commons/styles/Button.ts';
|
||||||
|
import Input from '../../commons/styles/Input.ts';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TRoom } from '../../globals/types.ts';
|
import { Nullable, TRoom } from '../../globals/types.ts';
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
|
// Constants
|
||||||
import { ROOM_PATH } from '../../constants/path.ts';
|
import { ROOM_PATH } from '../../constants/path.ts';
|
||||||
import { STATUS_CODE } from '../../constants/responseStatus.ts';
|
import { STATUS_CODE } from '../../constants/responseStatus.ts';
|
||||||
import toast from 'react-hot-toast';
|
import {
|
||||||
import { ADD_SUCCESS, EDIT_SUCCESS, errorMsg } from '../../constants/messages.ts';
|
ADD_SUCCESS,
|
||||||
|
EDIT_SUCCESS,
|
||||||
|
errorMsg,
|
||||||
|
} from '../../constants/messages.ts';
|
||||||
|
import {
|
||||||
|
INVALID_DISCOUNT,
|
||||||
|
INVALID_FIELD,
|
||||||
|
REQUIRED_FIELD_ERROR,
|
||||||
|
} from '../../constants/formValidateMessage.ts';
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { sendRequest } from '../../helpers/sendRequest.ts';
|
import { sendRequest } from '../../helpers/sendRequest.ts';
|
||||||
import Input from '../../commons/styles/Input.ts';
|
import {
|
||||||
|
isValidDiscount,
|
||||||
|
isValidNumber,
|
||||||
|
isValidString,
|
||||||
|
} from '../../helpers/validators.ts';
|
||||||
|
|
||||||
|
// Components
|
||||||
import FormRow from '../../components/LabelControl/index.tsx';
|
import FormRow from '../../components/LabelControl/index.tsx';
|
||||||
import { INVALID_DISCOUNT, INVALID_FIELD, REQUIRED_FIELD_ERROR } from '../../constants/formValidateMessage.ts';
|
|
||||||
import { isValidDiscount, isValidNumber, isValidString } from '../../helpers/validators.ts';
|
|
||||||
import Form from '../../components/Form/index.tsx';
|
import Form from '../../components/Form/index.tsx';
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
const FormBtn = styled(Button)`
|
const FormBtn = styled(Button)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -29,8 +51,8 @@ const FormBtn = styled(Button)`
|
|||||||
interface IRoomFormProp {
|
interface IRoomFormProp {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
room?: TRoom | null;
|
room?: Nullable<TRoom>;
|
||||||
isAdd: boolean;
|
isAdd: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,20 +73,24 @@ const RoomForm = ({
|
|||||||
} = formMethods;
|
} = formMethods;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(room) {
|
if (room) {
|
||||||
reset(room);
|
reset(room);
|
||||||
}
|
}
|
||||||
}, [room, reset]);
|
}, [room, reset]);
|
||||||
|
|
||||||
// Submit form
|
// Submit form
|
||||||
const onSubmit = async (room: TRoom) => {
|
const onSubmit = async (room: TRoom) => {
|
||||||
|
// Calculate final price
|
||||||
|
room.finalPrice = room.price - (room.price * room.discount) / 100;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
// Add request
|
// Add request
|
||||||
|
|
||||||
const response = await sendRequest(
|
const response = await sendRequest(
|
||||||
ROOM_PATH,
|
ROOM_PATH,
|
||||||
'POST',
|
'POST',
|
||||||
JSON.stringify(room),
|
JSON.stringify(room)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode === STATUS_CODE.CREATE) {
|
if (response.statusCode === STATUS_CODE.CREATE) {
|
||||||
@@ -77,7 +103,7 @@ const RoomForm = ({
|
|||||||
const response = await sendRequest(
|
const response = await sendRequest(
|
||||||
ROOM_PATH + `/${room!.id}`,
|
ROOM_PATH + `/${room!.id}`,
|
||||||
'PUT',
|
'PUT',
|
||||||
JSON.stringify(room),
|
JSON.stringify(room)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == STATUS_CODE.OK) {
|
if (response.statusCode == STATUS_CODE.OK) {
|
||||||
@@ -99,68 +125,66 @@ const RoomForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit(onSubmit)}>
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Input type="hidden" id="id" {...register('id')} />
|
<Input type="hidden" id="id" {...register('id')} />
|
||||||
<FormRow label="Name" error={errors?.name?.message}>
|
<FormRow label="Name" error={errors?.name?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
{...register('name', {
|
{...register('name', {
|
||||||
required: REQUIRED_FIELD_ERROR,
|
required: REQUIRED_FIELD_ERROR,
|
||||||
validate: {
|
validate: {
|
||||||
checkValidName: (value) =>
|
checkValidName: (value) => isValidString(value) || INVALID_FIELD,
|
||||||
isValidString(value) || INVALID_FIELD,
|
},
|
||||||
},
|
onChange: () => trigger('name'),
|
||||||
onChange: () => trigger('name'),
|
})}
|
||||||
})}
|
/>
|
||||||
/>
|
</FormRow>
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<FormRow
|
<FormRow label="Price" error={errors?.price?.message}>
|
||||||
label="Price"
|
<Input
|
||||||
error={errors?.price?.message}
|
type="text"
|
||||||
>
|
id="price"
|
||||||
<Input
|
{...register('price', {
|
||||||
type="text"
|
valueAsNumber: true,
|
||||||
id="price"
|
required: REQUIRED_FIELD_ERROR,
|
||||||
{...register('price', {
|
validate: {
|
||||||
required: REQUIRED_FIELD_ERROR,
|
checkIdentifiedCode: (v) =>
|
||||||
validate: {
|
isValidNumber(v.toString()) || INVALID_FIELD,
|
||||||
checkIdentifiedCode: (v) => isValidNumber(v) || INVALID_FIELD,
|
},
|
||||||
},
|
onChange: () => trigger('price'),
|
||||||
onChange: () => trigger('price'),
|
})}
|
||||||
})}
|
/>
|
||||||
/>
|
</FormRow>
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<FormRow label="discount" error={errors?.discount?.message}>
|
<FormRow label="discount" error={errors?.discount?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="phone"
|
id="phone"
|
||||||
{...register('discount', {
|
{...register('discount', {
|
||||||
required: REQUIRED_FIELD_ERROR,
|
valueAsNumber: true,
|
||||||
validate: {
|
required: REQUIRED_FIELD_ERROR,
|
||||||
checkPhoneNum: (v) => isValidDiscount(v) || INVALID_DISCOUNT,
|
validate: {
|
||||||
},
|
checkPhoneNum: (v) => isValidDiscount(v) || INVALID_DISCOUNT,
|
||||||
onChange: () => trigger('discount'),
|
},
|
||||||
})}
|
onChange: () => trigger('discount'),
|
||||||
/>
|
})}
|
||||||
</FormRow>
|
/>
|
||||||
|
</FormRow>
|
||||||
|
|
||||||
<Form.Action>
|
<Form.Action>
|
||||||
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
|
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
|
||||||
{
|
{
|
||||||
// prettier-ignore
|
isAdd
|
||||||
isAdd
|
? 'Add'
|
||||||
? 'Add'
|
|
||||||
: 'Save'
|
: 'Save'
|
||||||
}
|
}
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={onClose}>
|
<FormBtn type="button" styled="secondary" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
</Form.Action>
|
</Form.Action>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
@@ -15,7 +15,7 @@ import SortBy from '../../components/SortBy';
|
|||||||
import OrderBy from '../../components/OrderBy';
|
import OrderBy from '../../components/OrderBy';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TRoom } from '../../globals/types';
|
import { Nullable, TRoom } from '../../globals/types';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
@@ -27,16 +27,16 @@ import { ORDERBY_OPTIONS, ROOM_PAGE } from '../../constants/variables';
|
|||||||
// Styled
|
// Styled
|
||||||
import Spinner from '../../commons/styles/Spinner';
|
import Spinner from '../../commons/styles/Spinner';
|
||||||
|
|
||||||
// Utils
|
// Helpers
|
||||||
import { sendRequest } from '../../helpers/sendRequest';
|
import { sendRequest } from '../../helpers/sendRequest';
|
||||||
import { formatCurrency } from '../../helpers/utils';
|
import { formatCurrency } from '../../helpers/utils';
|
||||||
|
|
||||||
interface IRoomRow {
|
interface IRoomRow {
|
||||||
room: TRoom;
|
room: TRoom;
|
||||||
openFormDialog: () => void;
|
openFormDialog: () => void;
|
||||||
setRoom: React.Dispatch<React.SetStateAction<TRoom | null>>;
|
setRoom: Dispatch<SetStateAction<Nullable<TRoom>>>;
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomRow = ({
|
const RoomRow = ({
|
||||||
@@ -46,12 +46,12 @@ const RoomRow = ({
|
|||||||
reload,
|
reload,
|
||||||
setReload,
|
setReload,
|
||||||
}: IRoomRow) => {
|
}: IRoomRow) => {
|
||||||
const handleOnEdit = (room: TRoom) => {
|
const handleEdit = (room: TRoom) => {
|
||||||
setRoom(room);
|
setRoom(room);
|
||||||
openFormDialog();
|
openFormDialog();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnDelete = async (room: TRoom) => {
|
const handleDelete = async (room: TRoom) => {
|
||||||
if (confirm(CONFIRM_DELETE)) {
|
if (confirm(CONFIRM_DELETE)) {
|
||||||
const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE');
|
const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE');
|
||||||
|
|
||||||
@@ -64,12 +64,8 @@ const RoomRow = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { id, name, price, discount, status } = room;
|
const { id, name, finalPrice, status } = room;
|
||||||
|
|
||||||
// Calculate final price
|
|
||||||
const finalPrice = price - (price * discount / 100);
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const statusText = status
|
const statusText = status
|
||||||
? 'Unavailable'
|
? 'Unavailable'
|
||||||
: 'Available';
|
: 'Available';
|
||||||
@@ -87,11 +83,11 @@ const RoomRow = ({
|
|||||||
<Menus.List id={id.toString()}>
|
<Menus.List id={id.toString()}>
|
||||||
<Menus.Button
|
<Menus.Button
|
||||||
icon={<RiEditBoxFill />}
|
icon={<RiEditBoxFill />}
|
||||||
onClick={() => handleOnEdit(room)}
|
onClick={() => handleEdit(room)}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Menus.Button>
|
</Menus.Button>
|
||||||
<Menus.Button icon={<HiTrash />} onClick={() => handleOnDelete(room)}>
|
<Menus.Button icon={<HiTrash />} onClick={() => handleDelete(room)}>
|
||||||
Delete
|
Delete
|
||||||
</Menus.Button>
|
</Menus.Button>
|
||||||
</Menus.List>
|
</Menus.List>
|
||||||
@@ -102,9 +98,9 @@ const RoomRow = ({
|
|||||||
|
|
||||||
interface IRoomTable {
|
interface IRoomTable {
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
openFormDialog: () => void;
|
openFormDialog: () => void;
|
||||||
setRoom?: React.Dispatch<React.SetStateAction<TRoom | null>>;
|
setRoom?: Dispatch<SetStateAction<Nullable<TRoom>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomTable = ({
|
const RoomTable = ({
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Direction from '../../commons/styles/Direction';
|
|
||||||
import Button from '../../commons/styles/Button';
|
|
||||||
import RoomTable from './Table';
|
import RoomTable from './Table';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { StyledRoom, Title } from './styled';
|
import { StyledRoom, Title } from './styled';
|
||||||
|
import Direction from '../../commons/styles/Direction';
|
||||||
|
import Button from '../../commons/styles/Button';
|
||||||
import RoomDialog from './Dialog';
|
import RoomDialog from './Dialog';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TRoom } from '../../globals/types';
|
import { Nullable, TRoom } from '../../globals/types';
|
||||||
|
|
||||||
const Room = () => {
|
const Room = () => {
|
||||||
const dialogRef = useRef<HTMLDialogElement>();
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||||
const [reload, setReload] = useState(true);
|
const [reload, setReload] = useState(true);
|
||||||
const [room, setRoom] = useState<TRoom | null>(null);
|
const [room, setRoom] = useState<Nullable<TRoom>>(null);
|
||||||
const [isAdd, setIsAdd] = useState(false);
|
const [isAdd, setIsAdd] = useState(false);
|
||||||
|
|
||||||
const openFormDialog = (isAddForm: boolean = false) => {
|
const openFormDialog = (isAddForm: boolean = false) => {
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import { forwardRef, useEffect } from 'react';
|
import {
|
||||||
|
Dispatch,
|
||||||
|
MutableRefObject,
|
||||||
|
SetStateAction,
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Dialog from '../../components/Dialog';
|
import Dialog from '../../components/Dialog';
|
||||||
import UserForm from './Form';
|
import UserForm from './Form';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TUser } from '../../globals/types';
|
import { Nullable, TUser } from '../../globals/types';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
import { useForwardRef } from '../../hooks/useForwardRef';
|
import { useForwardRef } from '../../hooks/useForwardRef';
|
||||||
|
|
||||||
interface IUserDialog {
|
interface IUserDialog {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
ref: React.MutableRefObject<HTMLDialogElement | null>;
|
ref: MutableRefObject<Nullable<HTMLDialogElement>>;
|
||||||
user: TUser | null;
|
user: Nullable<TUser>;
|
||||||
isAdd: boolean;
|
isAdd: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserDialog = forwardRef((props, ref) => {
|
const UserDialog = forwardRef<HTMLDialogElement, IUserDialog>((props, ref) => {
|
||||||
const dialogRef = useForwardRef(ref);
|
const dialogRef = useForwardRef(ref);
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const {
|
const {
|
||||||
onClose,
|
onClose,
|
||||||
setReload,
|
setReload,
|
||||||
@@ -56,6 +63,6 @@ const UserDialog = forwardRef((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}) as React.FC<IUserDialog>;
|
});
|
||||||
|
|
||||||
export default UserDialog;
|
export default UserDialog;
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import {
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
@@ -8,14 +16,15 @@ import Input from '../../commons/styles/Input';
|
|||||||
// Components
|
// Components
|
||||||
import Form from '../../components/Form';
|
import Form from '../../components/Form';
|
||||||
import FormRow from '../../components/LabelControl/index.tsx';
|
import FormRow from '../../components/LabelControl/index.tsx';
|
||||||
|
import Select, { ISelectOptions } from '../../components/Select';
|
||||||
|
|
||||||
// Utils
|
// Helpers
|
||||||
import { sendRequest } from '../../helpers/sendRequest.ts';
|
import { sendRequest } from '../../helpers/sendRequest.ts';
|
||||||
import {
|
import {
|
||||||
isEmptyObj,
|
isEmptyObj,
|
||||||
|
isValidName,
|
||||||
isValidNumber,
|
isValidNumber,
|
||||||
isValidPhoneNumber,
|
isValidPhoneNumber,
|
||||||
isValidString,
|
|
||||||
} from '../../helpers/validators.ts';
|
} from '../../helpers/validators.ts';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
@@ -26,26 +35,27 @@ import {
|
|||||||
errorMsg,
|
errorMsg,
|
||||||
} from '../../constants/messages.ts';
|
} from '../../constants/messages.ts';
|
||||||
import { USER_PATH } from '../../constants/path.ts';
|
import { USER_PATH } from '../../constants/path.ts';
|
||||||
import Select, { ISelectOptions } from '../../components/Select';
|
|
||||||
|
|
||||||
// Hooks
|
|
||||||
|
|
||||||
// Styled
|
|
||||||
import { FormBtn } from './styled.ts';
|
|
||||||
import {
|
import {
|
||||||
INVALID_FIELD,
|
INVALID_FIELD,
|
||||||
INVALID_PHONE,
|
INVALID_PHONE,
|
||||||
REQUIRED_FIELD_ERROR,
|
REQUIRED_FIELD_ERROR,
|
||||||
} from '../../constants/formValidateMessage.ts';
|
} from '../../constants/formValidateMessage.ts';
|
||||||
import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts';
|
|
||||||
import { TRoom, TUser } from '../../globals/types.ts';
|
|
||||||
import { INIT_VALUE_USER_FORM } from '../../constants/variables.ts';
|
import { INIT_VALUE_USER_FORM } from '../../constants/variables.ts';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { FormBtn } from './styled.ts';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Nullable, TRoom, TUser } from '../../globals/types.ts';
|
||||||
|
|
||||||
interface IUserFormProp {
|
interface IUserFormProp {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
user: TUser | null;
|
user: Nullable<TUser>;
|
||||||
isAdd: boolean;
|
isAdd: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +88,13 @@ const UserForm = ({
|
|||||||
rooms.forEach((item) => {
|
rooms.forEach((item) => {
|
||||||
if (!item.status || tempUser?.roomId === item.id)
|
if (!item.status || tempUser?.roomId === item.id)
|
||||||
options.push({
|
options.push({
|
||||||
label: item.name! as string,
|
label: item.name!,
|
||||||
value: item.id!.toString(),
|
value: item.id!.toString(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.length > 0) {
|
||||||
setOptions(options);
|
setOptions(options);
|
||||||
reset({ roomId: +options[0].value });
|
reset({ roomId: +options[0].value });
|
||||||
}
|
}
|
||||||
@@ -178,8 +190,7 @@ const UserForm = ({
|
|||||||
{...register('name', {
|
{...register('name', {
|
||||||
required: REQUIRED_FIELD_ERROR,
|
required: REQUIRED_FIELD_ERROR,
|
||||||
validate: {
|
validate: {
|
||||||
checkValidName: (value) =>
|
checkValidName: (value) => isValidName(value) || INVALID_FIELD,
|
||||||
isValidString(value) || INVALID_FIELD,
|
|
||||||
},
|
},
|
||||||
onChange: () => trigger('name'),
|
onChange: () => trigger('name'),
|
||||||
})}
|
})}
|
||||||
@@ -196,7 +207,8 @@ const UserForm = ({
|
|||||||
{...register('identifiedCode', {
|
{...register('identifiedCode', {
|
||||||
required: REQUIRED_FIELD_ERROR,
|
required: REQUIRED_FIELD_ERROR,
|
||||||
validate: {
|
validate: {
|
||||||
checkIdentifiedCode: (v) => isValidNumber(v) || INVALID_FIELD,
|
checkIdentifiedCode: (v) =>
|
||||||
|
isValidNumber(v.toString()) || INVALID_FIELD,
|
||||||
},
|
},
|
||||||
onChange: () => trigger('identifiedCode'),
|
onChange: () => trigger('identifiedCode'),
|
||||||
})}
|
})}
|
||||||
@@ -218,25 +230,24 @@ const UserForm = ({
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<FormRow label="Room">
|
<FormRow label="Room">
|
||||||
<Select
|
{options && options.length > 0 ? (
|
||||||
id="roomId"
|
<Select
|
||||||
options={options!}
|
id="roomId"
|
||||||
ariaLabel="RoomId"
|
options={options!}
|
||||||
optionsConfigForm={{
|
ariaLabel="RoomId"
|
||||||
valueAsNumber: true,
|
optionsConfigForm={{
|
||||||
onChange: () => trigger('roomId'),
|
valueAsNumber: true,
|
||||||
}}
|
onChange: () => trigger('roomId'),
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>No room available!</p>
|
||||||
|
)}
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<Form.Action>
|
<Form.Action>
|
||||||
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
|
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
|
||||||
{
|
{isAdd ? 'Add' : 'Save'}
|
||||||
// prettier-ignore
|
|
||||||
isAdd
|
|
||||||
? 'Add'
|
|
||||||
: 'Save'
|
|
||||||
}
|
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={onClose}>
|
<FormBtn type="button" styled="secondary" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { RiEditBoxFill } from 'react-icons/ri';
|
import { RiEditBoxFill } from 'react-icons/ri';
|
||||||
import { IoExit } from 'react-icons/io5';
|
import { IoExit } from 'react-icons/io5';
|
||||||
import { StyledOperationTable } from './styled';
|
|
||||||
import Menus from '../../components/Menus';
|
import Menus from '../../components/Menus';
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import Direction from '../../commons/styles/Direction';
|
import Direction from '../../commons/styles/Direction';
|
||||||
@@ -15,27 +14,30 @@ import SortBy from '../../components/SortBy';
|
|||||||
import OrderBy from '../../components/OrderBy';
|
import OrderBy from '../../components/OrderBy';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TUser } from '../../globals/types';
|
import { Nullable, TUser } from '../../globals/types';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
|
||||||
import { STATUS_CODE } from '../../constants/responseStatus';
|
import { STATUS_CODE } from '../../constants/responseStatus';
|
||||||
import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables';
|
import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables';
|
||||||
|
import { CONFIRM_MESSAGE } from '../../constants/messages';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
|
import { StyledOperationTable } from './styled';
|
||||||
import Spinner from '../../commons/styles/Spinner';
|
import Spinner from '../../commons/styles/Spinner';
|
||||||
|
|
||||||
// Utils
|
// Services
|
||||||
import { updateRoomStatus } from '../../services/roomServices';
|
import { updateRoomStatus } from '../../services/roomServices';
|
||||||
import { checkOutUser } from '../../services/userServices';
|
import { checkOutUser } from '../../services/userServices';
|
||||||
import { CONFIRM_MESSAGE } from '../../constants/messages';
|
|
||||||
|
|
||||||
interface IUserRow {
|
interface IUserRow {
|
||||||
user: TUser;
|
user: TUser;
|
||||||
openFormDialog: () => void;
|
openFormDialog: () => void;
|
||||||
setUser: React.Dispatch<React.SetStateAction<TUser | null>>;
|
setUser: Dispatch<SetStateAction<Nullable<TUser>>>;
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserRow = ({
|
const UserRow = ({
|
||||||
@@ -45,12 +47,12 @@ const UserRow = ({
|
|||||||
reload,
|
reload,
|
||||||
setReload,
|
setReload,
|
||||||
}: IUserRow) => {
|
}: IUserRow) => {
|
||||||
const handleOnEdit = (user: TUser) => {
|
const handleEdit = (user: TUser) => {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
openFormDialog();
|
openFormDialog();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnCheckOut = async (user: TUser) => {
|
const handleCheckOut = async (user: TUser) => {
|
||||||
if (confirm(CONFIRM_MESSAGE)) {
|
if (confirm(CONFIRM_MESSAGE)) {
|
||||||
const resUpdateStatus = await updateRoomStatus(user.roomId, false);
|
const resUpdateStatus = await updateRoomStatus(user.roomId, false);
|
||||||
const resCheckoutUser = await checkOutUser(user);
|
const resCheckoutUser = await checkOutUser(user);
|
||||||
@@ -75,7 +77,11 @@ const UserRow = ({
|
|||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
<div>{identifiedCode}</div>
|
<div>{identifiedCode}</div>
|
||||||
<div>{phone}</div>
|
<div>{phone}</div>
|
||||||
<div>{roomId ? roomId : 'None'}</div>
|
<div>{
|
||||||
|
roomId
|
||||||
|
? roomId
|
||||||
|
: 'None'
|
||||||
|
}</div>
|
||||||
|
|
||||||
<Menus.Menu>
|
<Menus.Menu>
|
||||||
<Menus.Toggle id={id.toString()} />
|
<Menus.Toggle id={id.toString()} />
|
||||||
@@ -83,14 +89,11 @@ const UserRow = ({
|
|||||||
<Menus.List id={id.toString()}>
|
<Menus.List id={id.toString()}>
|
||||||
<Menus.Button
|
<Menus.Button
|
||||||
icon={<RiEditBoxFill />}
|
icon={<RiEditBoxFill />}
|
||||||
onClick={() => handleOnEdit(user)}
|
onClick={() => handleEdit(user)}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Menus.Button>
|
</Menus.Button>
|
||||||
<Menus.Button
|
<Menus.Button icon={<IoExit />} onClick={() => handleCheckOut(user)}>
|
||||||
icon={<IoExit />}
|
|
||||||
onClick={() => handleOnCheckOut(user)}
|
|
||||||
>
|
|
||||||
Check out
|
Check out
|
||||||
</Menus.Button>
|
</Menus.Button>
|
||||||
</Menus.List>
|
</Menus.List>
|
||||||
@@ -101,9 +104,9 @@ const UserRow = ({
|
|||||||
|
|
||||||
interface IUserTable {
|
interface IUserTable {
|
||||||
reload: boolean;
|
reload: boolean;
|
||||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload: Dispatch<SetStateAction<boolean>>;
|
||||||
openFormDialog: () => void;
|
openFormDialog: () => void;
|
||||||
setUser?: React.Dispatch<React.SetStateAction<TUser | null>>;
|
setUser?: Dispatch<SetStateAction<Nullable<TUser>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserTable = ({
|
const UserTable = ({
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Direction from '../../commons/styles/Direction';
|
|
||||||
import Button from '../../commons/styles/Button';
|
|
||||||
import UserTable from './Table';
|
import UserTable from './Table';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
|
import Button from '../../commons/styles/Button';
|
||||||
|
import Direction from '../../commons/styles/Direction';
|
||||||
import { StyledUser, Title } from './styled';
|
import { StyledUser, Title } from './styled';
|
||||||
import UserDialog from './Dialog';
|
import UserDialog from './Dialog';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TUser } from '../../globals/types';
|
import { Nullable, TUser } from '../../globals/types';
|
||||||
|
|
||||||
const User = () => {
|
const User = () => {
|
||||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||||
const [reload, setReload] = useState(true);
|
const [reload, setReload] = useState(true);
|
||||||
const [user, setUser] = useState<TUser | null>(null);
|
const [user, setUser] = useState<Nullable<TUser>>(null);
|
||||||
const [isAdd, setIsAdd] = useState(false);
|
const [isAdd, setIsAdd] = useState(false);
|
||||||
|
|
||||||
const openFormDialog = (isAddForm: boolean = false) => {
|
const openFormDialog = (isAddForm: boolean = false) => {
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { TResponse, TRoom } from '../globals/types';
|
|
||||||
|
// Types
|
||||||
|
import { Nullable, TResponse, TRoom } from '../globals/types';
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { sendRequest } from '../helpers/sendRequest';
|
import { sendRequest } from '../helpers/sendRequest';
|
||||||
|
|
||||||
|
// Constants
|
||||||
import { STATUS_CODE, RESPONSE_MESSAGE } from '../constants/responseStatus';
|
import { STATUS_CODE, RESPONSE_MESSAGE } from '../constants/responseStatus';
|
||||||
import { errorMsg } from '../constants/messages';
|
import { errorMsg } from '../constants/messages';
|
||||||
import { ROOM_PATH } from '../constants/path';
|
import { ROOM_PATH } from '../constants/path';
|
||||||
|
|
||||||
const getAllRoom = async (): Promise<TRoom[] | null> => {
|
/**
|
||||||
|
* Get all rooms from server
|
||||||
|
* @returns Return all rooms in server
|
||||||
|
*/
|
||||||
|
const getAllRoom = async (): Promise<Nullable<TRoom[]>> => {
|
||||||
try {
|
try {
|
||||||
const response = await sendRequest<TRoom[]>(ROOM_PATH);
|
const response = await sendRequest<TRoom[]>(ROOM_PATH);
|
||||||
|
|
||||||
@@ -25,7 +35,12 @@ const getAllRoom = async (): Promise<TRoom[] | null> => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRoom = async (roomId: number): Promise<TRoom | null> => {
|
/**
|
||||||
|
* Get room by id
|
||||||
|
* @param roomId The id room need to be get
|
||||||
|
* @returns Return the room object depend on room id
|
||||||
|
*/
|
||||||
|
const getRoom = async (roomId: number): Promise<Nullable<TRoom>> => {
|
||||||
try {
|
try {
|
||||||
const response = await sendRequest<TRoom>(ROOM_PATH + '/' + roomId);
|
const response = await sendRequest<TRoom>(ROOM_PATH + '/' + roomId);
|
||||||
|
|
||||||
@@ -45,7 +60,12 @@ const getRoom = async (roomId: number): Promise<TRoom | null> => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRoom = async (room: TRoom): Promise<TResponse<TRoom> | null> => {
|
/**
|
||||||
|
* Update room into server
|
||||||
|
* @param room Room object need to be updated
|
||||||
|
* @returns The response object
|
||||||
|
*/
|
||||||
|
const updateRoom = async (room: TRoom): Promise<Nullable<TResponse<TRoom>>> => {
|
||||||
try {
|
try {
|
||||||
const response = await sendRequest<TRoom>(
|
const response = await sendRequest<TRoom>(
|
||||||
ROOM_PATH + '/' + room.id,
|
ROOM_PATH + '/' + room.id,
|
||||||
@@ -67,11 +87,18 @@ const updateRoom = async (room: TRoom): Promise<TResponse<TRoom> | null> => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update room status
|
||||||
|
* @param roomId The id room need to be updated
|
||||||
|
* @param status Status of room
|
||||||
|
* @param roomIdNew The new id room need to be updated
|
||||||
|
* @returns Return the response object
|
||||||
|
*/
|
||||||
const updateRoomStatus = async (
|
const updateRoomStatus = async (
|
||||||
roomId: number,
|
roomId: number,
|
||||||
status: boolean,
|
status: boolean,
|
||||||
roomIdNew?: number
|
roomIdNew?: number
|
||||||
): Promise<TResponse<TRoom> | null> => {
|
): Promise<Nullable<TResponse<TRoom>>> => {
|
||||||
if (!roomIdNew) {
|
if (!roomIdNew) {
|
||||||
const response = await sendRequest<TRoom>(
|
const response = await sendRequest<TRoom>(
|
||||||
ROOM_PATH + '/' + roomId,
|
ROOM_PATH + '/' + roomId,
|
||||||
@@ -80,52 +107,36 @@ const updateRoomStatus = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} else {
|
}
|
||||||
// Update new room status
|
|
||||||
const resNewRoom = await sendRequest<TRoom>(
|
// Update new room status
|
||||||
ROOM_PATH + '/' + roomIdNew,
|
const resNewRoom = await sendRequest<TRoom>(
|
||||||
'PATCH',
|
ROOM_PATH + '/' + roomIdNew,
|
||||||
JSON.stringify({ status: status })
|
'PATCH',
|
||||||
);
|
JSON.stringify({ status: status })
|
||||||
|
);
|
||||||
|
|
||||||
// Update old room status;
|
// Update old room status;
|
||||||
const resOldRoom = await sendRequest<TRoom>(
|
const resOldRoom = await sendRequest<TRoom>(
|
||||||
ROOM_PATH + '/' + roomId,
|
ROOM_PATH + '/' + roomId,
|
||||||
'PATCH',
|
'PATCH',
|
||||||
JSON.stringify({ status: !status })
|
JSON.stringify({ status: !status })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
|
||||||
resNewRoom.statusCode === STATUS_CODE.OK &&
|
|
||||||
resOldRoom.statusCode === STATUS_CODE.OK
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
statusCode: STATUS_CODE.OK,
|
|
||||||
msg: RESPONSE_MESSAGE.UPDATE_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
resNewRoom.statusCode === STATUS_CODE.OK &&
|
||||||
|
resOldRoom.statusCode === STATUS_CODE.OK
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
statusCode: STATUS_CODE.INTERNAL_SERVER_ERROR,
|
statusCode: STATUS_CODE.OK,
|
||||||
msg: 'Something went wrong!',
|
msg: RESPONSE_MESSAGE.UPDATE_SUCCESS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (rooms?.length) {
|
return {
|
||||||
// const oldRoom = rooms.find((room) => room.id === roomId);
|
statusCode: STATUS_CODE.INTERNAL_SERVER_ERROR,
|
||||||
// const newRoom = rooms.find((room) => room.id === roomIdNew);
|
msg: 'Something went wrong!',
|
||||||
|
};
|
||||||
// if(newRoom) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// oldRoom!.status = status;
|
|
||||||
// const response = await updateRoom(oldRoom!);
|
|
||||||
|
|
||||||
// return response;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getRoom, updateRoom, updateRoomStatus, getAllRoom };
|
export { getRoom, updateRoom, updateRoomStatus, getAllRoom };
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Constants
|
||||||
import { errorMsg } from '../constants/messages';
|
import { errorMsg } from '../constants/messages';
|
||||||
import { USER_PATH } from '../constants/path';
|
import { USER_PATH } from '../constants/path';
|
||||||
import { STATUS_CODE } from '../constants/responseStatus';
|
import { STATUS_CODE } from '../constants/responseStatus';
|
||||||
import { TResponse, TUser } from '../globals/types';
|
|
||||||
|
// Types
|
||||||
|
import { Nullable, TResponse, TUser } from '../globals/types';
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { sendRequest } from '../helpers/sendRequest';
|
import { sendRequest } from '../helpers/sendRequest';
|
||||||
|
|
||||||
const updateUser = async (user: TUser): Promise<TResponse<TUser> | null> => {
|
const updateUser = async (user: TUser): Promise<Nullable<TResponse<TUser>>> => {
|
||||||
try {
|
try {
|
||||||
const response = await sendRequest<TUser>(
|
const response = await sendRequest<TUser>(
|
||||||
USER_PATH + '/' + user.id,
|
USER_PATH + '/' + user.id,
|
||||||
@@ -27,7 +33,7 @@ const updateUser = async (user: TUser): Promise<TResponse<TUser> | null> => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkOutUser = async (user: TUser): Promise<TResponse<TUser> | null> => {
|
const checkOutUser = async (user: TUser): Promise<Nullable<TResponse<TUser>>> => {
|
||||||
const tempUser = user;
|
const tempUser = user;
|
||||||
|
|
||||||
if (tempUser) {
|
if (tempUser) {
|
||||||
|
|||||||
Reference in New Issue
Block a user