Apply modal instead of dialog in user pages

This commit is contained in:
2023-11-07 21:51:35 +07:00
parent 64c44e6e7c
commit a99d81a411
13 changed files with 224 additions and 431 deletions
@@ -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<Nullable<HTMLDialogElement>>;
}
const Dialog = forwardRef<HTMLDialogElement, IDialogProps>(
(props: IDialogProps, ref) => {
const { title, children, onClose } = props;
return (
<StyledDialog ref={ref} onClose={onClose}>
<StyledTitle>{title}</StyledTitle>
<StyledBody>{children}</StyledBody>
</StyledDialog>
);
}
);
export default Dialog;
@@ -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 };
+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 = 'http://localhost:3000/';
export const BASE_URL = 'https://hotel-management-api.loiphan.com/';
export const USER_PATH = 'users';
export const ROOM_PATH = 'rooms';
@@ -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<SetStateAction<boolean>>;
ref?: MutableRefObject<Nullable<HTMLDialogElement>>;
room?: Nullable<IRoom>;
isAdd?: boolean;
}
const RoomDialog = forwardRef<HTMLDialogElement, IRoomDialog>(
(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 (
<Dialog title={'Add room'} onClose={onClose} ref={dialogRef}>
<RoomForm
onClose={onClose!}
reload={reload!}
setReload={setReload!}
room={room}
isAdd={isAdd!}
/>
</Dialog>
);
}
);
export default RoomDialog;
@@ -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<SetStateAction<boolean>>;
}
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 (
<Table.Row>
<div>{id}</div>
<div>{name}</div>
<div>{formatCurrency(finalPrice)}</div>
<div>{statusText}</div>
<div>
<Modal>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<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>
);
};
import RoomRow from './RoomRow';
interface IRoomTable {
reload: boolean;
@@ -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<SetStateAction<boolean>>;
room?: Nullable<IRoom>;
isAdd?: boolean;
roomEdit?: Nullable<IRoom>;
}
const RoomForm = ({
onCloseModal,
reload,
setReload,
room,
isAdd,
roomEdit,
}: IRoomFormProp) => {
const formMethods = useForm<IRoom>();
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 = ({
</FormRow>
<Form.Action>
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid || isSubmitting}>
{
isAdd
? 'Add'
: 'Save'
!roomEdit
? 'Add'
: 'Save'
}
</FormBtn>
<FormBtn type="button" styled="secondary" onClick={onCloseModal}>
@@ -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<SetStateAction<boolean>>;
}
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 (
<Table.Row>
<div>{id}</div>
<div>{name}</div>
<div>{formatCurrency(finalPrice)}</div>
<div>{statusText}</div>
<div>
<Modal>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<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 roomEdit={room} setReload={setReload} reload={reload} />
</Modal.Window>
</Menus.Menu>
</Modal>
</div>
</Table.Row>
);
};
export default RoomRow;
+4 -4
View File
@@ -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 = () => {
<Title>List Room</Title>
<Modal>
<Modal.Open modalName="cabin-form">
<Modal.Open modalName="room-form">
<Button>Add room</Button>
</Modal.Open>
<Modal.Window name="cabin-form" title="Add form">
<Modal.Window name="room-form" title="Add form">
<RoomForm setReload={setReload} reload={reload} />
</Modal.Window>
</Modal>
@@ -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<SetStateAction<boolean>>;
ref: MutableRefObject<Nullable<HTMLDialogElement>>;
user: Nullable<IUser>;
isAdd: boolean;
}
const UserDialog = forwardRef<HTMLDialogElement, IUserDialog>((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 (
<Dialog title={'Add user'} onClose={onClose} ref={dialogRef}>
<UserForm
onClose={onClose!}
reload={reload!}
setReload={setReload!}
user={user}
isAdd={isAdd!}
/>
</Dialog>
);
});
export default UserDialog;
@@ -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<SetStateAction<boolean>>;
user: Nullable<IUser>;
isAdd: boolean;
user?: Nullable<IUser>;
}
const UserForm = ({
onClose,
reload,
setReload,
user,
isAdd,
}: IUserFormProp) => {
const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
const formMethods = useForm<IUser>();
const {
register,
handleSubmit,
reset,
formState: { errors, isDirty, isValid },
formState: { errors, isDirty, isValid, isSubmitting },
trigger,
} = formMethods;
const [rooms, setRooms] = useState<IRoom[]>([]);
@@ -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 = ({
</FormRow>
<Form.Action>
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid || isSubmitting}>
{
isAdd
!user
? 'Add'
: 'Save'
}
</FormBtn>
<FormBtn type="button" styled="secondary" onClick={onClose}>
<FormBtn type="button" styled="secondary" onClick={onCloseModal}>
Close
</FormBtn>
</Form.Action>
@@ -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<SetStateAction<Nullable<IUser>>>;
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
}
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 (
<Table.Row>
<div>{id}</div>
<div>{name}</div>
<div>{identifiedCode}</div>
<div>{phone}</div>
<div>{
roomId
? roomId
: 'None'
}</div>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<Menus.List id={id.toString()}>
<Menus.Button
icon={<RiEditBoxFill />}
onClick={() => handleEdit(user)}
>
Edit
</Menus.Button>
<Menus.Button icon={<IoExit />} onClick={() => handleCheckOut(user)}>
Check out
</Menus.Button>
</Menus.List>
</Menus.Menu>
</Table.Row>
);
};
interface IUserTable {
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
openFormDialog: () => void;
setUser?: Dispatch<SetStateAction<Nullable<IUser>>>;
}
const UserTable = ({
reload,
setReload,
openFormDialog,
setUser,
}: IUserTable) => {
const UserTable = ({ reload, setReload }: IUserTable) => {
const [users, setUsers] = useState<IUser[]>([]);
const [phoneSearch, setPhoneSearch] = useState('');
const [searchParams] = useSearchParams();
@@ -185,8 +98,6 @@ const UserTable = ({
key={user.id}
reload={reload}
setReload={setReload}
openFormDialog={openFormDialog}
setUser={setUser!}
/>
)}
/>
@@ -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<SetStateAction<boolean>>;
}
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 (
<Table.Row>
<div>{id}</div>
<div>{name}</div>
<div>{identifiedCode}</div>
<div>{phone}</div>
<div>{roomId ? roomId : 'None'}</div>
<div>
<Modal>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<Menus.List id={id.toString()}>
<Modal.Open modalName="edit">
<Menus.Button icon={<RiEditBoxFill />}>Edit</Menus.Button>
</Modal.Open>
<Menus.Button
icon={<IoExit />}
onClick={() => handleCheckOut(user)}
>
Check out
</Menus.Button>
</Menus.List>
<Modal.Window name="edit" title="Edit user">
<UserForm user={user} setReload={setReload} reload={reload} />
</Modal.Window>
</Menus.Menu>
</Modal>
</div>
</Table.Row>
);
};
export default UserRow;
+14 -33
View File
@@ -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<HTMLDialogElement>(null);
const [reload, setReload] = useState(true);
const [user, setUser] = useState<Nullable<IUser>>(null);
const [isAdd, setIsAdd] = useState(false);
const openFormDialog = (isAddForm: boolean = false) => {
setIsAdd(isAddForm);
dialogRef.current?.showModal();
};
const closeFormDialog = () => {
dialogRef.current?.close();
};
return (
<>
<StyledUser>
<Direction type="horizontal">
<Title>List User</Title>
<Button onClick={() => openFormDialog(true)}>Add user</Button>
<Modal>
<Modal.Open modalName="user-form">
<Button>Add user</Button>
</Modal.Open>
<Modal.Window name="user-form" title="Add form">
<UserForm setReload={setReload} reload={reload} />
</Modal.Window>
</Modal>
</Direction>
<UserTable
reload={reload}
setReload={setReload}
openFormDialog={() => openFormDialog()}
setUser={setUser}
/>
<UserTable reload={reload} setReload={setReload} />
</StyledUser>
<UserDialog
onClose={closeFormDialog}
ref={dialogRef}
setReload={setReload}
reload={reload}
user={user}
isAdd={isAdd}
/>
</>
);
};