Fix all comments

This commit is contained in:
2023-11-05 18:28:51 +07:00
parent b0dd3abf0e
commit 3f95ce8343
39 changed files with 483 additions and 443 deletions
@@ -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;
`; `;
+18 -12
View File
@@ -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>`
+10 -13
View File
@@ -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,
}; };
+2 -2
View File
@@ -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>({});
+4 -32
View File
@@ -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,
}; };
+4 -5
View File
@@ -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,
}; };
}; };
+10 -74
View File
@@ -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,
};
+19 -4
View File
@@ -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,
}; };
+17
View File
@@ -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 };
+7 -7
View File
@@ -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}`
); );
} }
+4 -1
View File
@@ -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(() => {
+53 -44
View File
@@ -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;
+95 -71
View File
@@ -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>
); );
}; };
+12 -16
View File
@@ -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 = ({
+5 -5
View File
@@ -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) => {
+15 -8
View File
@@ -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;
+43 -32
View File
@@ -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
+21 -18
View File
@@ -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 = ({
+4 -4
View File
@@ -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) => {
+56 -45
View File
@@ -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) {