Add modal component and replace room dialog form

This commit is contained in:
2023-11-07 17:46:42 +07:00
parent 7bfc2bcfa4
commit 64c44e6e7c
10 changed files with 195 additions and 83 deletions
@@ -58,7 +58,7 @@ const List = ({
children: ReactNode;
}): Nullable<ReactNode> => {
const { openId, close } = useContext(MenusContext);
const ref = useOutsideClick(close!, false);
const ref = useOutsideClick<HTMLUListElement>(close!, false);
if (openId !== id) return null;
@@ -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 (
<ModalContext.Provider value={{ openName, close, open }}>
{children}
</ModalContext.Provider>
);
};
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<HTMLDivElement>(close!);
console.log('name: ', name);
console.log('openName: ', openName);
if (name !== openName) return null;
return createPortal(
<Overlay>
<StyledModal ref={ref}>
<StyledModalContent>
<TitleModal>{title}</TitleModal>
{cloneElement(children, { onCloseModal: close })}
</StyledModalContent>
</StyledModal>
</Overlay>,
document.body
);
};
Modal.Open = Open;
Modal.Window = Window;
export default Modal;
@@ -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 };
+1 -1
View File
@@ -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';
@@ -0,0 +1,11 @@
import { createContext } from 'react';
interface IModalContext {
openName?: string;
close?: () => void;
open?: React.Dispatch<React.SetStateAction<string>>;
}
const ModalContext = createContext<IModalContext>({});
export { ModalContext };
@@ -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 = <T extends Node>(
handler: () => void,
listeningCapturing = true
): MutableRefObject<Nullable<HTMLUListElement>> => {
const ref = useRef<HTMLUListElement>(null);
): MutableRefObject<Nullable<T>> => {
const ref = useRef<T>(null);
useEffect(() => {
const handleClick = (e: Event) => {
+8 -8
View File
@@ -48,15 +48,15 @@ const FormBtn = styled(Button)`
`;
interface IRoomFormProp {
onClose: () => void;
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
onCloseModal?: () => void;
reload?: boolean;
setReload?: Dispatch<SetStateAction<boolean>>;
room?: Nullable<IRoom>;
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'
}
</FormBtn>
<FormBtn type="button" styled="secondary" onClick={onClose}>
<FormBtn type="button" styled="secondary" onClick={onCloseModal}>
Close
</FormBtn>
</Form.Action>
+28 -42
View File
@@ -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<SetStateAction<Nullable<IRoom>>>;
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
}
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 (
<Table.Row>
@@ -78,21 +64,30 @@ const RoomRow = ({
<div>{formatCurrency(finalPrice)}</div>
<div>{statusText}</div>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<div>
<Modal>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<Menus.List id={id.toString()}>
<Menus.Button
icon={<RiEditBoxFill />}
onClick={() => handleEdit(room)}
>
Edit
</Menus.Button>
<Menus.Button icon={<HiTrash />} onClick={() => handleDelete(room)}>
Delete
</Menus.Button>
</Menus.List>
</Menus.Menu>
<Menus.List id={id.toString()}>
<Modal.Open modalName="edit">
<Menus.Button icon={<RiEditBoxFill />}>Edit</Menus.Button>
</Modal.Open>
<Menus.Button
icon={<HiTrash />}
onClick={() => handleDelete(room)}
>
Delete
</Menus.Button>
</Menus.List>
<Modal.Window name="edit" title="Edit Room">
<RoomForm room={room} />
</Modal.Window>
</Menus.Menu>
</Modal>
</div>
</Table.Row>
);
};
@@ -100,16 +95,9 @@ const RoomRow = ({
interface IRoomTable {
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
openFormDialog: () => void;
setRoom?: Dispatch<SetStateAction<Nullable<IRoom>>>;
}
const RoomTable = ({
reload,
setReload,
openFormDialog,
setRoom,
}: IRoomTable) => {
const RoomTable = ({ reload, setReload }: IRoomTable) => {
const [nameSearch, setNameSearch] = useState('');
const [searchParams] = useSearchParams();
const [rooms, setRooms] = useState<IRoom[]>([]);
@@ -173,8 +161,6 @@ const RoomTable = ({
key={room.id}
reload={reload}
setReload={setReload}
openFormDialog={openFormDialog}
setRoom={setRoom!}
/>
)}
/>
+12 -28
View File
@@ -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<HTMLDialogElement>(null);
const [reload, setReload] = useState(true);
const [room, setRoom] = useState<Nullable<IRoom>>(null);
const [isAdd, setIsAdd] = useState(false);
const openFormDialog = (isAddForm: boolean = false) => {
setIsAdd(isAddForm);
dialogRef.current?.showModal();
};
const closeFormDialog = () => {
dialogRef.current?.close();
};
return (
<>
<StyledRoom>
<Direction type="horizontal">
<Title>List Room</Title>
<Button onClick={() => openFormDialog(true)}>Add room</Button>
<Modal>
<Modal.Open modalName="cabin-form">
<Button>Add room</Button>
</Modal.Open>
<Modal.Window name="cabin-form" title="Add form">
<RoomForm setReload={setReload} reload={reload} />
</Modal.Window>
</Modal>
</Direction>
<RoomTable
reload={reload}
setReload={setReload}
openFormDialog={() => openFormDialog()}
setRoom={setRoom}
/>
</StyledRoom>
<RoomDialog
onClose={closeFormDialog}
ref={dialogRef}
setReload={setReload}
reload={reload}
room={room}
isAdd={isAdd}
/>
</>
);
};
@@ -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;