Merge pull request #29 from Nez27/feat/update-room-and-user-page

Update room, user pages and service
This commit is contained in:
Loi Phan
2023-11-22 21:54:33 +07:00
committed by GitHub
11 changed files with 179 additions and 446 deletions
+11 -2
View File
@@ -1,6 +1,7 @@
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import isPropValid from '@emotion/is-prop-valid'; import isPropValid from '@emotion/is-prop-valid';
import { StyleSheetManager } from 'styled-components'; import { StyleSheetManager } from 'styled-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// Components // Components
import AppLayout from './components/AppLayout'; import AppLayout from './components/AppLayout';
@@ -13,9 +14,17 @@ import NotFound from './pages/NotFound';
import * as PATH from './constants/path'; import * as PATH from './constants/path';
import Toast from './components/Toast'; import Toast from './components/Toast';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
}
}
});
function App() { function App() {
return ( return (
<> <QueryClientProvider client={queryClient}>
<StyleSheetManager shouldForwardProp={shouldForwardProp}> <StyleSheetManager shouldForwardProp={shouldForwardProp}>
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
@@ -31,7 +40,7 @@ function App() {
</StyleSheetManager> </StyleSheetManager>
<Toast /> <Toast />
</> </QueryClientProvider>
); );
} }
+14 -56
View File
@@ -1,5 +1,4 @@
import { useSearchParams } from 'react-router-dom'; import { useCallback } from 'react';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
// Components // Components
import Menus from '@component/Menus'; import Menus from '@component/Menus';
@@ -14,7 +13,6 @@ import RoomRow from './RoomRow';
import { IRoom } from '@type/rooms'; import { IRoom } from '@type/rooms';
// Constants // Constants
import { useFetch } from '@hook/useFetch';
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons'; import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons';
// Styled // Styled
@@ -22,45 +20,18 @@ import { StyledOperationTable } from './styled';
import Direction from '@commonStyle/Direction'; import Direction from '@commonStyle/Direction';
import Spinner from '@commonStyle/Spinner'; import Spinner from '@commonStyle/Spinner';
// Hooks
import { useRooms } from '@hook/rooms/useRooms';
interface IRoomTable { const RoomTable = () => {
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
}
const RoomTable = ({ reload, setReload }: IRoomTable) => {
const columnName = ['Id', 'Name', 'Price', 'Status']; const columnName = ['Id', 'Name', 'Price', 'Status'];
const [nameSearch, setNameSearch] = useState(''); const { isLoading, rooms } = useRooms();
const [searchParams] = useSearchParams();
const [rooms, setRooms] = useState<IRoom[]>([]);
const sortByValue = searchParams.get('sortBy')
? searchParams.get('sortBy')!
: '';
const orderByValue = searchParams.get('orderBy')
? searchParams.get('orderBy')!
: '';
const { data, isPending, errorFetchMsg } = useFetch( const renderRoomRow = useCallback(
'rooms', (room: IRoom) => <RoomRow room={room} key={room.id} />,
'name', []
nameSearch,
sortByValue,
orderByValue,
reload
); );
useEffect(() => {
if (data) {
setRooms(data);
} else {
setRooms([]);
}
if (errorFetchMsg) {
console.error(errorFetchMsg);
}
}, [data, errorFetchMsg]);
return ( return (
<> <>
<Direction> <Direction>
@@ -68,33 +39,20 @@ const RoomTable = ({ reload, setReload }: IRoomTable) => {
<OrderBy options={ORDERBY_OPTIONS} /> <OrderBy options={ORDERBY_OPTIONS} />
<SortBy options={ROOM_PAGE.SORTBY_OPTIONS} /> <SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />
<Search <Search setPlaceHolder="Search by name..." />
setValueSearch={setNameSearch}
setPlaceHolder="Search by name..."
/>
</StyledOperationTable> </StyledOperationTable>
{isPending && <Spinner />} {isLoading && <Spinner />}
{rooms.length ? ( {rooms && rooms.length ? (
<Menus> <Menus>
<Table columns="10% 40% 20% 20% 5%"> <Table columns="10% 40% 20% 20% 5%">
<Table.Header headerColumn={columnName}/> <Table.Header headerColumn={columnName} />
<Table.Body<IRoom> <Table.Body<IRoom> data={rooms} render={renderRoomRow} />
data={rooms}
render={(room: IRoom) => (
<RoomRow
room={room}
key={room.id}
reload={reload}
setReload={setReload}
/>
)}
/>
</Table> </Table>
</Menus> </Menus>
) : ( ) : (
!isPending && <Message>No data to show here!</Message> !isLoading && <Message>No data to show here!</Message>
)} )}
</Direction> </Direction>
</> </>
+40 -77
View File
@@ -1,40 +1,30 @@
import { Dispatch, SetStateAction, useEffect } from 'react';
import toast from 'react-hot-toast';
import styled from 'styled-components'; import styled from 'styled-components';
// Hooks
import { useForm } from 'react-hook-form';
// Styled // Styled
import Button from '@commonStyle/Button.ts'; import Button from '@commonStyle/Button.ts';
import Input from '@commonStyle/Input.ts'; import Input from '@commonStyle/Input.ts';
// Types // Types
import { Nullable } from '@type/common.ts';
import { IRoom } from '@type/rooms.ts'; import { IRoom } from '@type/rooms.ts';
// Constants // Constants
import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts';
import { import {
INVALID_DISCOUNT,
INVALID_FIELD, INVALID_FIELD,
REQUIRED_FIELD_ERROR, REQUIRED_FIELD_ERROR,
} from '@constant/formValidateMessage.ts'; } from '@constant/formValidateMessage.ts';
import { INIT_VALUE_ROOM_FORM, REGEX } from '../../constants/commons.ts'; import { REGEX } from '../../constants/commons.ts';
// Helpers // Helpers
import { import { isValidRegex, isValidString } from '@helper/validators.ts';
isValidDiscount,
isValidRegex,
isValidString,
} from '@helper/validators.ts';
// Components // Components
import FormRow from '@component/LabelControl/index.tsx'; import FormRow from '@component/LabelControl/index.tsx';
import Form from '@component/Form/index.tsx'; import Form from '@component/Form/index.tsx';
// Services // Hooks
import { addRoom, updateRoom } from '@service/roomServices.ts'; import { useForm } from 'react-hook-form';
import { useCreateRoom } from '@hook/rooms/useCreateRoom.ts';
import { useUpdateRoom } from '@hook/rooms/useUpdateRoom.ts';
const FormBtn = styled(Button)` const FormBtn = styled(Button)`
width: 100%; width: 100%;
@@ -48,65 +38,51 @@ const FormBtn = styled(Button)`
interface IRoomFormProp { interface IRoomFormProp {
onCloseModal?: () => void; onCloseModal?: () => void;
reload?: boolean; room?: IRoom;
setReload?: Dispatch<SetStateAction<boolean>>;
roomEdit?: Nullable<IRoom>;
} }
const RoomForm = ({ const RoomForm = ({ onCloseModal, room }: IRoomFormProp) => {
onCloseModal, const { isCreating, createRoom } = useCreateRoom();
reload, const { isUpdating, updateRoom } = useUpdateRoom();
setReload, const isLoading = isCreating || isUpdating;
roomEdit, const { id: editId, ...editValues } = { ...room };
}: IRoomFormProp) => { const formMethods = useForm<IRoom>({
const formMethods = useForm<IRoom>(); defaultValues: editId
? editValues
: {},
});
const { const {
register, register,
handleSubmit, handleSubmit,
reset, reset,
formState: { errors, isDirty, isValid, isSubmitting }, formState: { errors, isDirty, isValid },
trigger, trigger,
} = formMethods; } = formMethods;
useEffect(() => {
if (roomEdit) {
reset(roomEdit);
} else {
reset(INIT_VALUE_ROOM_FORM);
}
}, [roomEdit, reset]);
// Submit form // Submit form
const onSubmit = async (room: IRoom) => { const onSubmit = async (newRoom: IRoom) => {
// Calculate final price if (!editId) {
room.finalPrice = room.price - (room.price * room.discount) / 100;
if (!roomEdit) {
// Add request // Add request
const response = await addRoom(room); createRoom(newRoom, {
onSuccess: () => {
if (response) { reset();
toast.success(ADD_SUCCESS); onCloseModal?.();
} },
});
} else { } else {
// Edit request // Edit request
const response = await updateRoom(room); newRoom.id = editId!;
updateRoom(newRoom, {
if (response) { onSuccess: () => {
toast.success(EDIT_SUCCESS); reset();
} onCloseModal?.();
},
});
} }
// Reload table data
setReload!(!reload);
reset();
onCloseModal!();
}; };
return ( return (
<Form onSubmit={handleSubmit(onSubmit)}> <Form onSubmit={handleSubmit(onSubmit)}>
<Input type="hidden" id="id" {...register('id')} />
<FormRow label="Name" error={errors?.name?.message}> <FormRow label="Name" error={errors?.name?.message}>
<Input <Input
type="text" type="text"
@@ -114,7 +90,8 @@ const RoomForm = ({
{...register('name', { {...register('name', {
required: REQUIRED_FIELD_ERROR, required: REQUIRED_FIELD_ERROR,
validate: { validate: {
checkValidRoomName: (value) => isValidString(value) || INVALID_FIELD, checkValidRoomName: (value) =>
isValidString(value) || INVALID_FIELD,
}, },
onChange: () => trigger('name'), onChange: () => trigger('name'),
})} })}
@@ -130,41 +107,27 @@ const RoomForm = ({
required: REQUIRED_FIELD_ERROR, required: REQUIRED_FIELD_ERROR,
validate: { validate: {
checkPrice: (v) => checkPrice: (v) =>
isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || INVALID_FIELD, isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) ||
INVALID_FIELD,
}, },
onChange: () => trigger('price'), onChange: () => trigger('price'),
})} })}
/> />
</FormRow> </FormRow>
<FormRow label="discount" error={errors?.discount?.message}>
<Input
type="text"
id="phone"
{...register('discount', {
valueAsNumber: true,
required: REQUIRED_FIELD_ERROR,
validate: {
checkDiscount: (v) => isValidDiscount(v) || INVALID_DISCOUNT,
},
onChange: () => trigger('discount'),
})}
/>
</FormRow>
<Form.Action> <Form.Action>
<FormBtn <FormBtn
type="submit" type="submit"
name="submit" name="submit"
disabled={!isDirty || !isValid || isSubmitting} disabled={!isDirty || !isValid || isLoading}
> >
{ {
!roomEdit !editId
? 'Add' ? 'Add'
: 'Save' : 'Save'
} }
</FormBtn> </FormBtn>
<FormBtn type="button" styled="secondary" onClick={onCloseModal}> <FormBtn type="button" variations="secondary" onClick={onCloseModal}>
Close Close
</FormBtn> </FormBtn>
</Form.Action> </Form.Action>
+45 -42
View File
@@ -1,13 +1,6 @@
import { Dispatch, SetStateAction } from 'react'; import { useCallback, useMemo } from 'react';
import toast from 'react-hot-toast';
// Constants
import { ROOM_PATH } from '@constant/path';
import { STATUS_CODE } from '@constant/responseStatus';
import { CONFIRM_DELETE, DELETE_SUCCESS } from '@constant/messages';
// Helpers // Helpers
import { sendRequest } from '@helper/sendRequest';
import { formatCurrency } from '@helper/helper'; import { formatCurrency } from '@helper/helper';
// Types // Types
@@ -20,38 +13,47 @@ import Table from '@component/Table';
import Menus from '@component/Menus'; import Menus from '@component/Menus';
import { RiEditBoxFill } from 'react-icons/ri'; import { RiEditBoxFill } from 'react-icons/ri';
import { HiTrash } from 'react-icons/hi'; import { HiTrash } from 'react-icons/hi';
import ConfirmMessage from '@component/ConfirmMessage';
// Hooks
import { useDeleteRoom } from '@hook/rooms/useDeleteRoom';
// Constants
import { FORM } from '@constant/commons';
interface IRoomRow { interface IRoomRow {
room: IRoom; room: IRoom;
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
} }
const RoomRow = ({ room, reload, setReload }: IRoomRow) => { const RoomRow = ({ room }: IRoomRow) => {
const handleDelete = async (room: IRoom) => { const { id, name, price, status } = room;
if (confirm(CONFIRM_DELETE)) { const { isDeleting, deleteRoom } = useDeleteRoom();
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 const statusText = status
? 'Unavailable' ? 'Unavailable'
: 'Available'; : 'Available';
const formattedPrice = useMemo(() => formatCurrency(price), [price]);
const renderEditBtn = useCallback(
(onCloseModal: () => void) => (
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}>
Edit
</Menus.Button>
),
[]
);
const renderDeleteBtn = useCallback(
(onCloseModal: () => void) => (
<Menus.Button icon={<HiTrash />} onClick={onCloseModal}>
Delete
</Menus.Button>
),
[]
);
return ( return (
<Table.Row> <Table.Row>
<div>{id}</div> <div>{id}</div>
<div>{name}</div> <div>{name}</div>
<div>{formatCurrency(finalPrice)}</div> <div>{formattedPrice}</div>
<div>{statusText}</div> <div>{statusText}</div>
<div> <div>
@@ -61,24 +63,25 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => {
<Menus.List id={id.toString()}> <Menus.List id={id.toString()}>
<Modal.Open <Modal.Open
modalName="edit" modalName={FORM.EDIT}
renderChildren={(onCloseModal) => ( renderChildren={renderEditBtn}
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}> />
Edit <Modal.Open
</Menus.Button> modalName={FORM.DELETE}
)} renderChildren={renderDeleteBtn}
/> />
<Menus.Button
icon={<HiTrash />}
onClick={() => handleDelete(room)}
>
Delete
</Menus.Button>
</Menus.List> </Menus.List>
<Modal.Window name="edit" title="Edit Room"> <Modal.Window name={FORM.EDIT} title="Edit Room">
<RoomForm roomEdit={room} setReload={setReload} reload={reload} /> <RoomForm room={room} />
</Modal.Window>
<Modal.Window name={FORM.DELETE} title="Delete Room">
<ConfirmMessage
disabled={isDeleting}
message={`Are you sure to delete ${name}?`}
onConfirm={() => deleteRoom(id)}
/>
</Modal.Window> </Modal.Window>
</Menus.Menu> </Menus.Menu>
</Modal> </Modal>
+3 -9
View File
@@ -1,20 +1,14 @@
import { useState } from 'react';
// Components // Components
import RoomTable from './RomTable'; import RoomTable from './RomTable';
import RoomForm from './RoomForm'; import RoomForm from './RoomForm';
import Modal from '@component/Modal';
// Styled // Styled
import { StyledRoom, Title } from './styled'; import { StyledRoom, Title } from './styled';
import Direction from '@commonStyle/Direction.ts'; import Direction from '@commonStyle/Direction.ts';
import Button from '@commonStyle/Button'; import Button from '@commonStyle/Button';
// Types
import Modal from '@component/Modal';
const Room = () => { const Room = () => {
const [reload, setReload] = useState(true);
return ( return (
<> <>
<StyledRoom> <StyledRoom>
@@ -29,12 +23,12 @@ const Room = () => {
)} )}
/> />
<Modal.Window name="room-form" title="Add form"> <Modal.Window name="room-form" title="Add form">
<RoomForm setReload={setReload} reload={reload} /> <RoomForm />
</Modal.Window> </Modal.Window>
</Modal> </Modal>
</Direction> </Direction>
<RoomTable reload={reload} setReload={setReload} /> <RoomTable />
</StyledRoom> </StyledRoom>
</> </>
); );
+39 -144
View File
@@ -1,14 +1,7 @@
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from 'react';
import toast from 'react-hot-toast';
// Hooks // Hooks
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useCreateUser } from '@hook/users/useCreateUser.ts';
import { useUpdateUser } from '@hook/users/useUpdateUser.ts';
// Styled // Styled
import Input from '@commonStyle/Input.ts'; import Input from '@commonStyle/Input.ts';
@@ -16,139 +9,73 @@ import Input from '@commonStyle/Input.ts';
// Components // Components
import Form from '@component/Form/index.tsx'; import Form from '@component/Form/index.tsx';
import FormRow from '@component/LabelControl/index.tsx'; import FormRow from '@component/LabelControl/index.tsx';
import Select, { ISelectOptions } from '@component/Select/index.tsx';
// Helpers // Helpers
import { isEmptyObj, isValidRegex } from '@helper/validators.ts'; import { isValidRegex } from '@helper/validators.ts';
// Constants
import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts';
import { import {
INVALID_FIELD, INVALID_FIELD,
INVALID_PHONE, INVALID_PHONE,
REQUIRED_FIELD_ERROR, REQUIRED_FIELD_ERROR,
} from '@constant/formValidateMessage.ts'; } from '@constant/formValidateMessage.ts';
import { INIT_VALUE_USER_FORM, REGEX } from '@constant/commons.ts'; import { REGEX } from '@constant/commons.ts';
// Styled // Styled
import { FormBtn } from './styled.ts'; import { FormBtn } from './styled.ts';
// Services
import { getAllRoom, updateRoomStatus } from '@service/roomServices.ts';
import { createUser, updateUser } from '@service/userServices.ts';
// Types // Types
import { Nullable } from '@type/common.ts';
import { IUser } from '@type/users.ts'; import { IUser } from '@type/users.ts';
import { IRoom } from '@type/rooms.ts';
interface IUserFormProp { interface IUserFormProp {
onCloseModal?: () => void; onCloseModal?: () => void;
reload: boolean; user?: IUser;
setReload: Dispatch<SetStateAction<boolean>>;
user?: Nullable<IUser>;
} }
const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => { const UserForm = ({ onCloseModal, user }: IUserFormProp) => {
const formMethods = useForm<IUser>(); const { isCreating, createUser } = useCreateUser();
const { isUpdating, updateUser } = useUpdateUser();
const isLoading = isCreating || isUpdating;
const {id: editId, ...editValues} = {...user};
const formMethods = useForm<IUser>({
defaultValues: editId
? editValues
: {}
});
const { const {
register, register,
handleSubmit, handleSubmit,
reset, reset,
formState: { errors, isDirty, isValid, isSubmitting }, formState: { errors, isDirty, isValid },
trigger, trigger,
} = formMethods; } = formMethods;
const [rooms, setRooms] = useState<IRoom[]>([]);
const [options, setOptions] = useState<ISelectOptions[]>();
// Init value when edit form and load options
useEffect(() => {
const load = async () => {
const options: ISelectOptions[] = [];
const tempUser = !user ? { roomId: 0 } : { ...user };
// Load and set default options room
if (rooms.length > 0) {
// Init first options
options.push({
label: '---Select---',
value: '0',
});
rooms.forEach((item) => {
if (!item.status || tempUser?.roomId === item.id)
options.push({
label: item.name!,
value: item.id!.toString(),
});
});
}
if (options.length > 0) {
setOptions(options);
}
if (!isEmptyObj(tempUser)) {
// Init value
reset(tempUser);
} else {
reset(INIT_VALUE_USER_FORM);
}
};
load();
}, [reset, user, rooms]);
// Submit form // Submit form
const onSubmit = useCallback( const onSubmit =
async (newUser: IUser) => { (newUser: IUser) => {
if (!user) { if (!user) {
// Add request // Add request
const response = await createUser(newUser); createUser(newUser, {
onSuccess: () => {
if (response) { reset();
toast.success(ADD_SUCCESS); onCloseModal?.();
} },
});
// Update room status
updateRoomStatus(newUser.roomId, true);
} else { } else {
// Edit request // Edit request
const response = await updateUser(newUser); newUser.id = editId!;
updateUser(newUser, {
if (response) { onSuccess: () => {
toast.success(EDIT_SUCCESS); reset();
} onCloseModal?.();
}
// Update room status });
updateRoomStatus(user!.roomId, true, newUser.roomId);
}
// Reload table data
setReload(!reload);
reset();
onCloseModal!();
},
[onCloseModal, reload, reset, setReload, user]
);
// Load all rooms
useEffect(() => {
const loadRoom = async () => {
const rooms = await getAllRoom();
if (rooms) {
setRooms(rooms);
} }
}; };
loadRoom();
}, [onSubmit]);
return ( return (
<FormProvider {...formMethods}> <FormProvider {...formMethods}>
<Form onSubmit={handleSubmit(onSubmit)}> <Form onSubmit={handleSubmit(onSubmit)}>
<Input type="hidden" id="id" {...register('id')} />
<FormRow label="Full Name" error={errors?.name?.message}> <FormRow label="Full Name" error={errors?.name?.message}>
<Input <Input
type="text" type="text"
@@ -164,25 +91,6 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
/> />
</FormRow> </FormRow>
<FormRow
label="Identified Code"
error={errors?.identifiedCode?.message}
>
<Input
type="text"
id="identifiedCode"
{...register('identifiedCode', {
required: REQUIRED_FIELD_ERROR,
validate: {
checkIdentifiedCode: (v) =>
isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) ||
INVALID_FIELD,
},
onChange: () => trigger('identifiedCode'),
})}
/>
</FormRow>
<FormRow label="Phone" error={errors?.phone?.message}> <FormRow label="Phone" error={errors?.phone?.message}>
<Input <Input
type="text" type="text"
@@ -198,32 +106,19 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
/> />
</FormRow> </FormRow>
<FormRow label="Room">
{options && options.length > 1 ? (
<Select
id="roomId"
options={options!}
ariaLabel="RoomId"
optionsConfigForm={{
valueAsNumber: true,
onChange: () => trigger('roomId'),
validate: (v) => v !== 0,
}}
/>
) : (
<p>No room available!</p>
)}
</FormRow>
<Form.Action> <Form.Action>
<FormBtn <FormBtn
type="submit" type="submit"
name="submit" name="submit"
disabled={!isDirty || !isValid || isSubmitting} disabled={!isDirty || !isValid || isLoading}
> >
{!user ? 'Add' : 'Save'} {
!user
? 'Add'
: 'Save'
}
</FormBtn> </FormBtn>
<FormBtn type="button" styled="secondary" onClick={onCloseModal}> <FormBtn type="button" variations="secondary" onClick={onCloseModal}>
Close Close
</FormBtn> </FormBtn>
</Form.Action> </Form.Action>
+3 -51
View File
@@ -1,68 +1,26 @@
import toast from 'react-hot-toast';
import { Dispatch, SetStateAction } from 'react';
// Components // Components
import { RiEditBoxFill } from 'react-icons/ri'; import { RiEditBoxFill } from 'react-icons/ri';
import { IoExit } from 'react-icons/io5';
import Modal from '@component/Modal'; import Modal from '@component/Modal';
import Table from '@component/Table'; import Table from '@component/Table';
import Menus from '@component/Menus'; import Menus from '@component/Menus';
import UserForm from './UserForm'; import UserForm from './UserForm';
// Constants
import { STATUS_CODE } from '@constant/responseStatus';
import { CONFIRM_MESSAGE, DENIED_ACTION } from '@constant/messages';
// Services
import { updateRoomStatus } from '@service/roomServices';
import { checkOutUser } from '@service/userServices';
// Types // Types
import { IUser } from '@type/users'; import { IUser } from '@type/users';
interface IUserRow { interface IUserRow {
user: IUser; user: IUser;
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
} }
const UserRow = ({ user, reload, setReload }: IUserRow) => { const UserRow = ({ user }: IUserRow) => {
const handleCheckOut = async (user: IUser) => { const { id, name, phone } = user;
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 ( return (
<Table.Row> <Table.Row>
<div>{id}</div> <div>{id}</div>
<div>{name}</div> <div>{name}</div>
<div>{identifiedCode}</div>
<div>{phone}</div> <div>{phone}</div>
<div>
{
roomId
? roomId
: 'None'
}
</div>
<div> <div>
<Modal> <Modal>
<Menus.Menu> <Menus.Menu>
@@ -77,16 +35,10 @@ const UserRow = ({ user, reload, setReload }: IUserRow) => {
</Menus.Button> </Menus.Button>
)} )}
/> />
<Menus.Button
icon={<IoExit />}
onClick={() => handleCheckOut(user)}
>
Check out
</Menus.Button>
</Menus.List> </Menus.List>
<Modal.Window name="edit" title="Edit user"> <Modal.Window name="edit" title="Edit user">
<UserForm user={user} setReload={setReload} reload={reload} /> <UserForm user={user} />
</Modal.Window> </Modal.Window>
</Menus.Menu> </Menus.Menu>
</Modal> </Modal>
+20 -58
View File
@@ -1,5 +1,4 @@
import { useSearchParams } from 'react-router-dom'; import { useCallback } from 'react';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
// Components // Components
import Menus from '@component/Menus'; import Menus from '@component/Menus';
@@ -13,9 +12,6 @@ import UserRow from './UserRow';
// Types // Types
import { IUser } from '@type/users'; import { IUser } from '@type/users';
// Hooks
import { useFetch } from '@hook/useFetch';
// Constants // Constants
import { ORDERBY_OPTIONS, USER_PAGE } from '@constant/commons'; import { ORDERBY_OPTIONS, USER_PAGE } from '@constant/commons';
@@ -24,44 +20,23 @@ import Direction from '@commonStyle/Direction';
import { StyledOperationTable } from './styled'; import { StyledOperationTable } from './styled';
import Spinner from '@commonStyle/Spinner'; import Spinner from '@commonStyle/Spinner';
interface IUserTable { // Hooks
reload: boolean; import { useUsers } from '@hook/users/useUsers';
setReload: Dispatch<SetStateAction<boolean>>;
}
const UserTable = ({ reload, setReload }: IUserTable) => { const UserTable = () => {
const columnName = ['Id', 'Name', 'Identified Code', 'Phone', 'Room Id']; const columnName = ['Id', 'Name', 'Phone'];
const [users, setUsers] = useState<IUser[]>([]); const { isLoading, users } = useUsers();
const [phoneSearch, setPhoneSearch] = useState('');
const [searchParams] = useSearchParams();
const sortByValue = searchParams.get('sortBy')
? searchParams.get('sortBy')!
: '';
const orderByValue = searchParams.get('orderBy')
? searchParams.get('orderBy')!
: '';
const { data, isPending, errorFetchMsg } = useFetch( const renderUserRow = useCallback(
'users', (user: IUser) => (
'phone', <UserRow
phoneSearch, user={user}
sortByValue, key={user.id}
orderByValue, />
reload ),
[]
); );
useEffect(() => {
if (data) {
setUsers(data);
} else {
setUsers([]);
}
if (errorFetchMsg) {
console.error(errorFetchMsg);
}
}, [data, errorFetchMsg, setUsers]);
return ( return (
<> <>
<Direction> <Direction>
@@ -69,33 +44,20 @@ const UserTable = ({ reload, setReload }: IUserTable) => {
<OrderBy options={ORDERBY_OPTIONS} /> <OrderBy options={ORDERBY_OPTIONS} />
<SortBy options={USER_PAGE.SORTBY_OPTIONS} /> <SortBy options={USER_PAGE.SORTBY_OPTIONS} />
<Search <Search setPlaceHolder="Search by phone..." />
setValueSearch={setPhoneSearch}
setPlaceHolder="Search by phone..."
/>
</StyledOperationTable> </StyledOperationTable>
{isPending && <Spinner />} {isLoading && <Spinner />}
{users.length ? ( {users && users.length ? (
<Menus> <Menus>
<Table columns="10% 30% 20% 20% 10% 5%"> <Table columns="10% 40% 35% 15%">
<Table.Header headerColumn={columnName} /> <Table.Header headerColumn={columnName} />
<Table.Body<IUser> <Table.Body<IUser> data={users} render={renderUserRow} />
data={users}
render={(user: IUser) => (
<UserRow
user={user}
key={user.id}
reload={reload}
setReload={setReload}
/>
)}
/>
</Table> </Table>
</Menus> </Menus>
) : ( ) : (
!isPending && <Message>No data to show here!</Message> !isLoading && <Message>No data to show here!</Message>
)} )}
</Direction> </Direction>
</> </>
+2 -5
View File
@@ -1,5 +1,3 @@
import { useState } from 'react';
// Components // Components
import UserTable from './UserTable'; import UserTable from './UserTable';
@@ -17,7 +15,6 @@ import { FORM } from '@constant/commons';
const User = () => { const User = () => {
const TITLE = 'Add user'; const TITLE = 'Add user';
const [reload, setReload] = useState(true);
return ( return (
<> <>
@@ -33,12 +30,12 @@ const User = () => {
)} )}
/> />
<Modal.Window name={FORM.USER} title={TITLE}> <Modal.Window name={FORM.USER} title={TITLE}>
<UserForm setReload={setReload} reload={reload} /> <UserForm />
</Modal.Window> </Modal.Window>
</Modal> </Modal>
</Direction> </Direction>
<UserTable reload={reload} setReload={setReload} /> <UserTable />
</StyledUser> </StyledUser>
</> </>
); );
@@ -24,7 +24,7 @@ const getAllRooms = async (
.from(ROOMS_TABLE) .from(ROOMS_TABLE)
.select('*') .select('*')
.order(sortBy, { ascending: orderBy === 'asc' }) .order(sortBy, { ascending: orderBy === 'asc' })
.like('name', `%${roomName}%`); .ilike('name', `%${roomName}%`);
if (error) { if (error) {
console.error(error.message); console.error(error.message);
@@ -54,7 +54,7 @@ const getAllUsers = async (
.from(USERS_TABLE) .from(USERS_TABLE)
.select('*') .select('*')
.order(sortBy, { ascending: orderBy === 'asc' }) .order(sortBy, { ascending: orderBy === 'asc' })
.like('phone', `%${phoneSearch}%`); .ilike('phone', `%${phoneSearch}%`);
if (error) { if (error) {
console.error(error.message); console.error(error.message);