mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 20:01:59 +00:00
Add modal component and replace room dialog form
This commit is contained in:
@@ -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 };
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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!}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user