diff --git a/hotel-management/.gitignore b/hotel-management/.gitignore index 8cdfe8e..43c1272 100644 --- a/hotel-management/.gitignore +++ b/hotel-management/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.vscode src/db.json diff --git a/hotel-management/src/commons/styles/Button.ts b/hotel-management/src/commons/styles/Button.ts index 11d30f4..7eb5fed 100644 --- a/hotel-management/src/commons/styles/Button.ts +++ b/hotel-management/src/commons/styles/Button.ts @@ -11,7 +11,7 @@ const Button = styled.button` font-weight: 600; cursor: pointer; - + border-radius: var(--radius-md); ${(props) => diff --git a/hotel-management/src/commons/styles/ButtonIcon.ts b/hotel-management/src/commons/styles/ButtonIcon.ts index 110fea7..9653f45 100644 --- a/hotel-management/src/commons/styles/ButtonIcon.ts +++ b/hotel-management/src/commons/styles/ButtonIcon.ts @@ -2,9 +2,11 @@ import styled from 'styled-components'; const ButtonIcon = styled.button` background: none; - border: none; + padding: 8px; transition: all 0.2s; + + border: none; border-radius: var(--radius-md); &:hover { diff --git a/hotel-management/src/commons/styles/CommonInput.ts b/hotel-management/src/commons/styles/CommonInput.ts index d854062..15adb1f 100644 --- a/hotel-management/src/commons/styles/CommonInput.ts +++ b/hotel-management/src/commons/styles/CommonInput.ts @@ -3,9 +3,11 @@ import { css } from 'styled-components'; const CommonInput = css` border: 1px solid var(--border-color); 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; `; diff --git a/hotel-management/src/components/AppLayout/styled.ts b/hotel-management/src/components/AppLayout/styled.ts index 87fbadd..90bbd79 100644 --- a/hotel-management/src/components/AppLayout/styled.ts +++ b/hotel-management/src/components/AppLayout/styled.ts @@ -4,6 +4,7 @@ const StyledAppLayout = styled.div` display: grid; grid-template-columns: 300px 1fr; grid-template-rows: auto 1fr; + height: 100vh; `; diff --git a/hotel-management/src/components/Dialog/index.tsx b/hotel-management/src/components/Dialog/index.tsx index d3b010d..53b09eb 100644 --- a/hotel-management/src/components/Dialog/index.tsx +++ b/hotel-management/src/components/Dialog/index.tsx @@ -1,30 +1,29 @@ -import { forwardRef } from 'react'; +import { MutableRefObject, ReactNode, forwardRef } from 'react'; // Styled import { StyledBody, StyledDialog, StyledTitle } from './styled'; -export interface IDialogProps { +// Type +import { Nullable } from '../../globals/types'; + +export interface IDialogProps { title?: string; - children?: JSX.Element[] | JSX.Element; + children?: ReactNode; onClose?: () => void; - reload?: boolean; - setReload?: React.Dispatch>; - ref?: React.MutableRefObject; - data?: T | null; - isAdd?: boolean; + ref?: MutableRefObject>; } -const Dialog = forwardRef((props, ref) => { - const { title, children, onClose } = props as IDialogProps; - return ( - | undefined} - onClose={onClose} - > - {title} - {children} - - ); -}) as React.FC>; +const Dialog = forwardRef( + (props: IDialogProps, ref) => { + const { title, children, onClose } = props; + + return ( + + {title} + {children} + + ); + } +); export default Dialog; diff --git a/hotel-management/src/components/Form/index.tsx b/hotel-management/src/components/Form/index.tsx index 5fe4a93..99e2782 100644 --- a/hotel-management/src/components/Form/index.tsx +++ b/hotel-management/src/components/Form/index.tsx @@ -1,9 +1,11 @@ +import { FormEvent, ReactNode } from 'react'; + // Styled import { StyledActionBtn, StyledForm } from './styled'; interface IFormProps { - children: JSX.Element | JSX.Element[]; - onSubmit: (event: React.FormEvent) => void; + children: ReactNode; + onSubmit: (event: FormEvent) => void; id?: string; } diff --git a/hotel-management/src/components/Header/styled.ts b/hotel-management/src/components/Header/styled.ts index 304040e..9ba8124 100644 --- a/hotel-management/src/components/Header/styled.ts +++ b/hotel-management/src/components/Header/styled.ts @@ -2,6 +2,7 @@ import styled from 'styled-components'; const StyledHeader = styled.header` border-bottom: 1px solid var(--border-color); + padding: 20px 40px; display: flex; diff --git a/hotel-management/src/components/LabelControl/index.tsx b/hotel-management/src/components/LabelControl/index.tsx index 11b62ae..cb57530 100644 --- a/hotel-management/src/components/LabelControl/index.tsx +++ b/hotel-management/src/components/LabelControl/index.tsx @@ -1,10 +1,12 @@ +import { ReactNode } from 'react'; + // Styled import { Error, Label, StyledFormRow } from './styled'; interface IFormRow { label: string; error?: string; - children: JSX.Element | JSX.Element[]; + children: ReactNode; } const FormRow = ({ label, error, children }: IFormRow) => { diff --git a/hotel-management/src/components/LabelControl/styled.ts b/hotel-management/src/components/LabelControl/styled.ts index d37bbf4..587fabc 100644 --- a/hotel-management/src/components/LabelControl/styled.ts +++ b/hotel-management/src/components/LabelControl/styled.ts @@ -15,8 +15,10 @@ const Label = styled.label` const Error = styled.p` color: var(--error-text); font-size: var(--fs-sm-2x); + margin-top: 5px; padding-inline: 5px; + width: 220px; `; diff --git a/hotel-management/src/components/Menus/index.tsx b/hotel-management/src/components/Menus/index.tsx index c490e3e..afff48b 100644 --- a/hotel-management/src/components/Menus/index.tsx +++ b/hotel-management/src/components/Menus/index.tsx @@ -1,22 +1,28 @@ -import { useContext, useState } from 'react'; +import { MouseEvent, ReactNode, useContext, useState } from 'react'; // Components import { HiEllipsisVertical } from 'react-icons/hi2'; + +// Hooks import { useOutsideClick } from '../../hooks/useOutsideClick'; + +// Styled import { StyledMenu, StyledButton, StyledList, StyledToggle } from './styled'; // Contexts import MenusContext from '../../contexts/MenuContext'; +// Types +import { Nullable } from '../../globals/types'; + interface IButton { children?: string; - icon?: JSX.Element; + icon?: ReactNode; onClick?: () => void; } -const Menus = ({ children }: { children: JSX.Element }) => { +const Menus = ({ children }: { children: ReactNode }) => { const [openId, setOpenId] = useState(''); - const close = () => 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 handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { e.stopPropagation(); - openId === '' || openId !== id ? open!(id) : close!(); + openId === '' || openId !== id + ? open!(id) + : close!(); }; return ( @@ -48,8 +55,8 @@ const List = ({ children, }: { id: string; - children: JSX.Element[]; -}): React.JSX.Element | null => { + children: ReactNode; +}): Nullable => { const { openId, close } = useContext(MenusContext); const ref = useOutsideClick(close!, false); @@ -58,9 +65,8 @@ const List = ({ return {children}; }; -const Button = ({ children, icon, onClick }: IButton): React.JSX.Element => { +const Button = ({ children, icon, onClick }: IButton): ReactNode => { const { close } = useContext(MenusContext); - const handleClick = () => { onClick?.(); close!(); diff --git a/hotel-management/src/components/Menus/styled.ts b/hotel-management/src/components/Menus/styled.ts index 8d77015..8002912 100644 --- a/hotel-management/src/components/Menus/styled.ts +++ b/hotel-management/src/components/Menus/styled.ts @@ -8,13 +8,13 @@ const StyledMenu = styled.div` `; const StyledButton = styled.button` - width: 100%; text-align: left; background: none; border: none; padding: 10px 20px; font-size: var(--fs-sm-x); transition: all 0.2s; + width: 100%; display: flex; align-items: center; @@ -60,6 +60,7 @@ const StyledList = styled.ul` right: -10px; top: 40px; + width: max-content; `; export { StyledMenu, StyledButton, StyledList, StyledToggle }; diff --git a/hotel-management/src/components/Message/styled.ts b/hotel-management/src/components/Message/styled.ts index 10cc192..2f1d2bd 100644 --- a/hotel-management/src/components/Message/styled.ts +++ b/hotel-management/src/components/Message/styled.ts @@ -3,6 +3,7 @@ import styled from 'styled-components'; const StyledMessage = styled.p` font-size: var(--fs-sm); text-align: center; + padding: 20px; `; diff --git a/hotel-management/src/components/OrderBy/index.tsx b/hotel-management/src/components/OrderBy/index.tsx index e8f0716..c24df2e 100644 --- a/hotel-management/src/components/OrderBy/index.tsx +++ b/hotel-management/src/components/OrderBy/index.tsx @@ -13,10 +13,8 @@ interface IOrderProps { const OrderBy = memo(({ options }: IOrderProps) => { const field = 'orderBy'; - const [searchParams, setSearchParams] = useSearchParams(); const currentOrder = searchParams.get(field) || options[0].value; - const handleClick = (value: string) => { searchParams.set(field, value); diff --git a/hotel-management/src/components/OrderBy/styled.ts b/hotel-management/src/components/OrderBy/styled.ts index ea3325b..bfbfa56 100644 --- a/hotel-management/src/components/OrderBy/styled.ts +++ b/hotel-management/src/components/OrderBy/styled.ts @@ -14,8 +14,16 @@ interface IOrderBtn { const OrderButton = styled.button` 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.active && css` @@ -24,12 +32,6 @@ const OrderButton = styled.button` 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) { background-color: var(--hover-dark-background-color); } diff --git a/hotel-management/src/components/Search/index.tsx b/hotel-management/src/components/Search/index.tsx index 544a832..fa747a2 100644 --- a/hotel-management/src/components/Search/index.tsx +++ b/hotel-management/src/components/Search/index.tsx @@ -1,6 +1,11 @@ import { memo, useEffect, useState } from 'react'; + +// Styled import { StyledSearch } from './styled'; +// Hooks +import { useDebounce } from '../../hooks/useDebounce'; + interface ISearch { setPlaceHolder: string; setValueSearch: (phone: string) => void; @@ -8,14 +13,11 @@ interface ISearch { const Search = memo(({ setValueSearch, setPlaceHolder }: ISearch) => { const [query, setQuery] = useState(''); + const debounceValue = useDebounce(query, 700); useEffect(() => { - const timeOut = setTimeout(() => { - setValueSearch(query); - }, 500); - - return () => clearTimeout(timeOut); - }, [setValueSearch, query]); + setValueSearch(debounceValue); + }, [debounceValue, setValueSearch]); return ( | undefined; value?: string; - onChange?: React.ChangeEventHandler | undefined; + onChange?: ChangeEventHandler | undefined; id?: string; ariaLabel: string; } @@ -54,7 +56,6 @@ const RenderSelect = ({ id={id} value={value} {...register(id!, optionsConfigForm)} - onChange={onChange} > {children} diff --git a/hotel-management/src/components/Sidebar/styled.ts b/hotel-management/src/components/Sidebar/styled.ts index 23053b5..75e26c6 100644 --- a/hotel-management/src/components/Sidebar/styled.ts +++ b/hotel-management/src/components/Sidebar/styled.ts @@ -4,6 +4,7 @@ const StyledSidebar = styled.aside` padding: 50px 20px; grid-row: 1 / 3; + border: 1px solid var(--border-color); `; @@ -11,6 +12,7 @@ const Heading = styled.h1` font-size: var(--fs-md); text-transform: uppercase; text-align: center; + color: var(--primary-color); `; diff --git a/hotel-management/src/components/SortBy/index.tsx b/hotel-management/src/components/SortBy/index.tsx index def89c0..023be55 100644 --- a/hotel-management/src/components/SortBy/index.tsx +++ b/hotel-management/src/components/SortBy/index.tsx @@ -1,5 +1,5 @@ import { useSearchParams } from 'react-router-dom'; -import { memo } from 'react'; +import { ChangeEvent, memo } from 'react'; // Components import Select from '../Select'; @@ -14,8 +14,7 @@ interface ISortByProps { const SortBy = memo(({ options }: ISortByProps) => { const [searchParams, setSearchParams] = useSearchParams(); const sortBy = searchParams.get('sortBy') || ''; - - const handleChange = (event: React.ChangeEvent) => { + const handleChange = (event: ChangeEvent) => { searchParams.set('sortBy', event.target.value); setSearchParams(searchParams); }; diff --git a/hotel-management/src/components/Table/index.tsx b/hotel-management/src/components/Table/index.tsx index d305dc7..bb6107c 100644 --- a/hotel-management/src/components/Table/index.tsx +++ b/hotel-management/src/components/Table/index.tsx @@ -8,12 +8,12 @@ import TableContext from '../../contexts/TableContext'; export interface ITable { columns?: string; - children: React.ReactNode; + children: ReactNode; } interface ITableBody { data?: T[]; - render?: (value: T) => JSX.Element; + render?: CallbackMapFunc; } type CallbackMapFunc = (value: T, index: number, array: T[]) => ReactNode; @@ -28,6 +28,7 @@ const Table = ({ columns, children }: ITable) => { const Header = ({ children }: ITable) => { const { columns } = useContext(TableContext); + return {children}; }; @@ -41,6 +42,7 @@ const Body = ({ data, render }: ITableBody) => { const Row = ({ children }: ITable) => { const { columns } = useContext(TableContext); + return {children}; }; diff --git a/hotel-management/src/components/Table/styled.ts b/hotel-management/src/components/Table/styled.ts index 9219f55..9b1f31e 100644 --- a/hotel-management/src/components/Table/styled.ts +++ b/hotel-management/src/components/Table/styled.ts @@ -1,11 +1,13 @@ import styled from 'styled-components'; + +// Interfaces import { ITable } from '.'; const StyledTable = styled.div` border: 1px solid var(--border-color); + border-radius: var(--radius-md); font-size: 20px; - border-radius: var(--radius-md); `; const CommonRow = styled.div` diff --git a/hotel-management/src/constants/messages.ts b/hotel-management/src/constants/messages.ts index 9d427b5..5a9be64 100644 --- a/hotel-management/src/constants/messages.ts +++ b/hotel-management/src/constants/messages.ts @@ -4,13 +4,17 @@ const errorMsg = (errorCode: number, msg: string) => { const ADD_SUCCESS = 'Add success'; const EDIT_SUCCESS = 'Edit success'; -const CONFIRM_DELETE = 'Are you sure to delete it?'; +const CONFIRM_MESSAGE = 'Do you want to checkout this user?'; +const CONFIRM_DELETE = 'Are you sure to delete it?' const DELETE_SUCCESS = 'Delete success'; +const CHECKOUT_SUCCESS = 'Check out success'; export { ADD_SUCCESS, errorMsg, EDIT_SUCCESS, + CONFIRM_MESSAGE, CONFIRM_DELETE, DELETE_SUCCESS, + CHECKOUT_SUCCESS, }; diff --git a/hotel-management/src/constants/responseStatus.ts b/hotel-management/src/constants/responseStatus.ts new file mode 100644 index 0000000..d279258 --- /dev/null +++ b/hotel-management/src/constants/responseStatus.ts @@ -0,0 +1,12 @@ +const STATUS_CODE = { + OK: 200, + CREATE: 201, + NOT_FOUND: 404, + INTERNAL_SERVER_ERROR: 500, +}; + +const RESPONSE_MESSAGE = { + UPDATE_SUCCESS: 'Update success', +}; + +export { STATUS_CODE, RESPONSE_MESSAGE }; diff --git a/hotel-management/src/constants/statusCode.ts b/hotel-management/src/constants/statusCode.ts deleted file mode 100644 index 587e7ea..0000000 --- a/hotel-management/src/constants/statusCode.ts +++ /dev/null @@ -1,7 +0,0 @@ -const STATUS_CODE = { - OK: 200, - CREATE: 201, - NOT_FOUND: 404, -}; - -export { STATUS_CODE }; diff --git a/hotel-management/src/constants/variables.ts b/hotel-management/src/constants/variables.ts index 6287950..98558b2 100644 --- a/hotel-management/src/constants/variables.ts +++ b/hotel-management/src/constants/variables.ts @@ -1,11 +1,10 @@ -// prettier-ignore const USER_PAGE = { SORTBY_OPTIONS: [ - { + { value: 'id', label: 'Sort by id', }, - { + { value: 'name', label: 'Sort by name', }, @@ -13,73 +12,60 @@ const USER_PAGE = { value: 'identifiedCode', label: 'Sort by identified code', }, - { + { value: 'phone', label: 'Sort by phone', }, - { + { value: 'roomId', label: 'Sort by room', }, ], - ORDERBY_OPTIONS: [ - { - value: 'asc', - label: 'Ascending', - }, - { - value: 'desc', - label: 'Descending', - }, - ], }; -// prettier-ignore +const ORDERBY_OPTIONS = [ + { + value: 'asc', + label: 'Ascending', + }, + { + value: 'desc', + label: 'Descending', + }, +]; + const ROOM_PAGE = { - 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: 'price', + { + value: 'finalPrice', label: 'Sort by price', }, - { - value: 'discount', - label: 'Sort by discount', - }, - { - value: 'status', - label: 'Sort by status', - }, ], -} - -const INITIAL_STATE_SCHEMA = { - id: '', - name: '', - identifiedCode: '', - phone: '', - roomId: 'undefined', - address: '', }; const VALUE = 'value'; const ERROR = 'error'; -export { USER_PAGE, ROOM_PAGE, VALUE, ERROR, INITIAL_STATE_SCHEMA }; +const INIT_VALUE_USER_FORM = { + name: '', + id: 0, + identifiedCode: '', + phone: '', +}; + +export { + USER_PAGE, + ROOM_PAGE, + VALUE, + ERROR, + ORDERBY_OPTIONS, + INIT_VALUE_USER_FORM, +}; diff --git a/hotel-management/src/contexts/MenuContext.ts b/hotel-management/src/contexts/MenuContext.ts index 0a166dc..08ce3c5 100644 --- a/hotel-management/src/contexts/MenuContext.ts +++ b/hotel-management/src/contexts/MenuContext.ts @@ -1,9 +1,9 @@ -import { createContext } from 'react'; +import { Dispatch, SetStateAction, createContext } from 'react'; interface IMenusContext { openId?: string; close?: () => void; - open?: React.Dispatch>; + open?: Dispatch>; } const MenusContext = createContext({}); diff --git a/hotel-management/src/globals/types.ts b/hotel-management/src/globals/types.ts index 46d566c..4d15334 100644 --- a/hotel-management/src/globals/types.ts +++ b/hotel-management/src/globals/types.ts @@ -2,7 +2,6 @@ type TUser = { id: number; name: string; identifiedCode: string; - address: string; phone: string; roomId: number; }; @@ -12,48 +11,21 @@ type TRoom = { name: string; price: number; discount: number; + finalPrice: number; 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 = { +type TResponse = { statusCode: number; msg: string; + data?: T; }; +type Nullable = T | null; + export type { TUser, TRoom, - TStateSchema, - TKeyValue, - TKeyString, - TPropValues, - TValidator, TResponse, + Nullable, }; diff --git a/hotel-management/src/helpers/sendRequest.ts b/hotel-management/src/helpers/sendRequest.ts index 4696504..e851cd1 100644 --- a/hotel-management/src/helpers/sendRequest.ts +++ b/hotel-management/src/helpers/sendRequest.ts @@ -4,7 +4,7 @@ import { BASE_URL } from '../constants/path'; // Types import { TResponse } from '../globals/types'; -type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE'; +type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; /** * The send request method to the server @@ -13,23 +13,25 @@ type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE'; * @param method HTTP method * @returns The status code and message from server */ -export const sendRequest = async ( +export const sendRequest = async ( path: string, - body: BodyInit | null | undefined, method: TMethodRequest = 'GET', -): Promise => { + body?: BodyInit +): Promise> => { const response = await fetch(BASE_URL + path, { method, body, headers: { - // prettier-ignore 'Accept': 'application/json', 'Content-Type': 'application/json', }, }); + const data = (await response.json()) as T; + return { statusCode: response.status, msg: response.statusText, + data, }; }; diff --git a/hotel-management/src/helpers/utils.ts b/hotel-management/src/helpers/utils.ts index 0b0b6cf..3b890d0 100644 --- a/hotel-management/src/helpers/utils.ts +++ b/hotel-management/src/helpers/utils.ts @@ -1,23 +1,6 @@ // Constants 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 * @param value The value set required or not @@ -29,50 +12,6 @@ const isRequired = (value: string | number, isRequired: unknown) => { 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]; - - 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 = (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 * @param columnSearch Column want to search @@ -90,11 +29,9 @@ const searchQuery = ( const phoneParams = keySearch ? `${columnSearch}_like=` + keySearch : ''; - const sortParams = sort ? '_sort=' + sort : ''; - const orderParams = order ? '_order=' + order : ''; @@ -116,10 +53,11 @@ const searchQuery = ( return query; }; -export { - isRequired, - getPropValues, - getValueFromObj, - searchQuery, - REQUIRED_FIELD_ERROR +const formatCurrency = (value: number): string => { + return Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(value); }; + +export { isRequired, searchQuery, formatCurrency, REQUIRED_FIELD_ERROR }; diff --git a/hotel-management/src/helpers/validators.ts b/hotel-management/src/helpers/validators.ts index 2a17471..ae3c974 100644 --- a/hotel-management/src/helpers/validators.ts +++ b/hotel-management/src/helpers/validators.ts @@ -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 * @param value Value need to be checked * @returns A boolean indicating whether or not the argument has valid */ -const isValidNumber = (value: unknown): boolean => { - return /^[0-9]*$/.test(value as string); +const isValidNumber = (value: string): boolean => { + return /^[0-9]*$/.test(value); }; /** @@ -12,8 +21,8 @@ const isValidNumber = (value: unknown): boolean => { * @param value Value need to be checked * @returns A boolean indicating whether or not the argument has valid */ -const isValidDiscount = (value: string): boolean => { - return +value >= 0 && +value <= 100; +const isValidDiscount = (value: number): boolean => { + return value >= 0 && value <= 100; }; /** @@ -40,10 +49,21 @@ const isValidString = (value: string): boolean => { */ 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) => { + return Object.keys(obj).length === 0; +}; + export { + isValidName, isValidNumber, isValidPhoneNumber, isValidString, skipCheck, isValidDiscount, + isEmptyObj, }; diff --git a/hotel-management/src/hooks/useDebounce.ts b/hotel-management/src/hooks/useDebounce.ts new file mode 100644 index 0000000..3af1ecd --- /dev/null +++ b/hotel-management/src/hooks/useDebounce.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react'; + +const useDebounce = (value: T, delay: number): T => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +}; + +export { useDebounce }; diff --git a/hotel-management/src/hooks/useFetch.ts b/hotel-management/src/hooks/useFetch.ts index 080c502..cce60ce 100644 --- a/hotel-management/src/hooks/useFetch.ts +++ b/hotel-management/src/hooks/useFetch.ts @@ -4,9 +4,12 @@ import { useEffect, useState } from 'react'; import { BASE_URL } from '../constants/path'; import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config'; -// Utils +// Helpers import { searchQuery } from '../helpers/utils'; +// Types +import { Nullable } from '../globals/types'; + /** * The function use to fetch data on server API. * @param path The path of url @@ -23,11 +26,11 @@ const useFetch = ( keyWord: string = '', tempSortBy?: string, tempOrderBy?: string, - reload?: boolean, + reload?: boolean ) => { const [data, setData] = useState(null); const [isPending, setIsPending] = useState(false); - const [errorFetchMsg, setErrorFetchMsg] = useState(null); + const [errorFetchMsg, setErrorFetchMsg] = useState>(null); useEffect(() => { // Clear data @@ -37,12 +40,9 @@ const useFetch = ( setIsPending(true); // Set default value - // prettier-ignore const sortBy = tempSortBy ? tempSortBy : DEFAULT_SORT_BY; - - // prettier-ignore const orderBy = tempOrderBy ? tempOrderBy : DEFAULT_ORDER_BY; @@ -56,7 +56,7 @@ const useFetch = ( if (!response.ok) { throw new Error( - `Error code: ${response.status} \n Messages: ${response.text}`, + `Error code: ${response.status} \n Messages: ${response.text}` ); } diff --git a/hotel-management/src/hooks/useForwardRef.ts b/hotel-management/src/hooks/useForwardRef.ts new file mode 100644 index 0000000..8081ec8 --- /dev/null +++ b/hotel-management/src/hooks/useForwardRef.ts @@ -0,0 +1,25 @@ +import { ForwardedRef, useEffect, useRef } from 'react'; + +// Types +import { Nullable } from '../globals/types'; + +const useForwardRef = ( + ref: ForwardedRef, + initialValue: Nullable = null +) => { + const targetRef = useRef(initialValue); + + useEffect(() => { + if (!ref) return; + + if (typeof ref === 'function') { + ref(targetRef.current); + } else { + ref.current = targetRef.current; + } + }, [ref]); + + return targetRef; +}; + +export { useForwardRef }; diff --git a/hotel-management/src/hooks/useOutsideClick.ts b/hotel-management/src/hooks/useOutsideClick.ts index bc6e040..ded42be 100644 --- a/hotel-management/src/hooks/useOutsideClick.ts +++ b/hotel-management/src/hooks/useOutsideClick.ts @@ -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 @@ -9,7 +12,7 @@ import { useEffect, useRef } from 'react'; export const useOutsideClick = ( handler: () => void, listeningCapturing = true, -): React.MutableRefObject => { +): MutableRefObject> => { const ref = useRef(null); useEffect(() => { diff --git a/hotel-management/src/pages/Room/Dialog.tsx b/hotel-management/src/pages/Room/Dialog.tsx index 2b08d17..ea1a8ec 100644 --- a/hotel-management/src/pages/Room/Dialog.tsx +++ b/hotel-management/src/pages/Room/Dialog.tsx @@ -1,52 +1,69 @@ -import { forwardRef, useEffect } from 'react'; +import { + Dispatch, + MutableRefObject, + SetStateAction, + forwardRef, + useEffect, +} from 'react'; // Components -import Dialog, { IDialogProps } from '../../components/Dialog'; +import Dialog from '../../components/Dialog'; import RoomForm from './Form'; // Types -import { TRoom } from '../../globals/types'; +import { Nullable, TRoom } from '../../globals/types'; -const RoomDialog = forwardRef((props, ref) => { - const dialogRef = ref as React.MutableRefObject< - HTMLDialogElement | undefined - >; - // prettier-ignore - const { - onClose, - setReload, - reload, - data, - isAdd - } = props; +// Hooks +import { useForwardRef } from '../../hooks/useForwardRef'; - useEffect(() => { - if (dialogRef.current) { - dialogRef.current.addEventListener('click', (e: MouseEvent) => { - const dialogDimensions = dialogRef.current!.getBoundingClientRect(); - if ( - e.clientX < dialogDimensions.left || - e.clientX > dialogDimensions.right || - e.clientY < dialogDimensions.top || - e.clientY > dialogDimensions.bottom - ) { - dialogRef.current!.close(); - } - }); - } - }, [dialogRef]); +interface IRoomDialog { + onClose?: () => void; + reload?: boolean; + setReload?: Dispatch>; + ref?: MutableRefObject>; + room?: Nullable; + isAdd?: boolean; +} - return ( - - - - ); -}) as React.FC>; +const RoomDialog = forwardRef( + (props: IRoomDialog, ref) => { + const dialogRef = useForwardRef(ref); + const { + onClose, + setReload, + reload, + room, + isAdd + } = props; + + useEffect(() => { + if (dialogRef.current) { + dialogRef.current.addEventListener('click', (e: MouseEvent) => { + const dialogDimensions = dialogRef.current!.getBoundingClientRect(); + if ( + e.clientX < dialogDimensions.left || + e.clientX > dialogDimensions.right || + e.clientY < dialogDimensions.top || + e.clientY > dialogDimensions.bottom + ) { + dialogRef.current!.close(); + } + }); + } + }, [dialogRef]); + + return ( + + + + ); + } +); export default RoomDialog; diff --git a/hotel-management/src/pages/Room/Form.tsx b/hotel-management/src/pages/Room/Form.tsx index b010635..8bb6ff0 100644 --- a/hotel-management/src/pages/Room/Form.tsx +++ b/hotel-management/src/pages/Room/Form.tsx @@ -1,20 +1,42 @@ +import { Dispatch, SetStateAction, useEffect } from 'react'; +import toast from 'react-hot-toast'; import styled from 'styled-components'; + +// Hooks +import { useForm } from 'react-hook-form'; + +// Styled import Button from '../../commons/styles/Button.ts'; +import Input from '../../commons/styles/Input.ts'; // Types -import { TRoom } from '../../globals/types.ts'; -import { useForm } from 'react-hook-form'; +import { Nullable, TRoom } from '../../globals/types.ts'; + +// Constants import { ROOM_PATH } from '../../constants/path.ts'; -import { STATUS_CODE } from '../../constants/statusCode.ts'; -import toast from 'react-hot-toast'; -import { ADD_SUCCESS, EDIT_SUCCESS, errorMsg } from '../../constants/messages.ts'; +import { STATUS_CODE } from '../../constants/responseStatus.ts'; +import { + 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 Input from '../../commons/styles/Input.ts'; +import { + isValidDiscount, + isValidNumber, + isValidString, +} from '../../helpers/validators.ts'; + +// Components import FormRow from '../../components/LabelControl/index.tsx'; -import { INVALID_DISCOUNT, INVALID_FIELD, REQUIRED_FIELD_ERROR } from '../../constants/formValidateMessage.ts'; -import { isValidNumber, isValidString } from '../../helpers/validators.ts'; import Form from '../../components/Form/index.tsx'; -import { useEffect } from 'react'; const FormBtn = styled(Button)` width: 100%; @@ -29,8 +51,8 @@ const FormBtn = styled(Button)` interface IRoomFormProp { onClose: () => void; reload: boolean; - setReload: React.Dispatch>; - room?: TRoom | null; + setReload: Dispatch>; + room?: Nullable; isAdd: boolean; } @@ -51,20 +73,24 @@ const RoomForm = ({ } = formMethods; useEffect(() => { - if(room) { + if (room) { reset(room); } }, [room, reset]); // Submit form const onSubmit = async (room: TRoom) => { + // Calculate final price + room.finalPrice = room.price - (room.price * room.discount) / 100; + try { if (isAdd) { // Add request + const response = await sendRequest( ROOM_PATH, - JSON.stringify(room), - 'POST' + 'POST', + JSON.stringify(room) ); if (response.statusCode === STATUS_CODE.CREATE) { @@ -76,8 +102,8 @@ const RoomForm = ({ // Edit request const response = await sendRequest( ROOM_PATH + `/${room!.id}`, - JSON.stringify(room), - 'PUT' + 'PUT', + JSON.stringify(room) ); if (response.statusCode == STATUS_CODE.OK) { @@ -99,68 +125,66 @@ const RoomForm = ({ }; return ( -
- - - - isValidString(value) || INVALID_FIELD, - }, - onChange: () => trigger('name'), - })} - /> - + + + + isValidString(value) || INVALID_FIELD, + }, + onChange: () => trigger('name'), + })} + /> + - - isValidNumber(v) || INVALID_FIELD, - }, - onChange: () => trigger('price'), - })} - /> - + + + isValidNumber(v.toString()) || INVALID_FIELD, + }, + onChange: () => trigger('price'), + })} + /> + - - isValidNumber(v) || INVALID_DISCOUNT, - }, - onChange: () => trigger('discount'), - })} - /> - + + isValidDiscount(v) || INVALID_DISCOUNT, + }, + onChange: () => trigger('discount'), + })} + /> + - - - { - // prettier-ignore - isAdd - ? 'Add' + + + { + isAdd + ? 'Add' : 'Save' - } - - - Close - - - + } + + + Close + +
+ ); }; diff --git a/hotel-management/src/pages/Room/Table.tsx b/hotel-management/src/pages/Room/Table.tsx index 81cd4e6..74d6e79 100644 --- a/hotel-management/src/pages/Room/Table.tsx +++ b/hotel-management/src/pages/Room/Table.tsx @@ -1,9 +1,9 @@ import { useSearchParams } from 'react-router-dom'; -import { useEffect, useState } from 'react'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import toast from 'react-hot-toast'; // Components -import { HiSquare2Stack } from 'react-icons/hi2'; +import { RiEditBoxFill } from 'react-icons/ri'; import { HiTrash } from 'react-icons/hi'; import { StyledOperationTable } from './styled'; import Menus from '../../components/Menus'; @@ -15,27 +15,28 @@ import SortBy from '../../components/SortBy'; import OrderBy from '../../components/OrderBy'; // Types -import { TRoom } from '../../globals/types'; +import { Nullable, TRoom } from '../../globals/types'; // Constants import { useFetch } from '../../hooks/useFetch'; import { ROOM_PATH } from '../../constants/path'; -import { STATUS_CODE } from '../../constants/statusCode'; +import { STATUS_CODE } from '../../constants/responseStatus'; import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages'; -import { ROOM_PAGE } from '../../constants/variables'; +import { ORDERBY_OPTIONS, ROOM_PAGE } from '../../constants/variables'; // Styled import Spinner from '../../commons/styles/Spinner'; -// Utils +// Helpers import { sendRequest } from '../../helpers/sendRequest'; +import { formatCurrency } from '../../helpers/utils'; interface IRoomRow { room: TRoom; openFormDialog: () => void; - setRoom: React.Dispatch>; + setRoom: Dispatch>>; reload: boolean; - setReload: React.Dispatch>; + setReload: Dispatch>; } const RoomRow = ({ @@ -45,18 +46,14 @@ const RoomRow = ({ reload, setReload, }: IRoomRow) => { - const handleOnEdit = (room: TRoom) => { + const handleEdit = (room: TRoom) => { setRoom(room); openFormDialog(); }; - const handleOnDelete = async (room: TRoom) => { + const handleDelete = async (room: TRoom) => { if (confirm(CONFIRM_DELETE)) { - const response = await sendRequest( - ROOM_PATH + `/${room.id}`, - null, - 'DELETE', - ); + const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE'); if (response.statusCode === STATUS_CODE.OK) { toast.success(DELETE_SUCCESS); @@ -67,9 +64,8 @@ const RoomRow = ({ } }; - const { id, name, price, status } = room; + const { id, name, finalPrice, status } = room; - // prettier-ignore const statusText = status ? 'Unavailable' : 'Available'; @@ -78,7 +74,7 @@ const RoomRow = ({
{id}
{name}
-
{price}
+
{formatCurrency(finalPrice)}
{statusText}
@@ -86,12 +82,12 @@ const RoomRow = ({ } - onClick={() => handleOnEdit(room)} + icon={} + onClick={() => handleEdit(room)} > Edit - } onClick={() => handleOnDelete(room)}> + } onClick={() => handleDelete(room)}> Delete @@ -102,9 +98,9 @@ const RoomRow = ({ interface IRoomTable { reload: boolean; - setReload: React.Dispatch>; + setReload: Dispatch>; openFormDialog: () => void; - setRoom?: React.Dispatch>; + setRoom?: Dispatch>>; } const RoomTable = ({ @@ -129,7 +125,7 @@ const RoomTable = ({ nameSearch, sortByValue, orderByValue, - reload, + reload ); useEffect(() => { @@ -148,7 +144,7 @@ const RoomTable = ({ <> - + { - const dialogRef = useRef(); + const dialogRef = useRef(null); const [reload, setReload] = useState(true); - const [room, setRoom] = useState(null); + const [room, setRoom] = useState>(null); const [isAdd, setIsAdd] = useState(false); const openFormDialog = (isAddForm: boolean = false) => { @@ -48,7 +48,7 @@ const Room = () => { ref={dialogRef} setReload={setReload} reload={reload} - data={room} + room={room} isAdd={isAdd} /> diff --git a/hotel-management/src/pages/User/Dialog.tsx b/hotel-management/src/pages/User/Dialog.tsx index c995b12..4b9a76e 100644 --- a/hotel-management/src/pages/User/Dialog.tsx +++ b/hotel-management/src/pages/User/Dialog.tsx @@ -1,24 +1,39 @@ -import { forwardRef, useEffect } from 'react'; +import { + Dispatch, + MutableRefObject, + SetStateAction, + forwardRef, + useEffect, +} from 'react'; // Components -import Dialog, { IDialogProps } from '../../components/Dialog'; +import Dialog from '../../components/Dialog'; import UserForm from './Form'; // Types -import { TUser } from '../../globals/types'; +import { Nullable, TUser } from '../../globals/types'; -const UserDialog = forwardRef((props, ref) => { - const dialogRef = ref as React.MutableRefObject< - HTMLDialogElement | undefined - >; +// Hooks +import { useForwardRef } from '../../hooks/useForwardRef'; + +interface IUserDialog { + onClose: () => void; + reload: boolean; + setReload: Dispatch>; + ref: MutableRefObject>; + user: Nullable; + isAdd: boolean; +} + +const UserDialog = forwardRef((props, ref) => { + const dialogRef = useForwardRef(ref); - // prettier-ignore const { onClose, setReload, reload, - data, - isAdd + user, + isAdd, } = props; useEffect(() => { @@ -43,11 +58,11 @@ const UserDialog = forwardRef((props, ref) => { onClose={onClose!} reload={reload!} setReload={setReload!} - user={data} + user={user} isAdd={isAdd!} /> ); -}) as React.FC>; +}); export default UserDialog; diff --git a/hotel-management/src/pages/User/Form.tsx b/hotel-management/src/pages/User/Form.tsx index 40a6d44..37a6d4e 100644 --- a/hotel-management/src/pages/User/Form.tsx +++ b/hotel-management/src/pages/User/Form.tsx @@ -1,48 +1,61 @@ -import { useEffect, useState } from 'react'; +import { + Dispatch, + SetStateAction, + useCallback, + useEffect, + useState, +} from 'react'; import toast from 'react-hot-toast'; + +// Hooks import { FormProvider, useForm } from 'react-hook-form'; // Styled import Input from '../../commons/styles/Input'; -import TextArea from '../../commons/styles/TextArea'; // Components import Form from '../../components/Form'; import FormRow from '../../components/LabelControl/index.tsx'; +import Select, { ISelectOptions } from '../../components/Select'; -// Types -import { TKeyValue, TUser } from '../../globals/types'; - -// Utils +// Helpers import { sendRequest } from '../../helpers/sendRequest.ts'; import { + isEmptyObj, + isValidName, isValidNumber, isValidPhoneNumber, - isValidString, } from '../../helpers/validators.ts'; // Constants -import { STATUS_CODE } from '../../constants/statusCode.ts'; +import { STATUS_CODE } from '../../constants/responseStatus.ts'; import { ADD_SUCCESS, EDIT_SUCCESS, errorMsg, } from '../../constants/messages.ts'; import { USER_PATH } from '../../constants/path.ts'; -import Select, { ISelectOptions } from '../../components/Select'; - -// Hooks -import { useFetch } from '../../hooks/useFetch.ts'; +import { + INVALID_FIELD, + INVALID_PHONE, + REQUIRED_FIELD_ERROR, +} from '../../constants/formValidateMessage.ts'; +import { INIT_VALUE_USER_FORM } from '../../constants/variables.ts'; // Styled import { FormBtn } from './styled.ts'; -import { INVALID_FIELD, INVALID_PHONE, REQUIRED_FIELD_ERROR } from '../../constants/formValidateMessage.ts'; + +// Services +import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts'; + +// Types +import { Nullable, TRoom, TUser } from '../../globals/types.ts'; interface IUserFormProp { onClose: () => void; reload: boolean; - setReload: React.Dispatch>; - user?: TUser | null; + setReload: Dispatch>; + user: Nullable; isAdd: boolean; } @@ -53,11 +66,7 @@ const UserForm = ({ user, isAdd, }: IUserFormProp) => { - const formMethods = useForm({ - defaultValues: { - roomId: 1, - }, - }); + const formMethods = useForm(); const { register, handleSubmit, @@ -65,78 +74,110 @@ const UserForm = ({ formState: { errors, isDirty, isValid }, trigger, } = formMethods; + const [rooms, setRooms] = useState([]); const [options, setOptions] = useState(); - const { data, errorFetchMsg } = useFetch('rooms'); + // Init value when edit form and load options useEffect(() => { - if (user) { - reset(user); - } - }, [reset, user]); + const load = async () => { + const options: ISelectOptions[] = []; + const tempUser = isAdd ? {} : { ...user }; - useEffect(() => { - if (data) { - const tempData = data as TKeyValue[]; - const tempOptions: ISelectOptions[] = []; - tempData.forEach((item) => { - tempOptions.push({ - label: item.name! as string, - value: item.id! as string, + // Load and set default options room + if (rooms.length > 0) { + rooms.forEach((item) => { + if (!item.status || tempUser?.roomId === item.id) + options.push({ + label: item.name!, + value: item.id!.toString(), + }); }); - }); + } - setOptions(tempOptions); - } + if (options.length > 0) { + setOptions(options); + reset({ roomId: +options[0].value }); + } - if (errorFetchMsg) { - toast.error(errorFetchMsg); - } - }, [data, errorFetchMsg]); + if (!isEmptyObj(tempUser)) { + // Init value + // Set default value when user not have room yet. + if (!tempUser.roomId) { + tempUser.roomId = +options[0].value; + } + + reset(tempUser); + } else { + reset(INIT_VALUE_USER_FORM); + } + }; + + load(); + }, [reset, user, isAdd, rooms]); // Submit form - const onSubmit = async (user: TUser) => { - try { - if (isAdd) { - // Add request - const response = await sendRequest( - USER_PATH, - JSON.stringify(user), - 'POST' - ); + const onSubmit = useCallback( + async (newUser: TUser) => { + try { + if (isAdd) { + // Add request + const response = await sendRequest( + USER_PATH, + 'POST', + JSON.stringify(newUser) + ); - if (response.statusCode === STATUS_CODE.CREATE) { - toast.success(ADD_SUCCESS); + if (response.statusCode === STATUS_CODE.CREATE) { + toast.success(ADD_SUCCESS); + } else { + throw new Error(errorMsg(response.statusCode, response.msg)); + } + + // Update room status + updateRoomStatus(newUser.roomId, true); } else { - throw new Error(errorMsg(response.statusCode, response.msg)); + // Edit request + const response = await sendRequest( + USER_PATH + `/${newUser.id}`, + 'PUT', + JSON.stringify(newUser) + ); + + if (response.statusCode == STATUS_CODE.OK) { + toast.success(EDIT_SUCCESS); + } else { + throw new Error(errorMsg(response.statusCode, response.msg)); + } + + // Update room status + updateRoomStatus(user!.roomId, true, newUser.roomId); } - - // TODO Update status when user create - - } else { - // Edit request - const response = await sendRequest( - USER_PATH + `/${user!.id}`, - JSON.stringify(user), - 'PUT' - ); - - if (response.statusCode == STATUS_CODE.OK) { - toast.success(EDIT_SUCCESS); - } else { - throw new Error(errorMsg(response.statusCode, response.msg)); + // Reload table data + setReload(!reload); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); } } - // Reload table data - setReload(!reload); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - } - } - reset(); - onClose(); - }; + reset(); + onClose(); + }, + [isAdd, onClose, reload, reset, setReload, user] + ); + + // Load all rooms + useEffect(() => { + const loadRoom = async () => { + const rooms = await getAllRoom(); + + if (rooms) { + setRooms(rooms); + } + }; + + loadRoom(); + }, [onSubmit]); return ( @@ -149,8 +190,7 @@ const UserForm = ({ {...register('name', { required: REQUIRED_FIELD_ERROR, validate: { - checkValidName: (value) => - isValidString(value) || INVALID_FIELD, + checkValidName: (value) => isValidName(value) || INVALID_FIELD, }, onChange: () => trigger('name'), })} @@ -168,7 +208,7 @@ const UserForm = ({ required: REQUIRED_FIELD_ERROR, validate: { checkIdentifiedCode: (v) => - isValidNumber(v) || INVALID_FIELD, + isValidNumber(v.toString()) || INVALID_FIELD, }, onChange: () => trigger('identifiedCode'), })} @@ -182,8 +222,7 @@ const UserForm = ({ {...register('phone', { required: REQUIRED_FIELD_ERROR, validate: { - checkPhoneNum: (v) => - isValidPhoneNumber(v) || INVALID_PHONE, + checkPhoneNum: (v) => isValidPhoneNumber(v) || INVALID_PHONE, }, onChange: () => trigger('phone'), })} @@ -191,32 +230,24 @@ const UserForm = ({ -