From 4f24d9c6e21257d3701ee7a09d5a03de11dd1bd8 Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Sun, 29 Oct 2023 13:19:01 +0700 Subject: [PATCH] Implement add user function --- hotel-management/src/constants/config.ts | 3 + hotel-management/src/constants/messages.ts | 12 ++- hotel-management/src/constants/statusCode.ts | 7 ++ hotel-management/src/globals/interfaces.ts | 2 + hotel-management/src/globals/types.ts | 14 +++- hotel-management/src/helpers/sendRequest.ts | 29 +++++++ hotel-management/src/helpers/utils.ts | 4 +- hotel-management/src/hooks/useFetch.ts | 7 +- hotel-management/src/pages/User/Dialog.tsx | 4 +- hotel-management/src/pages/User/Form.tsx | 82 ++++++++++++++------ hotel-management/src/pages/User/Table.tsx | 13 +++- hotel-management/src/pages/User/index.tsx | 12 ++- 12 files changed, 149 insertions(+), 40 deletions(-) create mode 100644 hotel-management/src/constants/config.ts create mode 100644 hotel-management/src/constants/statusCode.ts create mode 100644 hotel-management/src/helpers/sendRequest.ts diff --git a/hotel-management/src/constants/config.ts b/hotel-management/src/constants/config.ts new file mode 100644 index 0000000..9ad978c --- /dev/null +++ b/hotel-management/src/constants/config.ts @@ -0,0 +1,3 @@ +const TIME_OUT_SEC = 1; + +export { TIME_OUT_SEC }; diff --git a/hotel-management/src/constants/messages.ts b/hotel-management/src/constants/messages.ts index 67eb3df..cb5ad71 100644 --- a/hotel-management/src/constants/messages.ts +++ b/hotel-management/src/constants/messages.ts @@ -1,5 +1,13 @@ -const invalidFormatMessage = (field: string) => { +const INVALID_FORMAT_MSG = (field: string) => { return `Invalid ${field} format`; }; -export { invalidFormatMessage }; +const ADD_SUCCESS = 'Add success'; + +const ERROR_MSG = (errorCode: number, msg: string) => { + return `Error code: ${errorCode}. Message: ${msg}`; +}; + +const TIME_OUT_MSG = 'Connection time out!'; + +export { INVALID_FORMAT_MSG, ADD_SUCCESS, ERROR_MSG, TIME_OUT_MSG }; diff --git a/hotel-management/src/constants/statusCode.ts b/hotel-management/src/constants/statusCode.ts new file mode 100644 index 0000000..3504d05 --- /dev/null +++ b/hotel-management/src/constants/statusCode.ts @@ -0,0 +1,7 @@ +const STATUS_CODE = { + CREATE: 201, + NOT_FOUND: 404, + CONNECTION_TIME_OUT: 522, +}; + +export { STATUS_CODE }; diff --git a/hotel-management/src/globals/interfaces.ts b/hotel-management/src/globals/interfaces.ts index e604962..21baafc 100644 --- a/hotel-management/src/globals/interfaces.ts +++ b/hotel-management/src/globals/interfaces.ts @@ -24,6 +24,8 @@ interface IDialogProps { title?: string; children?: JSX.Element[] | JSX.Element; onClose?: () => void; + reload?: boolean; + setReload?: React.Dispatch>; ref?: React.MutableRefObject; } diff --git a/hotel-management/src/globals/types.ts b/hotel-management/src/globals/types.ts index 1288023..8514620 100644 --- a/hotel-management/src/globals/types.ts +++ b/hotel-management/src/globals/types.ts @@ -30,4 +30,16 @@ type TValidator = { }; }; -export type { TUser, TStateSchema, TKeyValue, TPropValues, TValidator }; +type TResponse = { + statusCode: number; + msg: string; +}; + +export type { + TUser, + TStateSchema, + TKeyValue, + TPropValues, + TValidator, + TResponse, +}; diff --git a/hotel-management/src/helpers/sendRequest.ts b/hotel-management/src/helpers/sendRequest.ts new file mode 100644 index 0000000..f626704 --- /dev/null +++ b/hotel-management/src/helpers/sendRequest.ts @@ -0,0 +1,29 @@ +// Constants +import { BASE_URL } from '../constants/path'; +import { TResponse } from '../globals/types'; +type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE'; + +export const sendRequest = async ( + path: string, + body: BodyInit | null | undefined, + method: TMethodRequest = 'GET', +): Promise => { + const controller = new AbortController(); + const signal = controller.signal; + + const response = await fetch(BASE_URL + path, { + method, + body, + headers: { + // prettier-ignore + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + signal, + }); + + return { + statusCode: response.status, + msg: response.statusText, + }; +}; diff --git a/hotel-management/src/helpers/utils.ts b/hotel-management/src/helpers/utils.ts index b5a3c19..c804404 100644 --- a/hotel-management/src/helpers/utils.ts +++ b/hotel-management/src/helpers/utils.ts @@ -1,4 +1,4 @@ -import { invalidFormatMessage } from '../constants/messages'; +import { INVALID_FORMAT_MSG } from '../constants/messages'; import { TKeyValue, TPropValues, TStateSchema } from '../globals/types'; const VALUE = 'value'; @@ -39,7 +39,7 @@ const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => { required, validator: { func: validatorFunc, - error: invalidFormatMessage(prop), + error: INVALID_FORMAT_MSG(prop), }, }; }; diff --git a/hotel-management/src/hooks/useFetch.ts b/hotel-management/src/hooks/useFetch.ts index cb21b4f..aacea1f 100644 --- a/hotel-management/src/hooks/useFetch.ts +++ b/hotel-management/src/hooks/useFetch.ts @@ -3,12 +3,15 @@ import { useEffect, useState } from 'react'; // Constants import { BASE_URL } from '../constants/path'; -export const useFetch = (path: string) => { +export const useFetch = (path: string, reload?: boolean) => { const [data, setData] = useState(null); const [isPending, setIsPending] = useState(false); const [errorMsg, setErrorMsg] = useState(null); useEffect(() => { + // Clear data + setData(null); + const fetchData = async () => { setIsPending(true); @@ -31,6 +34,6 @@ export const useFetch = (path: string) => { }; fetchData(); - }, [path]); + }, [path, reload]); return { data, isPending, errorMsg }; }; diff --git a/hotel-management/src/pages/User/Dialog.tsx b/hotel-management/src/pages/User/Dialog.tsx index defe696..414b1e5 100644 --- a/hotel-management/src/pages/User/Dialog.tsx +++ b/hotel-management/src/pages/User/Dialog.tsx @@ -11,7 +11,7 @@ const UserDialog = forwardRef((props, ref) => { const dialogRef = ref as React.MutableRefObject< HTMLDialogElement | undefined >; - const { onClose } = props; + const { onClose, setReload, reload } = props; useEffect(() => { if (dialogRef.current) { @@ -31,7 +31,7 @@ const UserDialog = forwardRef((props, ref) => { return ( - + ); }) as React.FC; diff --git a/hotel-management/src/pages/User/Form.tsx b/hotel-management/src/pages/User/Form.tsx index 01bf120..1a90b22 100644 --- a/hotel-management/src/pages/User/Form.tsx +++ b/hotel-management/src/pages/User/Form.tsx @@ -1,8 +1,9 @@ +import { useState } from 'react'; import styled from 'styled-components'; +import toast from 'react-hot-toast'; // Styled import Input from '../../commons/styles/Input'; -import { useState } from 'react'; import TextArea from '../../commons/styles/TextArea'; // Components @@ -11,7 +12,12 @@ import FormRow from '../../components/FormRow'; import Button from '../../commons/styles/Button.ts'; // Types -import { TKeyValue, TStateSchema, TValidator } from '../../globals/types'; +import { + TKeyValue, + TResponse, + TStateSchema, + TValidator, +} from '../../globals/types'; // Hooks import useForm from '../../hooks/useForm'; @@ -24,6 +30,11 @@ import { isValidString, } from '../../helpers/validators'; import { addValidator } from '../../helpers/utils.ts'; +import { sendRequest } from '../../helpers/sendRequest.ts'; + +// Constants +import { STATUS_CODE } from '../../constants/statusCode.ts'; +import { ADD_SUCCESS, ERROR_MSG } from '../../constants/messages.ts'; const FormBtn = styled(Button)` width: 100%; @@ -36,23 +47,25 @@ const FormBtn = styled(Button)` interface IUserFormProp { onClose: () => void; + reload: boolean; + onReload: React.Dispatch>; } -const UserForm = ({ onClose }: IUserFormProp) => { +const UserForm = ({ onClose, reload, onReload }: IUserFormProp) => { const [reset, setReset] = useState(true); // Define your state schema const stateSchema: TStateSchema = { - fullName: { value: '', error: '' }, + name: { value: '', error: '' }, identifiedCode: { value: '', error: '' }, phone: { value: '', error: '' }, - room: { value: '', error: '' }, + roomId: { value: '', error: '' }, address: { value: '', error: '' }, }; // prettier-ignore const stateValidatorSchema: TValidator = { - fullName: addValidator({ + name: addValidator({ validatorFunc: isValidString, prop: 'full name' }), @@ -64,7 +77,7 @@ const UserForm = ({ onClose }: IUserFormProp) => { validatorFunc: isValidPhoneNumber, prop: 'phone number', }), - room: addValidator({ + roomId: addValidator({ validatorFunc: isValidNumber, prop: 'room number' }), @@ -75,19 +88,38 @@ const UserForm = ({ onClose }: IUserFormProp) => { }; // Submit form - const onSubmitForm = (state: TKeyValue) => { - alert(JSON.stringify(state, null, 2)); + const onSubmitForm = async (state: TKeyValue) => { + try { + const response = await sendRequest( + 'users', + JSON.stringify(state), + 'POST', + ); + + if (response.statusCode === STATUS_CODE.CREATE) { + toast.success(ADD_SUCCESS); + onReload(!reload); + } else { + toast.error(ERROR_MSG(response.statusCode, response.msg)); + } + + onResetForm(); + } catch (error) { + toast.error( + ERROR_MSG((error as TResponse).statusCode, (error as TResponse).msg), + ); + } + onClose(); - onResetForm(); }; // prettier-ignore const { - values, - errors, - dirty, - handleOnChange, - handleOnSubmit, + values, + errors, + dirty, + handleOnChange, + handleOnSubmit, disable } = useForm( stateSchema, @@ -97,10 +129,10 @@ const UserForm = ({ onClose }: IUserFormProp) => { // prettier-ignore const { - fullName, + name, identifiedCode, phone, - room, + roomId, address } = values; @@ -117,15 +149,15 @@ const UserForm = ({ onClose }: IUserFormProp) => { label="Full Name" error={ // prettier-ignore - errors.fullName && dirty.fullName ? - (errors.fullName as string) + errors.name && dirty.name ? + (errors.name as string) : '' } > @@ -167,15 +199,15 @@ const UserForm = ({ onClose }: IUserFormProp) => { label="Room" error={ // prettier-ignore - errors.room && dirty.room ? - (errors.room as string) + errors.roomId && dirty.roomId ? + (errors.roomId as string) : '' } > diff --git a/hotel-management/src/pages/User/Table.tsx b/hotel-management/src/pages/User/Table.tsx index 1d1ddb5..fdcedd9 100644 --- a/hotel-management/src/pages/User/Table.tsx +++ b/hotel-management/src/pages/User/Table.tsx @@ -1,3 +1,5 @@ +import { useEffect, useState } from 'react'; + // Components import { HiSquare2Stack } from 'react-icons/hi2'; import { HiTrash } from 'react-icons/hi'; @@ -15,7 +17,6 @@ import { useFetch } from '../../hooks/useFetch'; // Styled import Spinner from '../../commons/styles/Spinner'; -import { useEffect, useState } from 'react'; type TUserModal = { user: TUser }; @@ -53,13 +54,19 @@ const UserRow = ({ user }: TUserModal) => { ); }; -const UserTable = () => { - const { data, isPending, errorMsg } = useFetch('users'); +interface IUserTable { + reload: boolean; +} + +const UserTable = ({ reload }: IUserTable) => { + const { data, isPending, errorMsg } = useFetch('users', reload); const [users, setUsers] = useState([]); useEffect(() => { if (data) { setUsers(data); + } else { + setUsers([]); } if (errorMsg) { diff --git a/hotel-management/src/pages/User/index.tsx b/hotel-management/src/pages/User/index.tsx index 8fa2d9a..ce784df 100644 --- a/hotel-management/src/pages/User/index.tsx +++ b/hotel-management/src/pages/User/index.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useRef, useState } from 'react'; // Components import Direction from '../../commons/styles/Direction'; @@ -11,6 +11,7 @@ import UserDialog from './Dialog'; const User = () => { const dialogRef = useRef(); + const [reload, setReload] = useState(true); const openDialog = () => { dialogRef.current?.showModal(); @@ -28,10 +29,15 @@ const User = () => { - + - + ); };