diff --git a/hotel-management/src/App.tsx b/hotel-management/src/App.tsx index eee378e..d0c2887 100644 --- a/hotel-management/src/App.tsx +++ b/hotel-management/src/App.tsx @@ -1,6 +1,7 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import isPropValid from '@emotion/is-prop-valid'; import { StyleSheetManager } from 'styled-components'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; // Components import AppLayout from './components/AppLayout'; @@ -13,9 +14,17 @@ import NotFound from './pages/NotFound'; import * as PATH from './constants/path'; import Toast from './components/Toast'; +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + } + } +}); + function App() { return ( - <> + @@ -31,7 +40,7 @@ function App() { - + ); } diff --git a/hotel-management/src/pages/Room/RomTable.tsx b/hotel-management/src/pages/Room/RomTable.tsx index f6dd170..daaa726 100644 --- a/hotel-management/src/pages/Room/RomTable.tsx +++ b/hotel-management/src/pages/Room/RomTable.tsx @@ -1,5 +1,4 @@ -import { useSearchParams } from 'react-router-dom'; -import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { useCallback } from 'react'; // Components import Menus from '@component/Menus'; @@ -14,7 +13,6 @@ import RoomRow from './RoomRow'; import { IRoom } from '@type/rooms'; // Constants -import { useFetch } from '@hook/useFetch'; import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons'; // Styled @@ -22,45 +20,18 @@ import { StyledOperationTable } from './styled'; import Direction from '@commonStyle/Direction'; import Spinner from '@commonStyle/Spinner'; +// Hooks +import { useRooms } from '@hook/rooms/useRooms'; -interface IRoomTable { - reload: boolean; - setReload: Dispatch>; -} - -const RoomTable = ({ reload, setReload }: IRoomTable) => { +const RoomTable = () => { const columnName = ['Id', 'Name', 'Price', 'Status']; - const [nameSearch, setNameSearch] = useState(''); - const [searchParams] = useSearchParams(); - const [rooms, setRooms] = useState([]); - const sortByValue = searchParams.get('sortBy') - ? searchParams.get('sortBy')! - : ''; - const orderByValue = searchParams.get('orderBy') - ? searchParams.get('orderBy')! - : ''; + const { isLoading, rooms } = useRooms(); - const { data, isPending, errorFetchMsg } = useFetch( - 'rooms', - 'name', - nameSearch, - sortByValue, - orderByValue, - reload + const renderRoomRow = useCallback( + (room: IRoom) => , + [] ); - useEffect(() => { - if (data) { - setRooms(data); - } else { - setRooms([]); - } - - if (errorFetchMsg) { - console.error(errorFetchMsg); - } - }, [data, errorFetchMsg]); - return ( <> @@ -68,33 +39,20 @@ const RoomTable = ({ reload, setReload }: IRoomTable) => { - + - {isPending && } + {isLoading && } - {rooms.length ? ( + {rooms && rooms.length ? ( - - - data={rooms} - render={(room: IRoom) => ( - - )} - /> + + data={rooms} render={renderRoomRow} />
) : ( - !isPending && No data to show here! + !isLoading && No data to show here! )}
diff --git a/hotel-management/src/pages/Room/RoomForm.tsx b/hotel-management/src/pages/Room/RoomForm.tsx index 3f90f55..971ae28 100644 --- a/hotel-management/src/pages/Room/RoomForm.tsx +++ b/hotel-management/src/pages/Room/RoomForm.tsx @@ -1,40 +1,30 @@ -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 '@commonStyle/Button.ts'; import Input from '@commonStyle/Input.ts'; // Types -import { Nullable } from '@type/common.ts'; import { IRoom } from '@type/rooms.ts'; // Constants -import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts'; import { - INVALID_DISCOUNT, INVALID_FIELD, REQUIRED_FIELD_ERROR, } from '@constant/formValidateMessage.ts'; -import { INIT_VALUE_ROOM_FORM, REGEX } from '../../constants/commons.ts'; +import { REGEX } from '../../constants/commons.ts'; // Helpers -import { - isValidDiscount, - isValidRegex, - isValidString, -} from '@helper/validators.ts'; +import { isValidRegex, isValidString } from '@helper/validators.ts'; // Components import FormRow from '@component/LabelControl/index.tsx'; import Form from '@component/Form/index.tsx'; -// Services -import { addRoom, updateRoom } from '@service/roomServices.ts'; +// Hooks +import { useForm } from 'react-hook-form'; +import { useCreateRoom } from '@hook/rooms/useCreateRoom.ts'; +import { useUpdateRoom } from '@hook/rooms/useUpdateRoom.ts'; const FormBtn = styled(Button)` width: 100%; @@ -48,65 +38,51 @@ const FormBtn = styled(Button)` interface IRoomFormProp { onCloseModal?: () => void; - reload?: boolean; - setReload?: Dispatch>; - roomEdit?: Nullable; + room?: IRoom; } -const RoomForm = ({ - onCloseModal, - reload, - setReload, - roomEdit, -}: IRoomFormProp) => { - const formMethods = useForm(); +const RoomForm = ({ onCloseModal, room }: IRoomFormProp) => { + const { isCreating, createRoom } = useCreateRoom(); + const { isUpdating, updateRoom } = useUpdateRoom(); + const isLoading = isCreating || isUpdating; + const { id: editId, ...editValues } = { ...room }; + const formMethods = useForm({ + defaultValues: editId + ? editValues + : {}, + }); const { register, handleSubmit, reset, - formState: { errors, isDirty, isValid, isSubmitting }, + formState: { errors, isDirty, isValid }, trigger, } = formMethods; - useEffect(() => { - if (roomEdit) { - reset(roomEdit); - } else { - reset(INIT_VALUE_ROOM_FORM); - } - }, [roomEdit, reset]); - // Submit form - const onSubmit = async (room: IRoom) => { - // Calculate final price - room.finalPrice = room.price - (room.price * room.discount) / 100; - - if (!roomEdit) { + const onSubmit = async (newRoom: IRoom) => { + if (!editId) { // Add request - const response = await addRoom(room); - - if (response) { - toast.success(ADD_SUCCESS); - } + createRoom(newRoom, { + onSuccess: () => { + reset(); + onCloseModal?.(); + }, + }); } else { // Edit request - const response = await updateRoom(room); - - if (response) { - toast.success(EDIT_SUCCESS); - } + newRoom.id = editId!; + updateRoom(newRoom, { + onSuccess: () => { + reset(); + onCloseModal?.(); + }, + }); } - - // Reload table data - setReload!(!reload); - - reset(); - onCloseModal!(); }; return (
- isValidString(value) || INVALID_FIELD, + checkValidRoomName: (value) => + isValidString(value) || INVALID_FIELD, }, onChange: () => trigger('name'), })} @@ -130,41 +107,27 @@ const RoomForm = ({ required: REQUIRED_FIELD_ERROR, validate: { checkPrice: (v) => - isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || INVALID_FIELD, + isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || + INVALID_FIELD, }, onChange: () => trigger('price'), })} /> - - isValidDiscount(v) || INVALID_DISCOUNT, - }, - onChange: () => trigger('discount'), - })} - /> - - { - !roomEdit + !editId ? 'Add' : 'Save' } - + Close diff --git a/hotel-management/src/pages/Room/RoomRow.tsx b/hotel-management/src/pages/Room/RoomRow.tsx index f6662a4..ae7aaf4 100644 --- a/hotel-management/src/pages/Room/RoomRow.tsx +++ b/hotel-management/src/pages/Room/RoomRow.tsx @@ -1,13 +1,6 @@ -import { Dispatch, SetStateAction } from 'react'; -import toast from 'react-hot-toast'; - -// Constants -import { ROOM_PATH } from '@constant/path'; -import { STATUS_CODE } from '@constant/responseStatus'; -import { CONFIRM_DELETE, DELETE_SUCCESS } from '@constant/messages'; +import { useCallback, useMemo } from 'react'; // Helpers -import { sendRequest } from '@helper/sendRequest'; import { formatCurrency } from '@helper/helper'; // Types @@ -20,38 +13,47 @@ import Table from '@component/Table'; import Menus from '@component/Menus'; import { RiEditBoxFill } from 'react-icons/ri'; import { HiTrash } from 'react-icons/hi'; +import ConfirmMessage from '@component/ConfirmMessage'; + +// Hooks +import { useDeleteRoom } from '@hook/rooms/useDeleteRoom'; + +// Constants +import { FORM } from '@constant/commons'; interface IRoomRow { room: IRoom; - reload: boolean; - setReload: Dispatch>; } -const RoomRow = ({ room, reload, setReload }: IRoomRow) => { - const handleDelete = async (room: IRoom) => { - if (confirm(CONFIRM_DELETE)) { - const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE'); - - if (response.statusCode === STATUS_CODE.OK) { - toast.success(DELETE_SUCCESS); - - // Reload table - setReload(!reload); - } - } - }; - - const { id, name, finalPrice, status } = room; - +const RoomRow = ({ room }: IRoomRow) => { + const { id, name, price, status } = room; + const { isDeleting, deleteRoom } = useDeleteRoom(); const statusText = status ? 'Unavailable' : 'Available'; + const formattedPrice = useMemo(() => formatCurrency(price), [price]); + const renderEditBtn = useCallback( + (onCloseModal: () => void) => ( + }> + Edit + + ), + [] + ); + const renderDeleteBtn = useCallback( + (onCloseModal: () => void) => ( + } onClick={onCloseModal}> + Delete + + ), + [] + ); return (
{id}
{name}
-
{formatCurrency(finalPrice)}
+
{formattedPrice}
{statusText}
@@ -61,24 +63,25 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => { ( - }> - Edit - - )} + modalName={FORM.EDIT} + renderChildren={renderEditBtn} + /> + - - } - onClick={() => handleDelete(room)} - > - Delete - - - + + + + + + deleteRoom(id)} + /> diff --git a/hotel-management/src/pages/Room/index.tsx b/hotel-management/src/pages/Room/index.tsx index aa7c3ab..3704ca8 100644 --- a/hotel-management/src/pages/Room/index.tsx +++ b/hotel-management/src/pages/Room/index.tsx @@ -1,20 +1,14 @@ -import { useState } from 'react'; - // Components import RoomTable from './RomTable'; import RoomForm from './RoomForm'; +import Modal from '@component/Modal'; // Styled import { StyledRoom, Title } from './styled'; import Direction from '@commonStyle/Direction.ts'; import Button from '@commonStyle/Button'; -// Types -import Modal from '@component/Modal'; - const Room = () => { - const [reload, setReload] = useState(true); - return ( <> @@ -29,12 +23,12 @@ const Room = () => { )} /> - + - + ); diff --git a/hotel-management/src/pages/User/UserForm.tsx b/hotel-management/src/pages/User/UserForm.tsx index 80608be..0f56e50 100644 --- a/hotel-management/src/pages/User/UserForm.tsx +++ b/hotel-management/src/pages/User/UserForm.tsx @@ -1,14 +1,7 @@ -import { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useState, -} from 'react'; -import toast from 'react-hot-toast'; - // Hooks import { FormProvider, useForm } from 'react-hook-form'; +import { useCreateUser } from '@hook/users/useCreateUser.ts'; +import { useUpdateUser } from '@hook/users/useUpdateUser.ts'; // Styled import Input from '@commonStyle/Input.ts'; @@ -16,139 +9,73 @@ import Input from '@commonStyle/Input.ts'; // Components import Form from '@component/Form/index.tsx'; import FormRow from '@component/LabelControl/index.tsx'; -import Select, { ISelectOptions } from '@component/Select/index.tsx'; // Helpers -import { isEmptyObj, isValidRegex } from '@helper/validators.ts'; +import { isValidRegex } from '@helper/validators.ts'; -// Constants -import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts'; import { INVALID_FIELD, INVALID_PHONE, REQUIRED_FIELD_ERROR, } from '@constant/formValidateMessage.ts'; -import { INIT_VALUE_USER_FORM, REGEX } from '@constant/commons.ts'; +import { REGEX } from '@constant/commons.ts'; // Styled import { FormBtn } from './styled.ts'; -// Services -import { getAllRoom, updateRoomStatus } from '@service/roomServices.ts'; -import { createUser, updateUser } from '@service/userServices.ts'; - // Types -import { Nullable } from '@type/common.ts'; import { IUser } from '@type/users.ts'; -import { IRoom } from '@type/rooms.ts'; interface IUserFormProp { onCloseModal?: () => void; - reload: boolean; - setReload: Dispatch>; - user?: Nullable; + user?: IUser; } -const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { - const formMethods = useForm(); +const UserForm = ({ onCloseModal, user }: IUserFormProp) => { + const { isCreating, createUser } = useCreateUser(); + const { isUpdating, updateUser } = useUpdateUser(); + const isLoading = isCreating || isUpdating; + const {id: editId, ...editValues} = {...user}; + + const formMethods = useForm({ + defaultValues: editId + ? editValues + : {} + }); const { register, handleSubmit, reset, - formState: { errors, isDirty, isValid, isSubmitting }, + formState: { errors, isDirty, isValid }, trigger, } = formMethods; - const [rooms, setRooms] = useState([]); - const [options, setOptions] = useState(); - - // Init value when edit form and load options - useEffect(() => { - const load = async () => { - const options: ISelectOptions[] = []; - const tempUser = !user ? { roomId: 0 } : { ...user }; - - // Load and set default options room - if (rooms.length > 0) { - // Init first options - options.push({ - label: '---Select---', - value: '0', - }); - - rooms.forEach((item) => { - if (!item.status || tempUser?.roomId === item.id) - options.push({ - label: item.name!, - value: item.id!.toString(), - }); - }); - } - - if (options.length > 0) { - setOptions(options); - } - - if (!isEmptyObj(tempUser)) { - // Init value - reset(tempUser); - } else { - reset(INIT_VALUE_USER_FORM); - } - }; - - load(); - }, [reset, user, rooms]); // Submit form - const onSubmit = useCallback( - async (newUser: IUser) => { + const onSubmit = + (newUser: IUser) => { if (!user) { // Add request - const response = await createUser(newUser); - - if (response) { - toast.success(ADD_SUCCESS); - } - - // Update room status - updateRoomStatus(newUser.roomId, true); + createUser(newUser, { + onSuccess: () => { + reset(); + onCloseModal?.(); + }, + }); } else { // Edit request - const response = await updateUser(newUser); - - if (response) { - toast.success(EDIT_SUCCESS); - } - - // Update room status - updateRoomStatus(user!.roomId, true, newUser.roomId); - } - // Reload table data - setReload(!reload); - - reset(); - onCloseModal!(); - }, - [onCloseModal, reload, reset, setReload, user] - ); - - // Load all rooms - useEffect(() => { - const loadRoom = async () => { - const rooms = await getAllRoom(); - - if (rooms) { - setRooms(rooms); + newUser.id = editId!; + updateUser(newUser, { + onSuccess: () => { + reset(); + onCloseModal?.(); + } + }); } }; - loadRoom(); - }, [onSubmit]); - return ( - { /> - - - isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || - INVALID_FIELD, - }, - onChange: () => trigger('identifiedCode'), - })} - /> - - { /> - - {options && options.length > 1 ? ( -