From 64c44e6e7c3778f8744ddaf5f27862578478b082 Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Tue, 7 Nov 2023 17:46:42 +0700 Subject: [PATCH 1/4] Add modal component and replace room dialog form --- .../src/components/Menus/index.tsx | 2 +- .../src/components/Modal/index.tsx | 78 +++++++++++++++++++ .../src/components/Modal/styled.ts | 49 ++++++++++++ hotel-management/src/constants/path.ts | 2 +- hotel-management/src/contexts/ModalContext.ts | 11 +++ hotel-management/src/hooks/useOutsideClick.ts | 6 +- hotel-management/src/pages/Room/Form.tsx | 16 ++-- hotel-management/src/pages/Room/Table.tsx | 70 +++++++---------- hotel-management/src/pages/Room/index.tsx | 40 +++------- .../src/styles/abstracts/variables.css | 4 + 10 files changed, 195 insertions(+), 83 deletions(-) create mode 100644 hotel-management/src/components/Modal/index.tsx create mode 100644 hotel-management/src/components/Modal/styled.ts create mode 100644 hotel-management/src/contexts/ModalContext.ts diff --git a/hotel-management/src/components/Menus/index.tsx b/hotel-management/src/components/Menus/index.tsx index 629c97d..df9b4d3 100644 --- a/hotel-management/src/components/Menus/index.tsx +++ b/hotel-management/src/components/Menus/index.tsx @@ -58,7 +58,7 @@ const List = ({ children: ReactNode; }): Nullable => { const { openId, close } = useContext(MenusContext); - const ref = useOutsideClick(close!, false); + const ref = useOutsideClick(close!, false); if (openId !== id) return null; diff --git a/hotel-management/src/components/Modal/index.tsx b/hotel-management/src/components/Modal/index.tsx new file mode 100644 index 0000000..8d8ab64 --- /dev/null +++ b/hotel-management/src/components/Modal/index.tsx @@ -0,0 +1,78 @@ +import { + ReactElement, + ReactNode, + cloneElement, + useContext, + useState, +} from 'react'; +import { createPortal } from 'react-dom'; + +// Hooks +import { useOutsideClick } from '../../hooks/useOutsideClick'; + +// Contexts +import { ModalContext } from '../../contexts/ModalContext'; + +// Styled +import { Overlay, StyledModal, StyledModalContent, TitleModal } from './styled'; + +interface IModal { + children: ReactNode; +} + +const Modal = ({ children }: IModal) => { + const [openName, setOpenName] = useState(''); + + const close = () => setOpenName(''); + const open = setOpenName; + + return ( + + {children} + + ); +}; + +interface IOpen { + children: ReactElement; + modalName: string; +} + +const Open = ({ children, modalName }: IOpen) => { + const { open } = useContext(ModalContext); + + return cloneElement(children, { onClick: () => open!(modalName) }); +}; + +interface IWindow { + children: ReactElement; + name: string; + title: string; +} + +const Window = ({ children, name, title }: IWindow) => { + const { openName, close } = useContext(ModalContext); + const ref = useOutsideClick(close!); + + console.log('name: ', name); + console.log('openName: ', openName); + + if (name !== openName) return null; + + return createPortal( + + + + {title} + {cloneElement(children, { onCloseModal: close })} + + + , + document.body + ); +}; + +Modal.Open = Open; +Modal.Window = Window; + +export default Modal; diff --git a/hotel-management/src/components/Modal/styled.ts b/hotel-management/src/components/Modal/styled.ts new file mode 100644 index 0000000..1b1cc0d --- /dev/null +++ b/hotel-management/src/components/Modal/styled.ts @@ -0,0 +1,49 @@ +import styled from 'styled-components'; + +const StyledModal = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + background-color: var(--color-grey-0); + + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-lg); + + padding: 3.2rem 4rem; + + transition: all 0.5s; +`; + +const StyledModalContent = styled.div` + background-color: var(--form-color); + + border-radius: var(--radius-md); + + padding: 30px 30px; +`; + +const Overlay = styled.div` + position: fixed; + top: 0; + left: 0; + + width: 100%; + height: 100vh; + + background-color: var(--overlay-color); + + z-index: 1000; + transition: all 0.5s; +`; + +const TitleModal = styled.p` + font-size: var(--fs-md); + font-weight: 600; + text-align: center; + + margin-bottom: 30px; +` + +export { StyledModal, Overlay, StyledModalContent, TitleModal }; 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/contexts/ModalContext.ts b/hotel-management/src/contexts/ModalContext.ts new file mode 100644 index 0000000..d7a529a --- /dev/null +++ b/hotel-management/src/contexts/ModalContext.ts @@ -0,0 +1,11 @@ +import { createContext } from 'react'; + +interface IModalContext { + openName?: string; + close?: () => void; + open?: React.Dispatch>; +} + +const ModalContext = createContext({}); + +export { ModalContext }; diff --git a/hotel-management/src/hooks/useOutsideClick.ts b/hotel-management/src/hooks/useOutsideClick.ts index 76858ee..3c5d651 100644 --- a/hotel-management/src/hooks/useOutsideClick.ts +++ b/hotel-management/src/hooks/useOutsideClick.ts @@ -9,11 +9,11 @@ import { Nullable } from '../types/common'; * @param listeningCapturing A boolean value indicating that events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. If not specified, defaults to false. * @returns Return a mutable ref object */ -export const useOutsideClick = ( +export const useOutsideClick = ( handler: () => void, listeningCapturing = true -): MutableRefObject> => { - const ref = useRef(null); +): MutableRefObject> => { + const ref = useRef(null); useEffect(() => { const handleClick = (e: Event) => { diff --git a/hotel-management/src/pages/Room/Form.tsx b/hotel-management/src/pages/Room/Form.tsx index 76042fb..ef55f57 100644 --- a/hotel-management/src/pages/Room/Form.tsx +++ b/hotel-management/src/pages/Room/Form.tsx @@ -48,15 +48,15 @@ const FormBtn = styled(Button)` `; interface IRoomFormProp { - onClose: () => void; - reload: boolean; - setReload: Dispatch>; + onCloseModal?: () => void; + reload?: boolean; + setReload?: Dispatch>; room?: Nullable; - isAdd: boolean; + isAdd?: boolean; } const RoomForm = ({ - onClose, + onCloseModal, reload, setReload, room, @@ -101,7 +101,7 @@ const RoomForm = ({ } } // Reload table data - setReload(!reload); + // setReload(!reload); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); @@ -109,7 +109,7 @@ const RoomForm = ({ } reset(); - onClose(); + // onClose(); }; return ( @@ -168,7 +168,7 @@ const RoomForm = ({ : 'Save' } - + Close diff --git a/hotel-management/src/pages/Room/Table.tsx b/hotel-management/src/pages/Room/Table.tsx index 32bb775..168ae91 100644 --- a/hotel-management/src/pages/Room/Table.tsx +++ b/hotel-management/src/pages/Room/Table.tsx @@ -15,7 +15,6 @@ import SortBy from '../../components/SortBy'; import OrderBy from '../../components/OrderBy'; // Types -import { Nullable } from '../../types/common'; import { IRoom } from '../../types/rooms'; // Constants @@ -31,27 +30,16 @@ import Spinner from '../../commons/styles/Spinner'; // Helpers import { sendRequest } from '../../helpers/sendRequest'; import { formatCurrency } from '../../helpers/helper'; +import Modal from '../../components/Modal'; +import RoomForm from './Form'; interface IRoomRow { room: IRoom; - openFormDialog: () => void; - setRoom: Dispatch>>; reload: boolean; setReload: Dispatch>; } -const RoomRow = ({ - room, - openFormDialog, - setRoom, - reload, - setReload, -}: IRoomRow) => { - const handleEdit = (room: IRoom) => { - setRoom(room); - openFormDialog(); - }; - +const RoomRow = ({ room, reload, setReload }: IRoomRow) => { const handleDelete = async (room: IRoom) => { if (confirm(CONFIRM_DELETE)) { const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE'); @@ -67,9 +55,7 @@ const RoomRow = ({ const { id, name, finalPrice, status } = room; - const statusText = status - ? 'Unavailable' - : 'Available'; + const statusText = status ? 'Unavailable' : 'Available'; return ( @@ -78,21 +64,30 @@ const RoomRow = ({
{formatCurrency(finalPrice)}
{statusText}
- - +
+ + + - - } - onClick={() => handleEdit(room)} - > - Edit - - } onClick={() => handleDelete(room)}> - Delete - - - + + + }>Edit + + + } + onClick={() => handleDelete(room)} + > + Delete + + + + + + + + +
); }; @@ -100,16 +95,9 @@ const RoomRow = ({ interface IRoomTable { reload: boolean; setReload: Dispatch>; - openFormDialog: () => void; - setRoom?: Dispatch>>; } -const RoomTable = ({ - reload, - setReload, - openFormDialog, - setRoom, -}: IRoomTable) => { +const RoomTable = ({ reload, setReload }: IRoomTable) => { const [nameSearch, setNameSearch] = useState(''); const [searchParams] = useSearchParams(); const [rooms, setRooms] = useState([]); @@ -173,8 +161,6 @@ const RoomTable = ({ key={room.id} reload={reload} setReload={setReload} - openFormDialog={openFormDialog} - setRoom={setRoom!} /> )} /> diff --git a/hotel-management/src/pages/Room/index.tsx b/hotel-management/src/pages/Room/index.tsx index c416ebe..667e5c1 100644 --- a/hotel-management/src/pages/Room/index.tsx +++ b/hotel-management/src/pages/Room/index.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useState } from 'react'; // Components import RoomTable from './Table'; @@ -7,51 +7,35 @@ import RoomTable from './Table'; import { StyledRoom, Title } from './styled'; import Direction from '../../commons/styles/Direction'; import Button from '../../commons/styles/Button'; -import RoomDialog from './Dialog'; // Types -import { Nullable } from '../../types/common'; -import { IRoom } from '../../types/rooms'; +import Modal from '../../components/Modal'; +import RoomForm from './Form'; const Room = () => { - const dialogRef = useRef(null); const [reload, setReload] = useState(true); - const [room, setRoom] = useState>(null); - const [isAdd, setIsAdd] = useState(false); - - const openFormDialog = (isAddForm: boolean = false) => { - setIsAdd(isAddForm); - dialogRef.current?.showModal(); - }; - - const closeFormDialog = () => { - dialogRef.current?.close(); - }; return ( <> List Room - + + + + + + + + + openFormDialog()} - setRoom={setRoom} /> - - ); }; diff --git a/hotel-management/src/styles/abstracts/variables.css b/hotel-management/src/styles/abstracts/variables.css index c497130..648a503 100644 --- a/hotel-management/src/styles/abstracts/variables.css +++ b/hotel-management/src/styles/abstracts/variables.css @@ -17,6 +17,10 @@ --radius-sm: 5px; --radius-md: 10px; + --overlay-color: #0000004d; + + --form-color: #fff; + --shadow-sm: 0px 10px 20px rgba(0, 0, 0, 0.2); --fs-md: 30px; From a99d81a411512719d422706f2c6ed2e100e10e5d Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Tue, 7 Nov 2023 21:51:35 +0700 Subject: [PATCH 2/4] Apply modal instead of dialog in user pages --- .../src/components/Dialog/index.tsx | 29 ------ .../src/components/Dialog/styled.ts | 23 ----- hotel-management/src/constants/path.ts | 2 +- hotel-management/src/pages/Room/Dialog.tsx | 70 -------------- .../pages/Room/{Table.tsx => RomTable.tsx} | 72 +------------- .../src/pages/Room/{Form.tsx => RoomForm.tsx} | 32 +++---- hotel-management/src/pages/Room/RoomRow.tsx | 83 +++++++++++++++++ hotel-management/src/pages/Room/index.tsx | 8 +- hotel-management/src/pages/User/Dialog.tsx | 69 -------------- .../src/pages/User/{Form.tsx => FormUser.tsx} | 39 ++++---- .../pages/User/{Table.tsx => TableUser.tsx} | 93 +------------------ hotel-management/src/pages/User/UserRow.tsx | 88 ++++++++++++++++++ hotel-management/src/pages/User/index.tsx | 47 +++------- 13 files changed, 224 insertions(+), 431 deletions(-) delete mode 100644 hotel-management/src/components/Dialog/index.tsx delete mode 100644 hotel-management/src/components/Dialog/styled.ts delete mode 100644 hotel-management/src/pages/Room/Dialog.tsx rename hotel-management/src/pages/Room/{Table.tsx => RomTable.tsx} (58%) rename hotel-management/src/pages/Room/{Form.tsx => RoomForm.tsx} (89%) create mode 100644 hotel-management/src/pages/Room/RoomRow.tsx delete mode 100644 hotel-management/src/pages/User/Dialog.tsx rename hotel-management/src/pages/User/{Form.tsx => FormUser.tsx} (90%) rename hotel-management/src/pages/User/{Table.tsx => TableUser.tsx} (53%) create mode 100644 hotel-management/src/pages/User/UserRow.tsx diff --git a/hotel-management/src/components/Dialog/index.tsx b/hotel-management/src/components/Dialog/index.tsx deleted file mode 100644 index a58f982..0000000 --- a/hotel-management/src/components/Dialog/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MutableRefObject, ReactNode, forwardRef } from 'react'; - -// Styled -import { StyledBody, StyledDialog, StyledTitle } from './styled'; - -// Type -import { Nullable } from '../../types/common'; - -export interface IDialogProps { - title?: string; - children?: ReactNode; - onClose?: () => void; - ref?: MutableRefObject>; -} - -const Dialog = forwardRef( - (props: IDialogProps, ref) => { - const { title, children, onClose } = props; - - return ( - - {title} - {children} - - ); - } -); - -export default Dialog; diff --git a/hotel-management/src/components/Dialog/styled.ts b/hotel-management/src/components/Dialog/styled.ts deleted file mode 100644 index 700551b..0000000 --- a/hotel-management/src/components/Dialog/styled.ts +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; - -const StyledDialog = styled.dialog` - border-radius: var(--radius-sm); - border-color: var(--border-color); -`; - -const StyledTitle = styled.p` - font-size: var(--fs-md); - text-transform: capitalize; - font-weight: 600; - - padding: 20px 40px; - border-bottom: 1px solid var(--border-color); - - text-align: center; -`; - -const StyledBody = styled.div` - padding: 10px 20px; -`; - -export { StyledDialog, StyledTitle, StyledBody }; diff --git a/hotel-management/src/constants/path.ts b/hotel-management/src/constants/path.ts index 7775173..03014d3 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 = 'http://localhost:3000/'; +export const BASE_URL = 'https://hotel-management-api.loiphan.com/'; export const USER_PATH = 'users'; export const ROOM_PATH = 'rooms'; diff --git a/hotel-management/src/pages/Room/Dialog.tsx b/hotel-management/src/pages/Room/Dialog.tsx deleted file mode 100644 index 4f9adf9..0000000 --- a/hotel-management/src/pages/Room/Dialog.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { - Dispatch, - MutableRefObject, - SetStateAction, - forwardRef, - useEffect, -} from 'react'; - -// Components -import Dialog from '../../components/Dialog'; -import RoomForm from './Form'; - -// Types -import { Nullable } from '../../types/common'; -import { IRoom } from '../../types/rooms'; - -// Hooks -import { useForwardRef } from '../../hooks/useForwardRef'; - -interface IRoomDialog { - onClose?: () => void; - reload?: boolean; - setReload?: Dispatch>; - ref?: MutableRefObject>; - room?: Nullable; - isAdd?: boolean; -} - -const RoomDialog = forwardRef( - (props: IRoomDialog, ref) => { - const dialogRef = useForwardRef(ref); - const { - onClose, - setReload, - reload, - room, - isAdd - } = props; - - useEffect(() => { - if (dialogRef.current) { - dialogRef.current.addEventListener('click', (e: MouseEvent) => { - const dialogDimensions = dialogRef.current!.getBoundingClientRect(); - if ( - e.clientX < dialogDimensions.left || - e.clientX > dialogDimensions.right || - e.clientY < dialogDimensions.top || - e.clientY > dialogDimensions.bottom - ) { - dialogRef.current!.close(); - } - }); - } - }, [dialogRef]); - - return ( - - - - ); - } -); - -export default RoomDialog; diff --git a/hotel-management/src/pages/Room/Table.tsx b/hotel-management/src/pages/Room/RomTable.tsx similarity index 58% rename from hotel-management/src/pages/Room/Table.tsx rename to hotel-management/src/pages/Room/RomTable.tsx index 168ae91..9b57b7f 100644 --- a/hotel-management/src/pages/Room/Table.tsx +++ b/hotel-management/src/pages/Room/RomTable.tsx @@ -1,10 +1,7 @@ import { useSearchParams } from 'react-router-dom'; import { Dispatch, SetStateAction, useEffect, useState } from 'react'; -import toast from 'react-hot-toast'; // Components -import { RiEditBoxFill } from 'react-icons/ri'; -import { HiTrash } from 'react-icons/hi'; import { StyledOperationTable } from './styled'; import Menus from '../../components/Menus'; import Table from '../../components/Table'; @@ -19,78 +16,11 @@ import { IRoom } from '../../types/rooms'; // Constants import { useFetch } from '../../hooks/useFetch'; -import { ROOM_PATH } from '../../constants/path'; -import { STATUS_CODE } from '../../constants/responseStatus'; -import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages'; import { ORDERBY_OPTIONS, ROOM_PAGE } from '../../constants/variables'; // Styled import Spinner from '../../commons/styles/Spinner'; - -// Helpers -import { sendRequest } from '../../helpers/sendRequest'; -import { formatCurrency } from '../../helpers/helper'; -import Modal from '../../components/Modal'; -import RoomForm from './Form'; - -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 statusText = status ? 'Unavailable' : 'Available'; - - return ( - -
{id}
-
{name}
-
{formatCurrency(finalPrice)}
-
{statusText}
- -
- - - - - - - }>Edit - - - } - onClick={() => handleDelete(room)} - > - Delete - - - - - - - - -
-
- ); -}; +import RoomRow from './RoomRow'; interface IRoomTable { reload: boolean; diff --git a/hotel-management/src/pages/Room/Form.tsx b/hotel-management/src/pages/Room/RoomForm.tsx similarity index 89% rename from hotel-management/src/pages/Room/Form.tsx rename to hotel-management/src/pages/Room/RoomForm.tsx index ef55f57..2c824dd 100644 --- a/hotel-management/src/pages/Room/Form.tsx +++ b/hotel-management/src/pages/Room/RoomForm.tsx @@ -10,8 +10,8 @@ import Button from '../../commons/styles/Button.ts'; import Input from '../../commons/styles/Input.ts'; // Types -import { Nullable } from '../../types/common'; -import { IRoom } from '../../types/rooms'; +import { Nullable } from '../../types/common.ts'; +import { IRoom } from '../../types/rooms.ts'; // Constants import { @@ -51,33 +51,31 @@ interface IRoomFormProp { onCloseModal?: () => void; reload?: boolean; setReload?: Dispatch>; - room?: Nullable; - isAdd?: boolean; + roomEdit?: Nullable; } const RoomForm = ({ onCloseModal, reload, setReload, - room, - isAdd, + roomEdit, }: IRoomFormProp) => { const formMethods = useForm(); const { register, handleSubmit, reset, - formState: { errors, isDirty, isValid }, + formState: { errors, isDirty, isValid, isSubmitting }, trigger, } = formMethods; useEffect(() => { - if (room && !isAdd) { - reset(room); + if (roomEdit) { + reset(roomEdit); } else { reset(INIT_VALUE_ROOM_FORM); } - }, [room, reset, isAdd]); + }, [roomEdit, reset]); // Submit form const onSubmit = async (room: IRoom) => { @@ -85,7 +83,7 @@ const RoomForm = ({ room.finalPrice = room.price - (room.price * room.discount) / 100; try { - if (isAdd) { + if (!roomEdit) { // Add request const response = await addRoom(room); @@ -101,7 +99,7 @@ const RoomForm = ({ } } // Reload table data - // setReload(!reload); + setReload!(!reload); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); @@ -109,7 +107,7 @@ const RoomForm = ({ } reset(); - // onClose(); + onCloseModal!(); }; return ( @@ -161,11 +159,11 @@ const RoomForm = ({ - + { - isAdd - ? 'Add' - : 'Save' + !roomEdit + ? 'Add' + : 'Save' } diff --git a/hotel-management/src/pages/Room/RoomRow.tsx b/hotel-management/src/pages/Room/RoomRow.tsx new file mode 100644 index 0000000..6fa72c8 --- /dev/null +++ b/hotel-management/src/pages/Room/RoomRow.tsx @@ -0,0 +1,83 @@ +import { Dispatch, SetStateAction } from 'react'; +import toast from 'react-hot-toast'; + +// Constants +import { ROOM_PATH } from '../../constants/path'; +import { STATUS_CODE } from '../../constants/responseStatus'; +import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages'; + +// Helpers +import { sendRequest } from '../../helpers/sendRequest'; +import { formatCurrency } from '../../helpers/helper'; +import Modal from '../../components/Modal'; +import RoomForm from './RoomForm'; + +// Types +import { IRoom } from '../../types/rooms'; + +// Components +import Table from '../../components/Table'; +import Menus from '../../components/Menus'; +import { RiEditBoxFill } from 'react-icons/ri'; +import { HiTrash } from 'react-icons/hi'; + +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 statusText = status ? 'Unavailable' : 'Available'; + + return ( + +
{id}
+
{name}
+
{formatCurrency(finalPrice)}
+
{statusText}
+ +
+ + + + + + + }>Edit + + + } + onClick={() => handleDelete(room)} + > + Delete + + + + + + + + +
+
+ ); +}; + +export default RoomRow; diff --git a/hotel-management/src/pages/Room/index.tsx b/hotel-management/src/pages/Room/index.tsx index 667e5c1..7a0d801 100644 --- a/hotel-management/src/pages/Room/index.tsx +++ b/hotel-management/src/pages/Room/index.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; // Components -import RoomTable from './Table'; +import RoomTable from './RomTable'; // Styled import { StyledRoom, Title } from './styled'; @@ -10,7 +10,7 @@ import Button from '../../commons/styles/Button'; // Types import Modal from '../../components/Modal'; -import RoomForm from './Form'; +import RoomForm from './RoomForm'; const Room = () => { const [reload, setReload] = useState(true); @@ -22,10 +22,10 @@ const Room = () => { List Room - + - + diff --git a/hotel-management/src/pages/User/Dialog.tsx b/hotel-management/src/pages/User/Dialog.tsx deleted file mode 100644 index 03dfac7..0000000 --- a/hotel-management/src/pages/User/Dialog.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - Dispatch, - MutableRefObject, - SetStateAction, - forwardRef, - useEffect, -} from 'react'; - -// Components -import Dialog from '../../components/Dialog'; -import UserForm from './Form'; - -// Types -import { Nullable } from '../../types/common'; -import { IUser } from '../../types/users'; - -// Hooks -import { useForwardRef } from '../../hooks/useForwardRef'; - -interface IUserDialog { - onClose: () => void; - reload: boolean; - setReload: Dispatch>; - ref: MutableRefObject>; - user: Nullable; - isAdd: boolean; -} - -const UserDialog = forwardRef((props, ref) => { - const dialogRef = useForwardRef(ref); - - const { - onClose, - setReload, - reload, - user, - isAdd, - } = props; - - useEffect(() => { - if (dialogRef.current) { - dialogRef.current.addEventListener('click', (e: MouseEvent) => { - const dialogDimensions = dialogRef.current!.getBoundingClientRect(); - if ( - e.clientX < dialogDimensions.left || - e.clientX > dialogDimensions.right || - e.clientY < dialogDimensions.top || - e.clientY > dialogDimensions.bottom - ) { - dialogRef.current!.close(); - } - }); - } - }, [dialogRef]); - - return ( - - - - ); -}); - -export default UserDialog; diff --git a/hotel-management/src/pages/User/Form.tsx b/hotel-management/src/pages/User/FormUser.tsx similarity index 90% rename from hotel-management/src/pages/User/Form.tsx rename to hotel-management/src/pages/User/FormUser.tsx index d0ee6c0..8879ed0 100644 --- a/hotel-management/src/pages/User/Form.tsx +++ b/hotel-management/src/pages/User/FormUser.tsx @@ -11,12 +11,12 @@ import toast from 'react-hot-toast'; import { FormProvider, useForm } from 'react-hook-form'; // Styled -import Input from '../../commons/styles/Input'; +import Input from '../../commons/styles/Input.ts'; // Components -import Form from '../../components/Form'; +import Form from '../../components/Form/index.tsx'; import FormRow from '../../components/LabelControl/index.tsx'; -import Select, { ISelectOptions } from '../../components/Select'; +import Select, { ISelectOptions } from '../../components/Select/index.tsx'; // Helpers import { @@ -48,26 +48,19 @@ import { IRoom } from '../../types/rooms.ts'; import { createUser, updateUser } from '../../services/userServices.ts'; interface IUserFormProp { - onClose: () => void; + onCloseModal?: () => void; reload: boolean; setReload: Dispatch>; - user: Nullable; - isAdd: boolean; + user?: Nullable; } -const UserForm = ({ - onClose, - reload, - setReload, - user, - isAdd, -}: IUserFormProp) => { +const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { const formMethods = useForm(); const { register, handleSubmit, reset, - formState: { errors, isDirty, isValid }, + formState: { errors, isDirty, isValid, isSubmitting }, trigger, } = formMethods; const [rooms, setRooms] = useState([]); @@ -77,8 +70,8 @@ const UserForm = ({ useEffect(() => { const load = async () => { const options: ISelectOptions[] = []; - const tempUser = isAdd - ? {} + const tempUser = !user + ? { roomId: 0 } : { ...user }; // Load and set default options room @@ -111,13 +104,13 @@ const UserForm = ({ }; load(); - }, [reset, user, isAdd, rooms]); + }, [reset, user, rooms]); // Submit form const onSubmit = useCallback( async (newUser: IUser) => { try { - if (isAdd) { + if (!user) { // Add request const response = await createUser(newUser); @@ -147,9 +140,9 @@ const UserForm = ({ } reset(); - onClose(); + onCloseModal!(); }, - [isAdd, onClose, reload, reset, setReload, user] + [onCloseModal, reload, reset, setReload, user] ); // Load all rooms @@ -233,14 +226,14 @@ const UserForm = ({ - + { - isAdd + !user ? 'Add' : 'Save' } - + Close diff --git a/hotel-management/src/pages/User/Table.tsx b/hotel-management/src/pages/User/TableUser.tsx similarity index 53% rename from hotel-management/src/pages/User/Table.tsx rename to hotel-management/src/pages/User/TableUser.tsx index 825faf2..bdc68cb 100644 --- a/hotel-management/src/pages/User/Table.tsx +++ b/hotel-management/src/pages/User/TableUser.tsx @@ -1,10 +1,7 @@ import { useSearchParams } from 'react-router-dom'; import { Dispatch, SetStateAction, useEffect, useState } from 'react'; -import toast from 'react-hot-toast'; // Components -import { RiEditBoxFill } from 'react-icons/ri'; -import { IoExit } from 'react-icons/io5'; import Menus from '../../components/Menus'; import Table from '../../components/Table'; import Direction from '../../commons/styles/Direction'; @@ -14,113 +11,29 @@ import SortBy from '../../components/SortBy'; import OrderBy from '../../components/OrderBy'; // Types -import { Nullable } from '../../types/common'; import { IUser } from '../../types/users'; // Hooks import { useFetch } from '../../hooks/useFetch'; // Constants -import { STATUS_CODE } from '../../constants/responseStatus'; import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables'; -import { CONFIRM_MESSAGE, DENIED_ACTION } from '../../constants/messages'; // Styled import { StyledOperationTable } from './styled'; import Spinner from '../../commons/styles/Spinner'; +import UserRow from './UserRow'; -// Services -import { updateRoomStatus } from '../../services/roomServices'; -import { checkOutUser } from '../../services/userServices'; -interface IUserRow { - user: IUser; - openFormDialog: () => void; - setUser: Dispatch>>; - reload: boolean; - setReload: Dispatch>; -} -const UserRow = ({ - user, - openFormDialog, - setUser, - reload, - setReload, -}: IUserRow) => { - const handleEdit = (user: IUser) => { - setUser(user); - openFormDialog(); - }; - const handleCheckOut = async (user: IUser) => { - if(user.roomId !== 0) { - if (confirm(CONFIRM_MESSAGE)) { - const resUpdateStatus = await updateRoomStatus(user.roomId, false); - const resCheckoutUser = await checkOutUser(user); - - if ( - resCheckoutUser?.statusCode === STATUS_CODE.OK && - resUpdateStatus?.statusCode === STATUS_CODE.OK - ) { - toast.success('Check out complete!'); - - // Reload table - setReload(!reload); - } - } - } else { - toast.error(DENIED_ACTION); - } - - }; - - const { id, name, identifiedCode, phone, roomId } = user; - - return ( - -
{id}
-
{name}
-
{identifiedCode}
-
{phone}
-
{ - roomId - ? roomId - : 'None' - }
- - - - - - } - onClick={() => handleEdit(user)} - > - Edit - - } onClick={() => handleCheckOut(user)}> - Check out - - - -
- ); -}; interface IUserTable { reload: boolean; setReload: Dispatch>; - openFormDialog: () => void; - setUser?: Dispatch>>; } -const UserTable = ({ - reload, - setReload, - openFormDialog, - setUser, -}: IUserTable) => { +const UserTable = ({ reload, setReload }: IUserTable) => { const [users, setUsers] = useState([]); const [phoneSearch, setPhoneSearch] = useState(''); const [searchParams] = useSearchParams(); @@ -185,8 +98,6 @@ const UserTable = ({ key={user.id} reload={reload} setReload={setReload} - openFormDialog={openFormDialog} - setUser={setUser!} /> )} /> diff --git a/hotel-management/src/pages/User/UserRow.tsx b/hotel-management/src/pages/User/UserRow.tsx new file mode 100644 index 0000000..5218b2b --- /dev/null +++ b/hotel-management/src/pages/User/UserRow.tsx @@ -0,0 +1,88 @@ +import toast from 'react-hot-toast'; +import { Dispatch, SetStateAction } from 'react'; + +// Components +import { RiEditBoxFill } from 'react-icons/ri'; +import { IoExit } from 'react-icons/io5'; +import Modal from '../../components/Modal'; +import UserForm from './FormUser'; +import Table from '../../components/Table'; +import Menus from '../../components/Menus'; + +// Constants +import { STATUS_CODE } from '../../constants/responseStatus'; +import { CONFIRM_MESSAGE, DENIED_ACTION } from '../../constants/messages'; + +// Services +import { updateRoomStatus } from '../../services/roomServices'; +import { checkOutUser } from '../../services/userServices'; + +// Types +import { IUser } from '../../types/users'; + +interface IUserRow { + user: IUser; + reload: boolean; + setReload: Dispatch>; +} + +const UserRow = ({ user, reload, setReload }: IUserRow) => { + const handleCheckOut = async (user: IUser) => { + if (user.roomId !== 0) { + if (confirm(CONFIRM_MESSAGE)) { + const resUpdateStatus = await updateRoomStatus(user.roomId, false); + const resCheckoutUser = await checkOutUser(user); + + if ( + resCheckoutUser?.statusCode === STATUS_CODE.OK && + resUpdateStatus?.statusCode === STATUS_CODE.OK + ) { + toast.success('Check out complete!'); + + // Reload table + setReload(!reload); + } + } + } else { + toast.error(DENIED_ACTION); + } + }; + + const { id, name, identifiedCode, phone, roomId } = user; + + return ( + +
{id}
+
{name}
+
{identifiedCode}
+
{phone}
+
{roomId ? roomId : 'None'}
+ +
+ + + + + + + }>Edit + + } + onClick={() => handleCheckOut(user)} + > + Check out + + + + + + + + +
+
+ ); +}; + +export default UserRow; diff --git a/hotel-management/src/pages/User/index.tsx b/hotel-management/src/pages/User/index.tsx index 50af0a4..9713ed3 100644 --- a/hotel-management/src/pages/User/index.tsx +++ b/hotel-management/src/pages/User/index.tsx @@ -1,57 +1,38 @@ -import { useRef, useState } from 'react'; +import { useState } from 'react'; // Components -import UserTable from './Table'; +import UserTable from './TableUser'; // Styled import Button from '../../commons/styles/Button'; import Direction from '../../commons/styles/Direction'; import { StyledUser, Title } from './styled'; -import UserDialog from './Dialog'; // Types -import { Nullable } from '../../types/common'; -import { IUser } from '../../types/users'; +import Modal from '../../components/Modal'; +import UserForm from './FormUser'; const User = () => { - const dialogRef = useRef(null); const [reload, setReload] = useState(true); - const [user, setUser] = useState>(null); - const [isAdd, setIsAdd] = useState(false); - - const openFormDialog = (isAddForm: boolean = false) => { - setIsAdd(isAddForm); - dialogRef.current?.showModal(); - }; - - const closeFormDialog = () => { - dialogRef.current?.close(); - }; return ( <> List User - + + + + + + + + + - openFormDialog()} - setUser={setUser} - /> + - - ); }; From 8fce206f7f6d256815ffae96beb2d561c7090c9f Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Wed, 8 Nov 2023 17:46:26 +0700 Subject: [PATCH 3/4] Fix comments --- hotel-management/package.json | 3 +- hotel-management/pnpm-lock.yaml | 37 +++++ .../src/commons/styles/ButtonIcon.ts | 22 --- .../src/components/AppLayout/index.tsx | 4 +- .../src/components/ButtonIcon/ButtonIcon.tsx | 131 ++++++++++++++++++ .../src/components/ButtonIcon/styled.ts | 0 .../src/components/HeaderMenu/index.tsx | 18 ++- .../src/components/Menus/index.tsx | 21 ++- .../src/components/Modal/index.tsx | 13 +- hotel-management/src/components/Nav/index.tsx | 2 +- .../src/components/OrderBy/index.tsx | 5 +- .../src/components/Search/index.tsx | 2 +- .../src/components/Select/index.tsx | 56 +------- .../src/components/Sidebar/index.tsx | 2 +- .../src/components/SortBy/index.tsx | 8 +- .../src/components/Table/index.tsx | 12 +- .../constants/{variables.ts => commons.ts} | 19 ++- hotel-management/src/constants/styles.ts | 11 ++ hotel-management/src/helpers/sendRequest.ts | 4 +- hotel-management/src/helpers/validators.ts | 39 +----- hotel-management/src/hooks/useFetch.ts | 8 +- hotel-management/src/hooks/useForwardRef.ts | 2 +- hotel-management/src/hooks/useOutsideClick.ts | 2 +- hotel-management/src/pages/Room/RomTable.tsx | 35 +++-- hotel-management/src/pages/Room/RoomForm.tsx | 70 +++++----- hotel-management/src/pages/Room/RoomRow.tsx | 35 +++-- hotel-management/src/pages/Room/index.tsx | 22 +-- hotel-management/src/pages/User/FormUser.tsx | 81 +++++------ hotel-management/src/pages/User/TableUser.tsx | 45 +++--- hotel-management/src/pages/User/UserRow.tsx | 36 +++-- hotel-management/src/pages/User/index.tsx | 21 ++- hotel-management/src/services/roomServices.ts | 14 +- hotel-management/src/services/userServices.ts | 14 +- hotel-management/tsconfig.json | 16 ++- hotel-management/vite.config.ts | 3 +- 35 files changed, 471 insertions(+), 342 deletions(-) delete mode 100644 hotel-management/src/commons/styles/ButtonIcon.ts create mode 100644 hotel-management/src/components/ButtonIcon/ButtonIcon.tsx create mode 100644 hotel-management/src/components/ButtonIcon/styled.ts rename hotel-management/src/constants/{variables.ts => commons.ts} (82%) create mode 100644 hotel-management/src/constants/styles.ts diff --git a/hotel-management/package.json b/hotel-management/package.json index 09732e8..2c613bd 100644 --- a/hotel-management/package.json +++ b/hotel-management/package.json @@ -49,6 +49,7 @@ "husky": "^8.0.0", "lint-staged": "^15.0.1", "typescript": "^5.0.2", - "vite": "^4.4.5" + "vite": "^4.4.5", + "vite-tsconfig-paths": "^4.2.1" } } diff --git a/hotel-management/pnpm-lock.yaml b/hotel-management/pnpm-lock.yaml index ab6d085..428134c 100644 --- a/hotel-management/pnpm-lock.yaml +++ b/hotel-management/pnpm-lock.yaml @@ -70,6 +70,9 @@ devDependencies: vite: specifier: ^4.4.5 version: 4.4.11 + vite-tsconfig-paths: + specifier: ^4.2.1 + version: 4.2.1(typescript@5.2.2)(vite@4.4.11) packages: @@ -1406,6 +1409,10 @@ packages: slash: 3.0.0 dev: true + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + /goober@2.1.13(csstype@3.1.2): resolution: {integrity: sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==} peerDependencies: @@ -2088,6 +2095,19 @@ packages: typescript: 5.2.2 dev: true + /tsconfck@2.1.2(typescript@5.2.2): + resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} + engines: {node: ^14.13.1 || ^16 || >=18} + hasBin: true + peerDependencies: + typescript: ^4.3.5 || ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 + dev: true + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false @@ -2132,6 +2152,23 @@ packages: punycode: 2.3.0 dev: true + /vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@4.4.11): + resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + dependencies: + debug: 4.3.4 + globrex: 0.1.2 + tsconfck: 2.1.2(typescript@5.2.2) + vite: 4.4.11 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /vite@4.4.11: resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0} diff --git a/hotel-management/src/commons/styles/ButtonIcon.ts b/hotel-management/src/commons/styles/ButtonIcon.ts deleted file mode 100644 index 7dc043e..0000000 --- a/hotel-management/src/commons/styles/ButtonIcon.ts +++ /dev/null @@ -1,22 +0,0 @@ -import styled from 'styled-components'; - -const ButtonIcon = styled.button` - background: none; - - padding: 8px; - transition: all 0.2s; - - border: none; - border-radius: var(--radius-md); - - &:hover { - background-color: var(--hover-background-color); - } - - & svg { - width: 25px; - height: 25px; - } -`; - -export default ButtonIcon; diff --git a/hotel-management/src/components/AppLayout/index.tsx b/hotel-management/src/components/AppLayout/index.tsx index d2a0239..231cff6 100644 --- a/hotel-management/src/components/AppLayout/index.tsx +++ b/hotel-management/src/components/AppLayout/index.tsx @@ -1,8 +1,8 @@ import { Outlet } from 'react-router-dom'; // Components -import Header from '../Header'; -import Sidebar from '../Sidebar'; +import Header from '@component/Header'; +import Sidebar from '@component/Sidebar'; // Styled import { Main, StyledAppLayout } from './styled'; diff --git a/hotel-management/src/components/ButtonIcon/ButtonIcon.tsx b/hotel-management/src/components/ButtonIcon/ButtonIcon.tsx new file mode 100644 index 0000000..8375518 --- /dev/null +++ b/hotel-management/src/components/ButtonIcon/ButtonIcon.tsx @@ -0,0 +1,131 @@ +import { COLOR, SIZE } from '@constant/styles'; +import { ReactNode } from 'react'; +import styled, { css } from 'styled-components'; + +interface IStyledButtonIcon { + isHaveChildren: boolean; + style?: { + fontSize?: string; + backgroundColor?: string; + color?: string; + }; + iconStyle?: { + color?: string; + size?: string; + }; +} + +const StyledButtonIcon = styled.button` + background: none; + + padding: 10px; + transition: all 0.2s; + + border: none; + + ${(props) => + props.style?.fontSize && + css` + font-size: ${props.style.fontSize}; + `} + + ${(props) => + props.style?.fontSize && + css` + font-size: ${props.style.fontSize}; + `} + + ${(props) => + props.color && + css` + font-size: ${props.color}; + `} + + ${(props) => + props.isHaveChildren + ? css` + display: flex; + align-items: center; + gap: 10px; + + text-align: left; + width: 100%; + ` + : css` + border-radius: var(--radius-md); + `} + + &:hover { + background-color: var(--hover-background-color); + } + + & svg { + ${(props) => + props.iconStyle?.size && + css` + width: ${props.iconStyle.size}; + height: ${props.iconStyle.size}; + `} + + ${(props) => + props.iconStyle?.color && + css` + color: ${props.iconStyle.color}; + `} + transition: all 0.3s; + } +`; + +StyledButtonIcon.defaultProps = { + style: { + fontSize: SIZE.DEFAULT, + backgroundColor: COLOR.DEFAULT, + color: COLOR.BLACK, + }, + iconStyle: { + color: COLOR.BLACK, + size: SIZE.DEFAULT, + }, +}; + +interface IButtonIcon { + icon: ReactNode; + children?: ReactNode; + style?: { + fontSize?: string; + backgroundColor?: string; + color?: string; + }; + iconStyle?: { + color?: string; + size?: string; + }; + onClick?: () => void; +} + +const ButtonIcon = ({ + icon, + children, + style, + iconStyle, + onClick, +}: IButtonIcon) => { + const isHaveChildren = Boolean(children); + + return ( + + {icon} {children} + + ); +}; + +export default ButtonIcon; diff --git a/hotel-management/src/components/ButtonIcon/styled.ts b/hotel-management/src/components/ButtonIcon/styled.ts new file mode 100644 index 0000000..e69de29 diff --git a/hotel-management/src/components/HeaderMenu/index.tsx b/hotel-management/src/components/HeaderMenu/index.tsx index 3e24e4b..8365978 100644 --- a/hotel-management/src/components/HeaderMenu/index.tsx +++ b/hotel-management/src/components/HeaderMenu/index.tsx @@ -1,7 +1,7 @@ // Components import { IoPersonCircleOutline } from 'react-icons/io5'; import { HiOutlineLogout } from 'react-icons/hi'; -import ButtonIcon from '../../commons/styles/ButtonIcon'; +import ButtonIcon from '@component/ButtonIcon/ButtonIcon'; // Styled import { StyledHeaderMenu } from './styled'; @@ -10,14 +10,18 @@ const HeaderMenu = () => { return (
  • - - - + } + iconStyle={{ size: '23px' }} + />
  • - - - + } + iconStyle={{size: '23px' }} + />
  • ); diff --git a/hotel-management/src/components/Menus/index.tsx b/hotel-management/src/components/Menus/index.tsx index df9b4d3..e985f51 100644 --- a/hotel-management/src/components/Menus/index.tsx +++ b/hotel-management/src/components/Menus/index.tsx @@ -4,16 +4,18 @@ import { MouseEvent, ReactNode, useContext, useState } from 'react'; import { HiEllipsisVertical } from 'react-icons/hi2'; // Hooks -import { useOutsideClick } from '../../hooks/useOutsideClick'; +import { useOutsideClick } from '@hook/useOutsideClick'; // Styled -import { StyledMenu, StyledButton, StyledList, StyledToggle } from './styled'; +import { StyledMenu, StyledList, StyledToggle } from './styled'; // Contexts -import MenusContext from '../../contexts/MenuContext'; +import MenusContext from '@context/MenuContext'; // Types -import { Nullable } from '../../types/common'; +import { Nullable } from '@type/common'; +import ButtonIcon from '@component/ButtonIcon/ButtonIcon'; +import { COLOR } from '@constant/styles'; interface IButton { children?: string; @@ -74,9 +76,14 @@ const Button = ({ children, icon, onClick }: IButton): ReactNode => { return (
  • - - {icon} {children} - + + {children} +
  • ); }; diff --git a/hotel-management/src/components/Modal/index.tsx b/hotel-management/src/components/Modal/index.tsx index 8d8ab64..add80a6 100644 --- a/hotel-management/src/components/Modal/index.tsx +++ b/hotel-management/src/components/Modal/index.tsx @@ -8,10 +8,10 @@ import { import { createPortal } from 'react-dom'; // Hooks -import { useOutsideClick } from '../../hooks/useOutsideClick'; +import { useOutsideClick } from '@hook/useOutsideClick'; // Contexts -import { ModalContext } from '../../contexts/ModalContext'; +import { ModalContext } from '@context/ModalContext'; // Styled import { Overlay, StyledModal, StyledModalContent, TitleModal } from './styled'; @@ -34,14 +34,14 @@ const Modal = ({ children }: IModal) => { }; interface IOpen { - children: ReactElement; + renderChildren: (onCloseModal: () => void) => ReactNode; modalName: string; } -const Open = ({ children, modalName }: IOpen) => { +const Open = ({ renderChildren, modalName }: IOpen) => { const { open } = useContext(ModalContext); - return cloneElement(children, { onClick: () => open!(modalName) }); + return renderChildren(() => open!(modalName)); }; interface IWindow { @@ -54,9 +54,6 @@ const Window = ({ children, name, title }: IWindow) => { const { openName, close } = useContext(ModalContext); const ref = useOutsideClick(close!); - console.log('name: ', name); - console.log('openName: ', openName); - if (name !== openName) return null; return createPortal( diff --git a/hotel-management/src/components/Nav/index.tsx b/hotel-management/src/components/Nav/index.tsx index 6ce3f2a..7e0e452 100644 --- a/hotel-management/src/components/Nav/index.tsx +++ b/hotel-management/src/components/Nav/index.tsx @@ -7,7 +7,7 @@ import { BsHouseDoorFill } from 'react-icons/bs'; import { StyledNav, StyledNavLink } from './styled'; // Constants -import { DASHBOARD, ROOM, USER } from '../../constants/path'; +import { DASHBOARD, ROOM, USER } from '@constant/path'; const Nav = () => { return ( diff --git a/hotel-management/src/components/OrderBy/index.tsx b/hotel-management/src/components/OrderBy/index.tsx index c24df2e..f4ceaa8 100644 --- a/hotel-management/src/components/OrderBy/index.tsx +++ b/hotel-management/src/components/OrderBy/index.tsx @@ -1,4 +1,3 @@ -import { memo } from 'react'; import { useSearchParams } from 'react-router-dom'; // Styled @@ -11,7 +10,7 @@ interface IOrderProps { }[]; } -const OrderBy = memo(({ options }: IOrderProps) => { +const OrderBy = ({ options }: IOrderProps) => { const field = 'orderBy'; const [searchParams, setSearchParams] = useSearchParams(); const currentOrder = searchParams.get(field) || options[0].value; @@ -35,6 +34,6 @@ const OrderBy = memo(({ options }: IOrderProps) => { ))} ); -}); +}; export default OrderBy; diff --git a/hotel-management/src/components/Search/index.tsx b/hotel-management/src/components/Search/index.tsx index 06d3bbb..b60db2c 100644 --- a/hotel-management/src/components/Search/index.tsx +++ b/hotel-management/src/components/Search/index.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import { StyledSearch } from './styled'; // Hooks -import { useDebounce } from '../../hooks/useDebounce'; +import { useDebounce } from '@hook/useDebounce'; interface ISearch { setPlaceHolder: string; diff --git a/hotel-management/src/components/Select/index.tsx b/hotel-management/src/components/Select/index.tsx index 8a59409..720c0f0 100644 --- a/hotel-management/src/components/Select/index.tsx +++ b/hotel-management/src/components/Select/index.tsx @@ -1,10 +1,9 @@ import { FieldValues, RegisterOptions, - UseFormRegister, useFormContext, } from 'react-hook-form'; -import { ChangeEventHandler, ReactNode } from 'react'; +import { ChangeEventHandler } from 'react'; // Styled import { StyledSelect } from './styled'; @@ -22,46 +21,6 @@ interface ISelect { ariaLabel: string; } -interface IRender extends ISelect { - register: UseFormRegister; - children: ReactNode[]; -} - -const RenderSelect = ({ - options, - id, - value, - optionsConfigForm, - onChange, - register, - children, - ariaLabel, -}: IRender) => { - if (options && !register) { - return ( - - {children} - - ); - } - - return ( - - {children} - - ); -}; - const Select = ({ options, id, @@ -75,21 +34,20 @@ const Select = ({ if (!options) return; return ( - {options.map((option) => ( ))} - + ); }; diff --git a/hotel-management/src/components/Sidebar/index.tsx b/hotel-management/src/components/Sidebar/index.tsx index 3c14f06..b393109 100644 --- a/hotel-management/src/components/Sidebar/index.tsx +++ b/hotel-management/src/components/Sidebar/index.tsx @@ -1,5 +1,5 @@ // Components -import Nav from '../Nav'; +import Nav from '@component/Nav'; // Styled import { Heading, StyledSidebar } from './styled'; diff --git a/hotel-management/src/components/SortBy/index.tsx b/hotel-management/src/components/SortBy/index.tsx index ca85296..1d29c41 100644 --- a/hotel-management/src/components/SortBy/index.tsx +++ b/hotel-management/src/components/SortBy/index.tsx @@ -1,8 +1,8 @@ import { useSearchParams } from 'react-router-dom'; -import { ChangeEvent, memo } from 'react'; +import { ChangeEvent } from 'react'; // Components -import Select from '../Select'; +import Select from '@component/Select'; interface ISortByProps { options: { @@ -11,7 +11,7 @@ interface ISortByProps { }[]; } -const SortBy = memo(({ options }: ISortByProps) => { +const SortBy = ({ options }: ISortByProps) => { const [searchParams, setSearchParams] = useSearchParams(); const sortBy = searchParams.get('sortBy') || ''; const handleChange = (event: ChangeEvent) => { @@ -27,6 +27,6 @@ const SortBy = memo(({ options }: ISortByProps) => { ariaLabel="Sort" /> ); -}); +}; export default SortBy; diff --git a/hotel-management/src/components/Table/index.tsx b/hotel-management/src/components/Table/index.tsx index 0f16658..1cea173 100644 --- a/hotel-management/src/components/Table/index.tsx +++ b/hotel-management/src/components/Table/index.tsx @@ -4,13 +4,17 @@ import { ReactNode, useContext } from 'react'; import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled'; // Contexts -import TableContext from '../../contexts/TableContext'; +import TableContext from '@context/TableContext'; export interface ITable { columns?: string; children: ReactNode; } +export interface IHeader { + headerColumn: string[]; +} + interface ITableBody { data?: T[]; render?: CallbackMapFunc; @@ -26,10 +30,12 @@ const Table = ({ columns, children }: ITable) => { ); }; -const Header = ({ children }: ITable) => { +const Header = ({ headerColumn }: IHeader) => { const { columns } = useContext(TableContext); - return {children}; + return + {headerColumn.map((item) =>
    {item}
    )} +
    ; }; const Body = ({ data, render }: ITableBody) => { diff --git a/hotel-management/src/constants/variables.ts b/hotel-management/src/constants/commons.ts similarity index 82% rename from hotel-management/src/constants/variables.ts rename to hotel-management/src/constants/commons.ts index 426425d..eaaca99 100644 --- a/hotel-management/src/constants/variables.ts +++ b/hotel-management/src/constants/commons.ts @@ -51,9 +51,6 @@ const ROOM_PAGE = { ], }; -const VALUE = 'value'; -const ERROR = 'error'; - const INIT_VALUE_USER_FORM = { name: '', id: 0, @@ -68,11 +65,23 @@ const INIT_VALUE_ROOM_FORM = { discount: 0, }; +const FORM = { + EDIT: 'edit', + USER: 'user-form', + ROOM: 'room-form', +}; + +const REGEX = { + PHONE: /(84|0[3|5|7|8|9])+([0-9]{8})\b/g, + NAME: /^[a-zA-Z]{2,}(?: [a-zA-Z]+){0,2}$/gm, + NUMBER: /^[0-9]*$/, +}; + export { USER_PAGE, ROOM_PAGE, - VALUE, - ERROR, + FORM, + REGEX, ORDERBY_OPTIONS, INIT_VALUE_USER_FORM, INIT_VALUE_ROOM_FORM, diff --git a/hotel-management/src/constants/styles.ts b/hotel-management/src/constants/styles.ts new file mode 100644 index 0000000..2144a4c --- /dev/null +++ b/hotel-management/src/constants/styles.ts @@ -0,0 +1,11 @@ +const COLOR = { + PRIMARY: '#0010ba', + BLACK: '#000', + DEFAULT: 'transparent', +}; + +const SIZE = { + DEFAULT: '14px', +}; + +export { COLOR, SIZE }; diff --git a/hotel-management/src/helpers/sendRequest.ts b/hotel-management/src/helpers/sendRequest.ts index 329645b..717f7f0 100644 --- a/hotel-management/src/helpers/sendRequest.ts +++ b/hotel-management/src/helpers/sendRequest.ts @@ -1,8 +1,8 @@ // Constants -import { BASE_URL } from '../constants/path'; +import { BASE_URL } from '@constant/path'; // Types -import { IResponse } from '../types/responses'; +import { IResponse } from '@type/responses'; type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; diff --git a/hotel-management/src/helpers/validators.ts b/hotel-management/src/helpers/validators.ts index ae3c974..25ecace 100644 --- a/hotel-management/src/helpers/validators.ts +++ b/hotel-management/src/helpers/validators.ts @@ -1,20 +1,12 @@ /** - * 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 + * Compare the regex with value + * @param regex The regex to compare * @param value Value need to be checked - * @returns A boolean indicating whether or not the argument has valid + * @returns A boolean indicating whether of not the argument has valid */ -const isValidNumber = (value: string): boolean => { - return /^[0-9]*$/.test(value); -}; +const isValidRegex = (regex: RegExp, value: string): boolean => { + return regex.test(value); +} /** * Check value is valid discount or not @@ -25,14 +17,6 @@ const isValidDiscount = (value: number): boolean => { return value >= 0 && value <= 100; }; -/** - * Check value is valid phone number or not - * @param value Value need to be checked - * @returns A boolean indicating whether or not the argument has valid - */ -const isValidPhoneNumber = (value: string): boolean => { - return /(84|0[3|5|7|8|9])+([0-9]{8})\b/g.test(value); -}; /** * Check value is valid string or not @@ -43,12 +27,6 @@ const isValidString = (value: string): boolean => { return value.trim().length >= 5; }; -/** - * A method skip check value - * @returns Return true - */ -const skipCheck = () => true; - /** * Check object is empty or not * @param obj Object need to be check @@ -59,11 +37,8 @@ const isEmptyObj = (obj: object) => { }; export { - isValidName, - isValidNumber, - isValidPhoneNumber, + isValidRegex, isValidString, - skipCheck, isValidDiscount, isEmptyObj, }; diff --git a/hotel-management/src/hooks/useFetch.ts b/hotel-management/src/hooks/useFetch.ts index 70cd78e..39b6b02 100644 --- a/hotel-management/src/hooks/useFetch.ts +++ b/hotel-management/src/hooks/useFetch.ts @@ -1,14 +1,14 @@ import { useEffect, useState } from 'react'; // Constants -import { BASE_URL } from '../constants/path'; -import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config'; +import { BASE_URL } from '@constant/path'; +import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '@constant/config'; // Helpers -import { searchQuery } from '../helpers/helper'; +import { searchQuery } from '@helper/helper'; // Types -import { Nullable } from '../types/common'; +import { Nullable } from '@type/common'; /** * The function use to fetch data on server API. diff --git a/hotel-management/src/hooks/useForwardRef.ts b/hotel-management/src/hooks/useForwardRef.ts index e1ed2eb..e333401 100644 --- a/hotel-management/src/hooks/useForwardRef.ts +++ b/hotel-management/src/hooks/useForwardRef.ts @@ -1,7 +1,7 @@ import { ForwardedRef, useEffect, useRef } from 'react'; // Types -import { Nullable } from '../types/common'; +import { Nullable } from '@type/common'; const useForwardRef = ( ref: ForwardedRef, diff --git a/hotel-management/src/hooks/useOutsideClick.ts b/hotel-management/src/hooks/useOutsideClick.ts index 3c5d651..84a2248 100644 --- a/hotel-management/src/hooks/useOutsideClick.ts +++ b/hotel-management/src/hooks/useOutsideClick.ts @@ -1,7 +1,7 @@ import { MutableRefObject, useEffect, useRef } from 'react'; // Types -import { Nullable } from '../types/common'; +import { Nullable } from '@type/common'; /** * Custom hook to call handler when click outside element diff --git a/hotel-management/src/pages/Room/RomTable.tsx b/hotel-management/src/pages/Room/RomTable.tsx index 9b57b7f..f6dd170 100644 --- a/hotel-management/src/pages/Room/RomTable.tsx +++ b/hotel-management/src/pages/Room/RomTable.tsx @@ -2,25 +2,26 @@ import { useSearchParams } from 'react-router-dom'; import { Dispatch, SetStateAction, useEffect, useState } from 'react'; // Components -import { StyledOperationTable } from './styled'; -import Menus from '../../components/Menus'; -import Table from '../../components/Table'; -import Direction from '../../commons/styles/Direction'; -import Message from '../../components/Message'; -import Search from '../../components/Search'; -import SortBy from '../../components/SortBy'; -import OrderBy from '../../components/OrderBy'; +import Menus from '@component/Menus'; +import Table from '@component/Table'; +import Message from '@component/Message'; +import Search from '@component/Search'; +import SortBy from '@component/SortBy'; +import OrderBy from '@component/OrderBy'; +import RoomRow from './RoomRow'; // Types -import { IRoom } from '../../types/rooms'; +import { IRoom } from '@type/rooms'; // Constants -import { useFetch } from '../../hooks/useFetch'; -import { ORDERBY_OPTIONS, ROOM_PAGE } from '../../constants/variables'; +import { useFetch } from '@hook/useFetch'; +import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons'; // Styled -import Spinner from '../../commons/styles/Spinner'; -import RoomRow from './RoomRow'; +import { StyledOperationTable } from './styled'; +import Direction from '@commonStyle/Direction'; +import Spinner from '@commonStyle/Spinner'; + interface IRoomTable { reload: boolean; @@ -28,6 +29,7 @@ interface IRoomTable { } const RoomTable = ({ reload, setReload }: IRoomTable) => { + const columnName = ['Id', 'Name', 'Price', 'Status']; const [nameSearch, setNameSearch] = useState(''); const [searchParams] = useSearchParams(); const [rooms, setRooms] = useState([]); @@ -77,12 +79,7 @@ const RoomTable = ({ reload, setReload }: IRoomTable) => { {rooms.length ? ( - -
    Id
    -
    Name
    -
    Price
    -
    Status
    -
    + data={rooms} render={(room: IRoom) => ( diff --git a/hotel-management/src/pages/Room/RoomForm.tsx b/hotel-management/src/pages/Room/RoomForm.tsx index 2c824dd..f344976 100644 --- a/hotel-management/src/pages/Room/RoomForm.tsx +++ b/hotel-management/src/pages/Room/RoomForm.tsx @@ -6,36 +6,35 @@ import styled from 'styled-components'; import { useForm } from 'react-hook-form'; // Styled -import Button from '../../commons/styles/Button.ts'; -import Input from '../../commons/styles/Input.ts'; +import Button from '@commonStyle/Button.ts'; +import Input from '@commonStyle/Input.ts'; // Types -import { Nullable } from '../../types/common.ts'; -import { IRoom } from '../../types/rooms.ts'; +import { Nullable } from '@type/common.ts'; +import { IRoom } from '@type/rooms.ts'; // Constants -import { - ADD_SUCCESS, - EDIT_SUCCESS, -} from '../../constants/messages.ts'; +import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts'; import { INVALID_DISCOUNT, INVALID_FIELD, REQUIRED_FIELD_ERROR, -} from '../../constants/formValidateMessage.ts'; +} from '@constant/formValidateMessage.ts'; +import { INIT_VALUE_ROOM_FORM, REGEX } from '../../constants/commons.ts'; // Helpers import { isValidDiscount, - isValidNumber, + isValidRegex, isValidString, -} from '../../helpers/validators.ts'; +} from '@helper/validators.ts'; // Components -import FormRow from '../../components/LabelControl/index.tsx'; -import Form from '../../components/Form/index.tsx'; -import { addRoom, updateRoom } from '../../services/roomServices.ts'; -import { INIT_VALUE_ROOM_FORM } from '../../constants/variables.ts'; +import FormRow from '@component/LabelControl/index.tsx'; +import Form from '@component/Form/index.tsx'; + +// Services +import { addRoom, updateRoom } from '@service/roomServices.ts'; const FormBtn = styled(Button)` width: 100%; @@ -82,30 +81,25 @@ const RoomForm = ({ // Calculate final price room.finalPrice = room.price - (room.price * room.discount) / 100; - try { - if (!roomEdit) { - // Add request - const response = await addRoom(room); - - if(response) { - toast.success(ADD_SUCCESS); - } - } else { - // Edit request - const response = await updateRoom(room); + if (!roomEdit) { + // Add request + const response = await addRoom(room); - if (response) { - toast.success(EDIT_SUCCESS); - } + if (response) { + toast.success(ADD_SUCCESS); } - // Reload table data - setReload!(!reload); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); + } else { + // Edit request + const response = await updateRoom(room); + + if (response) { + toast.success(EDIT_SUCCESS); } } + // Reload table data + setReload!(!reload); + reset(); onCloseModal!(); }; @@ -136,7 +130,7 @@ const RoomForm = ({ required: REQUIRED_FIELD_ERROR, validate: { checkIdentifiedCode: (v) => - isValidNumber(v.toString()) || INVALID_FIELD, + isValidRegex(REGEX.NUMBER, v.toString()) || INVALID_FIELD, }, onChange: () => trigger('price'), })} @@ -159,7 +153,11 @@ const RoomForm = ({ - + { !roomEdit ? 'Add' diff --git a/hotel-management/src/pages/Room/RoomRow.tsx b/hotel-management/src/pages/Room/RoomRow.tsx index 6fa72c8..f6662a4 100644 --- a/hotel-management/src/pages/Room/RoomRow.tsx +++ b/hotel-management/src/pages/Room/RoomRow.tsx @@ -2,22 +2,22 @@ import { Dispatch, SetStateAction } from 'react'; import toast from 'react-hot-toast'; // Constants -import { ROOM_PATH } from '../../constants/path'; -import { STATUS_CODE } from '../../constants/responseStatus'; -import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages'; +import { ROOM_PATH } from '@constant/path'; +import { STATUS_CODE } from '@constant/responseStatus'; +import { CONFIRM_DELETE, DELETE_SUCCESS } from '@constant/messages'; // Helpers -import { sendRequest } from '../../helpers/sendRequest'; -import { formatCurrency } from '../../helpers/helper'; -import Modal from '../../components/Modal'; -import RoomForm from './RoomForm'; +import { sendRequest } from '@helper/sendRequest'; +import { formatCurrency } from '@helper/helper'; // Types -import { IRoom } from '../../types/rooms'; +import { IRoom } from '@type/rooms'; // Components -import Table from '../../components/Table'; -import Menus from '../../components/Menus'; +import RoomForm from './RoomForm'; +import Modal from '@component/Modal'; +import Table from '@component/Table'; +import Menus from '@component/Menus'; import { RiEditBoxFill } from 'react-icons/ri'; import { HiTrash } from 'react-icons/hi'; @@ -43,7 +43,9 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => { const { id, name, finalPrice, status } = room; - const statusText = status ? 'Unavailable' : 'Available'; + const statusText = status + ? 'Unavailable' + : 'Available'; return ( @@ -58,9 +60,14 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => { - - }>Edit - + ( + }> + Edit + + )} + /> } diff --git a/hotel-management/src/pages/Room/index.tsx b/hotel-management/src/pages/Room/index.tsx index 7a0d801..aa7c3ab 100644 --- a/hotel-management/src/pages/Room/index.tsx +++ b/hotel-management/src/pages/Room/index.tsx @@ -2,15 +2,15 @@ import { useState } from 'react'; // Components import RoomTable from './RomTable'; +import RoomForm from './RoomForm'; // Styled import { StyledRoom, Title } from './styled'; -import Direction from '../../commons/styles/Direction'; -import Button from '../../commons/styles/Button'; +import Direction from '@commonStyle/Direction.ts'; +import Button from '@commonStyle/Button'; // Types -import Modal from '../../components/Modal'; -import RoomForm from './RoomForm'; +import Modal from '@component/Modal'; const Room = () => { const [reload, setReload] = useState(true); @@ -22,19 +22,19 @@ const Room = () => { List Room - - - + ( + + )} + /> - + ); diff --git a/hotel-management/src/pages/User/FormUser.tsx b/hotel-management/src/pages/User/FormUser.tsx index 8879ed0..ebe47b4 100644 --- a/hotel-management/src/pages/User/FormUser.tsx +++ b/hotel-management/src/pages/User/FormUser.tsx @@ -11,41 +11,38 @@ import toast from 'react-hot-toast'; import { FormProvider, useForm } from 'react-hook-form'; // Styled -import Input from '../../commons/styles/Input.ts'; +import Input from '@commonStyle/Input.ts'; // Components -import Form from '../../components/Form/index.tsx'; -import FormRow from '../../components/LabelControl/index.tsx'; -import Select, { ISelectOptions } from '../../components/Select/index.tsx'; +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, - isValidName, - isValidNumber, - isValidPhoneNumber, -} from '../../helpers/validators.ts'; + isEmptyObj, isValidRegex, +} from '@helper/validators.ts'; // Constants -import { ADD_SUCCESS, EDIT_SUCCESS } from '../../constants/messages.ts'; +import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts'; import { INVALID_FIELD, INVALID_PHONE, REQUIRED_FIELD_ERROR, -} from '../../constants/formValidateMessage.ts'; -import { INIT_VALUE_USER_FORM } from '../../constants/variables.ts'; +} from '@constant/formValidateMessage.ts'; +import { INIT_VALUE_USER_FORM, REGEX } from '@constant/commons.ts'; // Styled import { FormBtn } from './styled.ts'; // Services -import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts'; +import { getAllRoom, updateRoomStatus } from '@service/roomServices.ts'; +import { createUser, updateUser } from '@service/userServices.ts'; // Types -import { Nullable } from '../../types/common.ts'; -import { IUser } from '../../types/users.ts'; -import { IRoom } from '../../types/rooms.ts'; -import { createUser, updateUser } from '../../services/userServices.ts'; +import { Nullable } from '@type/common.ts'; +import { IUser } from '@type/users.ts'; +import { IRoom } from '@type/rooms.ts'; interface IUserFormProp { onCloseModal?: () => void; @@ -109,35 +106,29 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { // Submit form const onSubmit = useCallback( async (newUser: IUser) => { - try { - if (!user) { - // Add request - const response = await createUser(newUser); + if (!user) { + // Add request + const response = await createUser(newUser); - if (response) { - toast.success(ADD_SUCCESS); - } - - // Update room status - updateRoomStatus(newUser.roomId, true); - } else { - // Edit request - const response = await updateUser(newUser); - - if (response) { - toast.success(EDIT_SUCCESS); - } - - // Update room status - updateRoomStatus(user!.roomId, true, newUser.roomId); + if (response) { + toast.success(ADD_SUCCESS); } - // Reload table data - setReload(!reload); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); + + // Update room status + updateRoomStatus(newUser.roomId, true); + } 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!(); @@ -169,7 +160,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { {...register('name', { required: REQUIRED_FIELD_ERROR, validate: { - checkValidName: (value) => isValidName(value) || INVALID_FIELD, + checkValidName: (value) => isValidRegex(REGEX.NAME, value) || INVALID_FIELD, }, onChange: () => trigger('name'), })} @@ -187,7 +178,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { required: REQUIRED_FIELD_ERROR, validate: { checkIdentifiedCode: (v) => - isValidNumber(v.toString()) || INVALID_FIELD, + isValidRegex(REGEX.NUMBER, v.toString()) || INVALID_FIELD, }, onChange: () => trigger('identifiedCode'), })} @@ -201,7 +192,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { {...register('phone', { required: REQUIRED_FIELD_ERROR, validate: { - checkPhoneNum: (v) => isValidPhoneNumber(v) || INVALID_PHONE, + checkPhoneNum: (v) => isValidRegex(REGEX.PHONE, v) || INVALID_PHONE, }, onChange: () => trigger('phone'), })} diff --git a/hotel-management/src/pages/User/TableUser.tsx b/hotel-management/src/pages/User/TableUser.tsx index bdc68cb..645969e 100644 --- a/hotel-management/src/pages/User/TableUser.tsx +++ b/hotel-management/src/pages/User/TableUser.tsx @@ -2,31 +2,27 @@ import { useSearchParams } from 'react-router-dom'; import { Dispatch, SetStateAction, useEffect, useState } from 'react'; // Components -import Menus from '../../components/Menus'; -import Table from '../../components/Table'; -import Direction from '../../commons/styles/Direction'; -import Message from '../../components/Message'; -import Search from '../../components/Search'; -import SortBy from '../../components/SortBy'; -import OrderBy from '../../components/OrderBy'; - -// Types -import { IUser } from '../../types/users'; - -// Hooks -import { useFetch } from '../../hooks/useFetch'; - -// Constants -import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables'; - -// Styled -import { StyledOperationTable } from './styled'; -import Spinner from '../../commons/styles/Spinner'; +import Menus from '@component/Menus'; +import Table from '@component/Table'; +import Message from '@component/Message'; +import Search from '@component/Search'; +import SortBy from '@component/SortBy'; +import OrderBy from '@component/OrderBy'; import UserRow from './UserRow'; +// Types +import { IUser } from '@type/users'; +// Hooks +import { useFetch } from '@hook/useFetch'; +// Constants +import { ORDERBY_OPTIONS, USER_PAGE } from '@constant/commons'; +// Styled +import Direction from '@commonStyle/Direction'; +import { StyledOperationTable } from './styled'; +import Spinner from '@commonStyle/Spinner'; interface IUserTable { reload: boolean; @@ -34,6 +30,7 @@ interface IUserTable { } const UserTable = ({ reload, setReload }: IUserTable) => { + const columnName = ['Id', 'Name', 'Identified Code', 'Phone', 'Room Id']; const [users, setUsers] = useState([]); const [phoneSearch, setPhoneSearch] = useState(''); const [searchParams] = useSearchParams(); @@ -83,13 +80,7 @@ const UserTable = ({ reload, setReload }: IUserTable) => { {users.length ? (
    - -
    Id
    -
    Name
    -
    Identified Code
    -
    Phone
    -
    Room Id
    -
    + data={users} render={(user: IUser) => ( diff --git a/hotel-management/src/pages/User/UserRow.tsx b/hotel-management/src/pages/User/UserRow.tsx index 5218b2b..dc45a61 100644 --- a/hotel-management/src/pages/User/UserRow.tsx +++ b/hotel-management/src/pages/User/UserRow.tsx @@ -4,21 +4,21 @@ import { Dispatch, SetStateAction } from 'react'; // Components import { RiEditBoxFill } from 'react-icons/ri'; import { IoExit } from 'react-icons/io5'; -import Modal from '../../components/Modal'; +import Modal from '@component/Modal'; +import Table from '@component/Table'; +import Menus from '@component/Menus'; import UserForm from './FormUser'; -import Table from '../../components/Table'; -import Menus from '../../components/Menus'; // Constants -import { STATUS_CODE } from '../../constants/responseStatus'; -import { CONFIRM_MESSAGE, DENIED_ACTION } from '../../constants/messages'; +import { STATUS_CODE } from '@constant/responseStatus'; +import { CONFIRM_MESSAGE, DENIED_ACTION } from '@constant/messages'; // Services -import { updateRoomStatus } from '../../services/roomServices'; -import { checkOutUser } from '../../services/userServices'; +import { updateRoomStatus } from '@service/roomServices'; +import { checkOutUser } from '@service/userServices'; // Types -import { IUser } from '../../types/users'; +import { IUser } from '@type/users'; interface IUserRow { user: IUser; @@ -56,17 +56,27 @@ const UserRow = ({ user, reload, setReload }: IUserRow) => {
    {name}
    {identifiedCode}
    {phone}
    -
    {roomId ? roomId : 'None'}
    - +
    + { + roomId + ? roomId + : 'None' + } +
    - - }>Edit - + ( + }> + Edit + + )} + /> } onClick={() => handleCheckOut(user)} diff --git a/hotel-management/src/pages/User/index.tsx b/hotel-management/src/pages/User/index.tsx index 9713ed3..86d794c 100644 --- a/hotel-management/src/pages/User/index.tsx +++ b/hotel-management/src/pages/User/index.tsx @@ -4,15 +4,19 @@ import { useState } from 'react'; import UserTable from './TableUser'; // Styled -import Button from '../../commons/styles/Button'; -import Direction from '../../commons/styles/Direction'; +import Button from '@commonStyle/Button'; +import Direction from '@commonStyle/Direction.ts'; import { StyledUser, Title } from './styled'; // Types -import Modal from '../../components/Modal'; +import Modal from '@component/Modal'; import UserForm from './FormUser'; +// Constants +import { FORM } from '@constant/commons'; + const User = () => { + const ADD_ROOM = 'Add room'; const [reload, setReload] = useState(true); return ( @@ -22,10 +26,13 @@ const User = () => { List User - - - - + ( + + )} + /> + diff --git a/hotel-management/src/services/roomServices.ts b/hotel-management/src/services/roomServices.ts index d258e37..4976142 100644 --- a/hotel-management/src/services/roomServices.ts +++ b/hotel-management/src/services/roomServices.ts @@ -1,17 +1,17 @@ import toast from 'react-hot-toast'; // Types -import { Nullable } from '../types/common'; -import { IResponse } from '../types/responses'; -import { IRoom } from '../types/rooms'; +import { Nullable } from '@type/common'; +import { IResponse } from '@type/responses'; +import { IRoom } from '@type/rooms'; // Helpers -import { sendRequest } from '../helpers/sendRequest'; -import { errorMsg } from '../helpers/helper'; +import { sendRequest } from '@helper/sendRequest'; +import { errorMsg } from '@helper/helper'; // Constants -import { STATUS_CODE, RESPONSE_MESSAGE } from '../constants/responseStatus'; -import { ROOM_PATH } from '../constants/path'; +import { STATUS_CODE, RESPONSE_MESSAGE } from '@constant/responseStatus'; +import { ROOM_PATH } from '@constant/path'; /** * Get all rooms from server diff --git a/hotel-management/src/services/userServices.ts b/hotel-management/src/services/userServices.ts index ae43f86..94cc7b3 100644 --- a/hotel-management/src/services/userServices.ts +++ b/hotel-management/src/services/userServices.ts @@ -1,17 +1,17 @@ import toast from 'react-hot-toast'; // Constants -import { USER_PATH } from '../constants/path'; -import { STATUS_CODE } from '../constants/responseStatus'; +import { USER_PATH } from '@constant/path'; +import { STATUS_CODE } from '@constant/responseStatus'; // Types -import { Nullable } from '../types/common'; -import { IResponse } from '../types/responses'; -import { IUser } from '../types/users'; +import { Nullable } from '@type/common'; +import { IResponse } from '@type/responses'; +import { IUser } from '@type/users'; // Helpers -import { sendRequest } from '../helpers/sendRequest'; -import { errorMsg } from '../helpers/helper'; +import { sendRequest } from '@helper/sendRequest'; +import { errorMsg } from '@helper/helper'; /** * Create user to the server diff --git a/hotel-management/tsconfig.json b/hotel-management/tsconfig.json index a7fc6fb..5e4f195 100644 --- a/hotel-management/tsconfig.json +++ b/hotel-management/tsconfig.json @@ -5,6 +5,7 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, + "types": ["node"], /* Bundler mode */ "moduleResolution": "bundler", @@ -18,7 +19,20 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + + /* Alias */ + "paths": { + "@service/*": ["./src/services/*"], + "@constant/*": ["./src/constants/*"], + "@hook/*": ["./src/hooks/*"], + "@helper/*": ["./src/helpers/*"], + "@context/*": ["./src/contexts/*"], + "@component/*": ["./src/components/*"], + "@commonStyle/*": ["./src/commons/styles/*"], + "@type/*": ["./src/types/*"], + "@page/*": ["./src/pages/*"] + } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/hotel-management/vite.config.ts b/hotel-management/vite.config.ts index 5a33944..3e8ce05 100644 --- a/hotel-management/vite.config.ts +++ b/hotel-management/vite.config.ts @@ -1,7 +1,8 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import tsconfigPaths from 'vite-tsconfig-paths' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), tsconfigPaths()], }) From 091f4c2e9d001aaac18513b445e2b75f39fad5c3 Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Wed, 8 Nov 2023 20:17:41 +0700 Subject: [PATCH 4/4] Fix form validate --- hotel-management/src/pages/Room/RoomForm.tsx | 8 ++--- .../pages/User/{FormUser.tsx => UserForm.tsx} | 29 +++++++++---------- hotel-management/src/pages/User/UserRow.tsx | 2 +- .../User/{TableUser.tsx => UserTable.tsx} | 0 hotel-management/src/pages/User/index.tsx | 8 ++--- hotel-management/tsconfig.json | 1 - 6 files changed, 23 insertions(+), 25 deletions(-) rename hotel-management/src/pages/User/{FormUser.tsx => UserForm.tsx} (89%) rename hotel-management/src/pages/User/{TableUser.tsx => UserTable.tsx} (100%) diff --git a/hotel-management/src/pages/Room/RoomForm.tsx b/hotel-management/src/pages/Room/RoomForm.tsx index f344976..3f90f55 100644 --- a/hotel-management/src/pages/Room/RoomForm.tsx +++ b/hotel-management/src/pages/Room/RoomForm.tsx @@ -114,7 +114,7 @@ const RoomForm = ({ {...register('name', { required: REQUIRED_FIELD_ERROR, validate: { - checkValidName: (value) => isValidString(value) || INVALID_FIELD, + checkValidRoomName: (value) => isValidString(value) || INVALID_FIELD, }, onChange: () => trigger('name'), })} @@ -129,8 +129,8 @@ const RoomForm = ({ valueAsNumber: true, required: REQUIRED_FIELD_ERROR, validate: { - checkIdentifiedCode: (v) => - isValidRegex(REGEX.NUMBER, v.toString()) || INVALID_FIELD, + checkPrice: (v) => + isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || INVALID_FIELD, }, onChange: () => trigger('price'), })} @@ -145,7 +145,7 @@ const RoomForm = ({ valueAsNumber: true, required: REQUIRED_FIELD_ERROR, validate: { - checkPhoneNum: (v) => isValidDiscount(v) || INVALID_DISCOUNT, + checkDiscount: (v) => isValidDiscount(v) || INVALID_DISCOUNT, }, onChange: () => trigger('discount'), })} diff --git a/hotel-management/src/pages/User/FormUser.tsx b/hotel-management/src/pages/User/UserForm.tsx similarity index 89% rename from hotel-management/src/pages/User/FormUser.tsx rename to hotel-management/src/pages/User/UserForm.tsx index ebe47b4..80608be 100644 --- a/hotel-management/src/pages/User/FormUser.tsx +++ b/hotel-management/src/pages/User/UserForm.tsx @@ -19,9 +19,7 @@ import FormRow from '@component/LabelControl/index.tsx'; import Select, { ISelectOptions } from '@component/Select/index.tsx'; // Helpers -import { - isEmptyObj, isValidRegex, -} from '@helper/validators.ts'; +import { isEmptyObj, isValidRegex } from '@helper/validators.ts'; // Constants import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts'; @@ -67,9 +65,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { useEffect(() => { const load = async () => { const options: ISelectOptions[] = []; - const tempUser = !user - ? { roomId: 0 } - : { ...user }; + const tempUser = !user ? { roomId: 0 } : { ...user }; // Load and set default options room if (rooms.length > 0) { @@ -160,7 +156,8 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { {...register('name', { required: REQUIRED_FIELD_ERROR, validate: { - checkValidName: (value) => isValidRegex(REGEX.NAME, value) || INVALID_FIELD, + checkValidName: (value) => + isValidRegex(new RegExp(REGEX.NAME), value) || INVALID_FIELD, }, onChange: () => trigger('name'), })} @@ -178,7 +175,8 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { required: REQUIRED_FIELD_ERROR, validate: { checkIdentifiedCode: (v) => - isValidRegex(REGEX.NUMBER, v.toString()) || INVALID_FIELD, + isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || + INVALID_FIELD, }, onChange: () => trigger('identifiedCode'), })} @@ -192,7 +190,8 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { {...register('phone', { required: REQUIRED_FIELD_ERROR, validate: { - checkPhoneNum: (v) => isValidRegex(REGEX.PHONE, v) || INVALID_PHONE, + checkPhoneNum: (v) => + isValidRegex(new RegExp(REGEX.PHONE), v) || INVALID_PHONE, }, onChange: () => trigger('phone'), })} @@ -217,12 +216,12 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { - - { - !user - ? 'Add' - : 'Save' - } + + {!user ? 'Add' : 'Save'} Close diff --git a/hotel-management/src/pages/User/UserRow.tsx b/hotel-management/src/pages/User/UserRow.tsx index dc45a61..c21c3c5 100644 --- a/hotel-management/src/pages/User/UserRow.tsx +++ b/hotel-management/src/pages/User/UserRow.tsx @@ -7,7 +7,7 @@ import { IoExit } from 'react-icons/io5'; import Modal from '@component/Modal'; import Table from '@component/Table'; import Menus from '@component/Menus'; -import UserForm from './FormUser'; +import UserForm from './UserForm'; // Constants import { STATUS_CODE } from '@constant/responseStatus'; diff --git a/hotel-management/src/pages/User/TableUser.tsx b/hotel-management/src/pages/User/UserTable.tsx similarity index 100% rename from hotel-management/src/pages/User/TableUser.tsx rename to hotel-management/src/pages/User/UserTable.tsx diff --git a/hotel-management/src/pages/User/index.tsx b/hotel-management/src/pages/User/index.tsx index 86d794c..d1c9b55 100644 --- a/hotel-management/src/pages/User/index.tsx +++ b/hotel-management/src/pages/User/index.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; // Components -import UserTable from './TableUser'; +import UserTable from './UserTable'; // Styled import Button from '@commonStyle/Button'; @@ -10,13 +10,13 @@ import { StyledUser, Title } from './styled'; // Types import Modal from '@component/Modal'; -import UserForm from './FormUser'; +import UserForm from './UserForm'; // Constants import { FORM } from '@constant/commons'; const User = () => { - const ADD_ROOM = 'Add room'; + const TITLE = 'Add user'; const [reload, setReload] = useState(true); return ( @@ -32,7 +32,7 @@ const User = () => { )} /> - + diff --git a/hotel-management/tsconfig.json b/hotel-management/tsconfig.json index 5e4f195..867cbe7 100644 --- a/hotel-management/tsconfig.json +++ b/hotel-management/tsconfig.json @@ -5,7 +5,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - "types": ["node"], /* Bundler mode */ "moduleResolution": "bundler",