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/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/Select/index.tsx b/hotel-management/src/components/Select/index.tsx index fc047d0..421aa9a 100644 --- a/hotel-management/src/components/Select/index.tsx +++ b/hotel-management/src/components/Select/index.tsx @@ -54,7 +54,6 @@ const RenderSelect = ({ id={id} value={value} {...register(id!, optionsConfigForm)} - onChange={onChange} > {children} 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/path.ts b/hotel-management/src/constants/path.ts index 03014d3..7775173 100644 --- a/hotel-management/src/constants/path.ts +++ b/hotel-management/src/constants/path.ts @@ -2,6 +2,6 @@ export const DASHBOARD = '/dashboard'; export const USER = '/user'; export const ROOM = '/room'; export const OTHER_PATH = '*'; -export const BASE_URL = 'https://hotel-management-api.loiphan.com/'; +export const BASE_URL = 'http://localhost:3000/'; export const USER_PATH = 'users'; export const ROOM_PATH = 'rooms'; 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..fb5b26a 100644 --- a/hotel-management/src/constants/variables.ts +++ b/hotel-management/src/constants/variables.ts @@ -34,18 +34,19 @@ const USER_PAGE = { ], }; +const ORDERBY_OPTIONS = [ + { + value: 'asc', + label: 'Ascending', + }, + { + value: 'desc', + label: 'Descending', + }, +]; + // prettier-ignore const ROOM_PAGE = { - ORDERBY_OPTIONS: [ - { - value: 'asc', - label: 'Ascending', - }, - { - value: 'desc', - label: 'Descending', - }, - ], SORTBY_OPTIONS: [ { value: 'id', @@ -82,4 +83,20 @@ const INITIAL_STATE_SCHEMA = { 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: '', + address: '', + phone: '', +}; + +export { + USER_PAGE, + ROOM_PAGE, + VALUE, + ERROR, + INITIAL_STATE_SCHEMA, + ORDERBY_OPTIONS, + INIT_VALUE_USER_FORM +}; diff --git a/hotel-management/src/globals/types.ts b/hotel-management/src/globals/types.ts index 46d566c..c36615c 100644 --- a/hotel-management/src/globals/types.ts +++ b/hotel-management/src/globals/types.ts @@ -42,9 +42,10 @@ type TValidator = { }; }; -type TResponse = { +type TResponse = { statusCode: number; msg: string; + data?: T; }; export type { diff --git a/hotel-management/src/helpers/sendRequest.ts b/hotel-management/src/helpers/sendRequest.ts index 4696504..7f3cd06 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,11 +13,11 @@ 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, @@ -28,8 +28,11 @@ export const sendRequest = async ( }, }); + 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..26e721a 100644 --- a/hotel-management/src/helpers/utils.ts +++ b/hotel-management/src/helpers/utils.ts @@ -116,6 +116,8 @@ const searchQuery = ( return query; }; + + export { isRequired, getPropValues, diff --git a/hotel-management/src/helpers/validators.ts b/hotel-management/src/helpers/validators.ts index 2a17471..cd57c15 100644 --- a/hotel-management/src/helpers/validators.ts +++ b/hotel-management/src/helpers/validators.ts @@ -40,10 +40,15 @@ const isValidString = (value: string): boolean => { */ const skipCheck = () => true; +const isEmptyObj = (obj: object) => { + return Object.keys(obj).length === 0; +} + export { isValidNumber, isValidPhoneNumber, isValidString, skipCheck, isValidDiscount, + isEmptyObj }; diff --git a/hotel-management/src/pages/Room/Form.tsx b/hotel-management/src/pages/Room/Form.tsx index b010635..d69e8ab 100644 --- a/hotel-management/src/pages/Room/Form.tsx +++ b/hotel-management/src/pages/Room/Form.tsx @@ -5,7 +5,7 @@ import Button from '../../commons/styles/Button.ts'; import { TRoom } from '../../globals/types.ts'; import { useForm } from 'react-hook-form'; import { ROOM_PATH } from '../../constants/path.ts'; -import { STATUS_CODE } from '../../constants/statusCode.ts'; +import { STATUS_CODE } from '../../constants/responseStatus.ts'; import toast from 'react-hot-toast'; import { ADD_SUCCESS, EDIT_SUCCESS, errorMsg } from '../../constants/messages.ts'; import { sendRequest } from '../../helpers/sendRequest.ts'; @@ -63,8 +63,8 @@ const RoomForm = ({ // Add request const response = await sendRequest( ROOM_PATH, + 'POST', JSON.stringify(room), - 'POST' ); if (response.statusCode === STATUS_CODE.CREATE) { @@ -76,8 +76,8 @@ const RoomForm = ({ // Edit request const response = await sendRequest( ROOM_PATH + `/${room!.id}`, + 'PUT', JSON.stringify(room), - 'PUT' ); if (response.statusCode == STATUS_CODE.OK) { diff --git a/hotel-management/src/pages/Room/Table.tsx b/hotel-management/src/pages/Room/Table.tsx index 81cd4e6..28de2e7 100644 --- a/hotel-management/src/pages/Room/Table.tsx +++ b/hotel-management/src/pages/Room/Table.tsx @@ -20,9 +20,9 @@ import { 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'; @@ -54,8 +54,7 @@ const RoomRow = ({ if (confirm(CONFIRM_DELETE)) { const response = await sendRequest( ROOM_PATH + `/${room.id}`, - null, - 'DELETE', + 'DELETE' ); if (response.statusCode === STATUS_CODE.OK) { @@ -148,7 +147,7 @@ const RoomTable = ({ <> - + void; @@ -53,11 +57,7 @@ const UserForm = ({ user, isAdd, }: IUserFormProp) => { - const formMethods = useForm({ - defaultValues: { - roomId: 1, - }, - }); + const formMethods = useForm(); const { register, handleSubmit, @@ -66,42 +66,55 @@ const UserForm = ({ trigger, } = formMethods; 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 rooms = await getAllRoom(); + 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) { + rooms.forEach((item) => { + if (!item.status || tempUser?.roomId === item.id) + options.push({ + label: item.name! as string, + value: item.id!.toString(), + }); }); - }); - setOptions(tempOptions); - } + 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]); // Submit form - const onSubmit = async (user: TUser) => { + const onSubmit = async (newUser: TUser) => { try { if (isAdd) { // Add request const response = await sendRequest( USER_PATH, - JSON.stringify(user), - 'POST' + 'POST', + JSON.stringify(newUser) ); if (response.statusCode === STATUS_CODE.CREATE) { @@ -110,14 +123,14 @@ const UserForm = ({ throw new Error(errorMsg(response.statusCode, response.msg)); } - // TODO Update status when user create - + // Update room status + updateRoomStatus(newUser.roomId, true); } else { // Edit request const response = await sendRequest( - USER_PATH + `/${user!.id}`, - JSON.stringify(user), - 'PUT' + USER_PATH + `/${newUser.id}`, + 'PUT', + JSON.stringify(newUser) ); if (response.statusCode == STATUS_CODE.OK) { @@ -125,6 +138,9 @@ const UserForm = ({ } else { throw new Error(errorMsg(response.statusCode, response.msg)); } + + // Update room status + updateRoomStatus(user!.roomId, true, newUser.roomId); } // Reload table data setReload(!reload); @@ -167,8 +183,7 @@ const UserForm = ({ {...register('identifiedCode', { required: REQUIRED_FIELD_ERROR, validate: { - checkIdentifiedCode: (v) => - isValidNumber(v) || INVALID_FIELD, + checkIdentifiedCode: (v) => isValidNumber(v) || INVALID_FIELD, }, onChange: () => trigger('identifiedCode'), })} @@ -182,8 +197,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,7 +205,15 @@ const UserForm = ({ - trigger('roomId'), + }} + /> @@ -201,8 +223,7 @@ const UserForm = ({ {...register('address', { required: REQUIRED_FIELD_ERROR, validate: { - checkValidString: (v) => - isValidString(v) || INVALID_FIELD, + checkValidString: (v) => isValidString(v) || INVALID_FIELD, }, onChange: () => trigger('address'), })} diff --git a/hotel-management/src/pages/User/Table.tsx b/hotel-management/src/pages/User/Table.tsx index f6e2001..3d63624 100644 --- a/hotel-management/src/pages/User/Table.tsx +++ b/hotel-management/src/pages/User/Table.tsx @@ -3,8 +3,8 @@ import { useEffect, useState } from 'react'; import toast from 'react-hot-toast'; // Components -import { HiSquare2Stack } from 'react-icons/hi2'; -import { HiTrash } from 'react-icons/hi'; +import { RiEditBoxFill } from 'react-icons/ri'; +import { IoExit } from 'react-icons/io5'; import { StyledOperationTable } from './styled'; import Menus from '../../components/Menus'; import Table from '../../components/Table'; @@ -19,16 +19,16 @@ import { TUser } from '../../globals/types'; // Constants import { useFetch } from '../../hooks/useFetch'; -import { USER_PATH } from '../../constants/path'; -import { STATUS_CODE } from '../../constants/statusCode'; -import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages'; -import { USER_PAGE } from '../../constants/variables'; +import { STATUS_CODE } from '../../constants/responseStatus'; +import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables'; // Styled import Spinner from '../../commons/styles/Spinner'; // Utils -import { sendRequest } from '../../helpers/sendRequest'; +import { updateRoomStatus } from '../../services/roomServices'; +import { checkOutUser } from '../../services/userServices'; +import { CONFIRM_MESSAGE } from '../../constants/messages'; interface IUserRow { user: TUser; @@ -50,16 +50,16 @@ const UserRow = ({ openFormDialog(); }; - const handleOnDelete = async (user: TUser) => { - if (confirm(CONFIRM_DELETE)) { - const response = await sendRequest( - USER_PATH + `/${user.id}`, - null, - 'DELETE', - ); + const handleOnCheckOut = async (user: TUser) => { + if (confirm(CONFIRM_MESSAGE)) { + const resUpdateStatus = await updateRoomStatus(user.roomId, false); + const resCheckoutUser = await checkOutUser(user); - if (response.statusCode === STATUS_CODE.OK) { - toast.success(DELETE_SUCCESS); + if ( + resCheckoutUser?.statusCode === STATUS_CODE.OK && + resUpdateStatus?.statusCode === STATUS_CODE.OK + ) { + toast.success('Check out complete!'); // Reload table setReload(!reload); @@ -75,20 +75,27 @@ const UserRow = ({
{name}
{identifiedCode}
{phone}
-
{roomId}
+
{ + roomId + ? roomId + : 'None' + }
} + icon={} onClick={() => handleOnEdit(user)} > Edit - } onClick={() => handleOnDelete(user)}> - Delete + } + onClick={() => handleOnCheckOut(user)} + > + Check out @@ -125,7 +132,7 @@ const UserTable = ({ phoneSearch, sortByValue, orderByValue, - reload, + reload ); useEffect(() => { @@ -140,11 +147,12 @@ const UserTable = ({ } }, [data, errorFetchMsg]); + return ( <> - + => { + try { + const response = await sendRequest(ROOM_PATH); + + if (response.statusCode === STATUS_CODE.OK) { + const rooms = response.data!; + + return rooms; + } else { + throw new Error(errorMsg(response.statusCode, response.msg)); + } + } catch (error) { + if (error instanceof Error) { + toast.error(error.message); + } + } + + return null; +}; + +const getRoom = async (roomId: number): Promise => { + try { + const response = await sendRequest(ROOM_PATH + '/' + roomId); + + if (response.statusCode === STATUS_CODE.OK) { + const rooms = response.data!; + + return rooms; + } else { + throw new Error(errorMsg(response.statusCode, response.msg)); + } + } catch (error) { + if (error instanceof Error) { + toast.error(error.message); + } + } + + return null; +}; + +const updateRoom = async (room: TRoom): Promise | null> => { + try { + const response = await sendRequest( + ROOM_PATH + '/' + room.id, + 'PUT', + JSON.stringify(room) + ); + + if (response.statusCode !== STATUS_CODE.OK) { + throw new Error(errorMsg(response.statusCode, response.msg)); + } + + return response; + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + + return null; +}; + +const updateRoomStatus = async ( + roomId: number, + status: boolean, + roomIdNew?: number +): Promise | null> => { + if (!roomIdNew) { + const response = await sendRequest( + ROOM_PATH + '/' + roomId, + 'PATCH', + JSON.stringify({ status: status }) + ); + + return response; + } else { + // Update new room status + const resNewRoom = await sendRequest( + ROOM_PATH + '/' + roomIdNew, + 'PATCH', + JSON.stringify({ status: status }) + ); + + // Update old room status; + const resOldRoom = await sendRequest( + ROOM_PATH + '/' + roomId, + 'PATCH', + 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, + }; + } + + return { + statusCode: STATUS_CODE.INTERNAL_SERVER_ERROR, + msg: 'Something went wrong!', + }; + } + + // if (rooms?.length) { + // const oldRoom = rooms.find((room) => room.id === roomId); + // const newRoom = rooms.find((room) => room.id === roomIdNew); + + // if(newRoom) { + + // } + + // oldRoom!.status = status; + // const response = await updateRoom(oldRoom!); + + // return response; + // } + + return null; +}; + +export { getRoom, updateRoom, updateRoomStatus, getAllRoom }; diff --git a/hotel-management/src/services/userServices.ts b/hotel-management/src/services/userServices.ts new file mode 100644 index 0000000..c127909 --- /dev/null +++ b/hotel-management/src/services/userServices.ts @@ -0,0 +1,43 @@ +import toast from 'react-hot-toast'; +import { errorMsg } from '../constants/messages'; +import { USER_PATH } from '../constants/path'; +import { STATUS_CODE } from '../constants/responseStatus'; +import { TResponse, TUser } from '../globals/types'; +import { sendRequest } from '../helpers/sendRequest'; + +const updateUser = async (user: TUser): Promise | null> => { + try { + const response = await sendRequest( + USER_PATH + '/' + user.id, + 'PUT', + JSON.stringify(user) + ); + + if (response.statusCode !== STATUS_CODE.OK) { + throw new Error(errorMsg(response.statusCode, response.msg)); + } + + return response; + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + + return null; +}; + +const checkOutUser = async (user: TUser): Promise | null> => { + const tempUser = user; + + if (tempUser) { + tempUser.roomId = 0; + const resUpdateUser = await updateUser(tempUser); + + return resUpdateUser; + } + + return null; +}; + +export { updateUser, checkOutUser };