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;