mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 05:32:07 +00:00
Fix sort, order, search function
This commit is contained in:
@@ -57,7 +57,7 @@ const AppLayout = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UserRoomAvailableContext.Provider value={{usersAvailable, roomsAvailable}}>
|
||||
<UserRoomAvailableContext.Provider value={{usersAvailable, roomsAvailable, dispatch}}>
|
||||
<StyledAppLayout>
|
||||
<Header accountName={account?.user_metadata.fullName} />
|
||||
<Sidebar heading="Hotel Management" />
|
||||
|
||||
@@ -17,7 +17,12 @@ interface ISearch {
|
||||
const Search = ({ setPlaceHolder }: ISearch) => {
|
||||
const field = 'search';
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [query, setQuery] = useState<Nullable<string>>(null);
|
||||
const [query, setQuery] = useState<Nullable<string>>(
|
||||
searchParams.get(field)
|
||||
? searchParams.get(field)
|
||||
: '',
|
||||
);
|
||||
|
||||
const debounceValue = useDebounce<Nullable<string>>(query, 700);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -32,6 +37,7 @@ const Search = ({ setPlaceHolder }: ISearch) => {
|
||||
<StyledSearch
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={setPlaceHolder}
|
||||
value={query!}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
// Components
|
||||
import Table from '.';
|
||||
|
||||
// Types
|
||||
import { IUser } from '@src/types/user';
|
||||
|
||||
interface ITableRow {
|
||||
user: IUser;
|
||||
}
|
||||
|
||||
describe('Table', () => {
|
||||
const columnName = ['Column 1', 'Column 2'];
|
||||
const tempUser: IUser[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Nezumi',
|
||||
phone: '123',
|
||||
isBooked: true,
|
||||
isDelete: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Loi Phan',
|
||||
phone: '453',
|
||||
isBooked: false,
|
||||
isDelete: true,
|
||||
},
|
||||
];
|
||||
const TableRow = ({ user }: ITableRow) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
phone,
|
||||
isBooked
|
||||
} = user;
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<div>{id}</div>
|
||||
<div>{name}</div>
|
||||
<div>{phone}</div>
|
||||
<div>{isBooked}</div>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
const renderRow = (user: IUser) => <TableRow user={user} key={user.id} />;
|
||||
|
||||
const wrapper = render(
|
||||
<Table columns="10% 35% 30% 15% 10%">
|
||||
<Table.Header headerColumn={columnName} />
|
||||
<Table.Body<IUser> data={tempUser} render={renderRow} />
|
||||
</Table>
|
||||
);
|
||||
|
||||
test('Should render correctly', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,29 +1,38 @@
|
||||
// Types
|
||||
import { ColumnProps } from '@src/types/common';
|
||||
import {ColumnProps} from '@src/types/common';
|
||||
|
||||
// Styled
|
||||
import {
|
||||
StyledBody, StyledFooter, StyledHeader, StyledRow, StyledTable
|
||||
StyledBody,
|
||||
StyledFooter,
|
||||
StyledHeader,
|
||||
StyledRow,
|
||||
StyledTable,
|
||||
} from './styled';
|
||||
|
||||
// Components
|
||||
import Pagination from '../Pagination';
|
||||
import { TbArrowNarrowRight } from 'react-icons/tb';
|
||||
import { StyledTableOption } from '@src/pages/Room/styled.ts';
|
||||
import {TbArrowNarrowRight} from 'react-icons/tb';
|
||||
import {StyledTableOption} from '@src/pages/Room/styled.ts';
|
||||
import OrderBy from '@src/components/OrderBy';
|
||||
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@src/constants/commons.ts';
|
||||
import {ORDERBY_OPTIONS} from '@src/constants/commons.ts';
|
||||
import SortBy from '@src/components/SortBy';
|
||||
import Search from '@src/components/Search';
|
||||
import Message from '@src/components/Message';
|
||||
import Direction from '@src/commons/styles/Direction.ts';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import {Dispatch, SetStateAction} from 'react';
|
||||
|
||||
const parseWidthString = (columnsWidth: number[]): string => {
|
||||
let result: string = '';
|
||||
|
||||
const totalWidth = columnsWidth.reduce((partialSum, a) => partialSum + a, 0);
|
||||
const totalWidth = columnsWidth.reduce((
|
||||
partialSum,
|
||||
a
|
||||
) => partialSum + a, 0);
|
||||
columnsWidth.forEach((width) => {
|
||||
result += Math.round((width / totalWidth) * 100).toString() + '% ';
|
||||
result += Math.round((
|
||||
width / totalWidth
|
||||
) * 100)
|
||||
.toString() + '% ';
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -34,13 +43,16 @@ type Props<T> = {
|
||||
rows: T[];
|
||||
stateSelected: {
|
||||
itemSelected: T | undefined;
|
||||
setItemSelected: Dispatch<SetStateAction<T | undefined>>
|
||||
setItemSelected: Dispatch<SetStateAction<T | undefined>>;
|
||||
};
|
||||
count?: number | null;
|
||||
onRowClick?: (rowData: T) => void;
|
||||
enabledOrder?: boolean;
|
||||
enabledSort?: boolean;
|
||||
enabledSearch?: boolean;
|
||||
sortBy?: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
searchPlaceHolder?: string;
|
||||
};
|
||||
|
||||
const Table = <T, >({
|
||||
@@ -49,18 +61,24 @@ const Table = <T, >({
|
||||
count,
|
||||
onRowClick,
|
||||
stateSelected,
|
||||
enabledSort = false,
|
||||
enabledSearch = false,
|
||||
enabledOrder = false
|
||||
sortBy,
|
||||
searchPlaceHolder,
|
||||
enabledOrder = false,
|
||||
}: Props<T>) => {
|
||||
const width = parseWidthString(columns.map((item) => item.width));
|
||||
const { itemSelected, setItemSelected } = stateSelected;
|
||||
const {itemSelected, setItemSelected} = stateSelected;
|
||||
|
||||
const headers = columns.map((column, index) => {
|
||||
const headers = columns.map((
|
||||
column,
|
||||
index
|
||||
) => {
|
||||
return <div key={`headCell-${index}`}>{column.title}</div>;
|
||||
});
|
||||
|
||||
const renderRows = rows.map((row, rowIndex) => {
|
||||
const renderRows = rows.map((
|
||||
row,
|
||||
rowIndex
|
||||
) => {
|
||||
const handleRowClick = () => {
|
||||
if (onRowClick) {
|
||||
onRowClick(row);
|
||||
@@ -73,45 +91,61 @@ const Table = <T, >({
|
||||
key={`row-${rowIndex}`}
|
||||
width={width}
|
||||
onDoubleClick={handleRowClick}
|
||||
className={JSON.stringify(itemSelected) === JSON.stringify(row)
|
||||
? 'selected'
|
||||
: ''
|
||||
}>
|
||||
{columns.map((column, columnIndex) => {
|
||||
className={
|
||||
JSON.stringify(itemSelected) === JSON.stringify(row)
|
||||
? 'selected'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{columns.map((
|
||||
column,
|
||||
columnIndex
|
||||
) => {
|
||||
const value = row[column.key as keyof typeof row] as string;
|
||||
|
||||
if (column.isDateValue) {
|
||||
return (<div
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
key={`cell-${columnIndex}`}
|
||||
>
|
||||
{value[0]} <TbArrowNarrowRight />
|
||||
{value[1]}
|
||||
</div>);
|
||||
return (
|
||||
<div
|
||||
style={{display: 'flex', alignItems: 'center'}}
|
||||
key={`cell-${columnIndex}`}
|
||||
>
|
||||
{value[0]} <TbArrowNarrowRight/>
|
||||
{value[1]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div key={`cell-${columnIndex}`}>{value}</div>;
|
||||
})}
|
||||
</StyledRow>);
|
||||
</StyledRow>
|
||||
);
|
||||
});
|
||||
|
||||
return (<Direction>
|
||||
<StyledTableOption>
|
||||
{enabledOrder && <OrderBy options={ORDERBY_OPTIONS} />}
|
||||
{enabledSort && <SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />}
|
||||
{enabledSearch && <Search setPlaceHolder="Search by name..." />}
|
||||
</StyledTableOption>
|
||||
return (
|
||||
<Direction>
|
||||
<StyledTableOption>
|
||||
{enabledOrder && <OrderBy options={ORDERBY_OPTIONS}/>}
|
||||
{Boolean(sortBy?.length) && <SortBy options={sortBy!}/>}
|
||||
{Boolean(searchPlaceHolder) && <Search setPlaceHolder={searchPlaceHolder!}/>}
|
||||
</StyledTableOption>
|
||||
|
||||
{rows && rows.length ? (<StyledTable>
|
||||
<StyledHeader width={width}>{headers}</StyledHeader>
|
||||
{rows && rows.length &&
|
||||
(
|
||||
<StyledTable>
|
||||
<StyledHeader width={width}>{headers}</StyledHeader>
|
||||
|
||||
<StyledBody>{renderRows}</StyledBody>
|
||||
<StyledBody>{renderRows}</StyledBody>
|
||||
|
||||
{Boolean(count) && (<StyledFooter>
|
||||
<Pagination count={count!} />
|
||||
</StyledFooter>)}
|
||||
</StyledTable>) : (<Message>No data to show here!</Message>)}
|
||||
</Direction>);
|
||||
};
|
||||
{Boolean(count) && (
|
||||
<StyledFooter>
|
||||
<Pagination count={count!}/>
|
||||
</StyledFooter>
|
||||
)}
|
||||
</StyledTable>
|
||||
)
|
||||
}
|
||||
</Direction>
|
||||
);
|
||||
}
|
||||
|
||||
export default Table;
|
||||
|
||||
@@ -25,6 +25,12 @@ const StyledBody = styled.div`
|
||||
|
||||
const StyledRow = styled(CommonRow)`
|
||||
padding: 20px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--item-hover);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--item-selected);
|
||||
|
||||
@@ -29,7 +29,12 @@ const getDayDiff = (startDate: Date, endDate: Date): number => {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const findItemInListById = <T,>(id: number, listItem: T[]): T | undefined => {
|
||||
return listItem.find((item) => item["id" as keyof typeof item] === id);
|
||||
}
|
||||
|
||||
export {
|
||||
formatCurrency,
|
||||
getDayDiff,
|
||||
findItemInListById,
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
||||
usersAvailable,
|
||||
dispatch
|
||||
} = useUserRoomAvailable();
|
||||
|
||||
const { isCreating, createBooking } = useCreateBooking();
|
||||
const { isUpdating, updateBooking } = useUpdateBooking();
|
||||
const isLoading = isCreating || isUpdating;
|
||||
@@ -83,7 +84,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
||||
type: 'updateStatusRoom',
|
||||
payload: [{ id: newBooking.roomId, status: true }],
|
||||
});
|
||||
|
||||
|
||||
// Update room, user available in server
|
||||
await updateRoomStatus(newBooking.roomId, true);
|
||||
await updateUserBookedStatus(newBooking.userId, true);
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import toast from 'react-hot-toast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
// Helpers
|
||||
import { formatCurrency } from '@src/helpers/helper';
|
||||
|
||||
// Types
|
||||
import { TBookingResponse } from '@src/types/booking';
|
||||
|
||||
// Components
|
||||
import Modal from '@src/components/Modal';
|
||||
import Table from '@src/components/Table';
|
||||
import Menus from '@src/components/Menus';
|
||||
import { RiEditBoxFill } from 'react-icons/ri';
|
||||
import { TbArrowNarrowRight } from 'react-icons/tb';
|
||||
import { ImExit } from 'react-icons/im';
|
||||
import BookingForm from './BookingForm';
|
||||
import ConfirmMessage from '@src/components/ConfirmMessage';
|
||||
|
||||
// Hooks
|
||||
import useCheckOut from '@src/hooks/bookings/useCheckout';
|
||||
|
||||
// Constants
|
||||
import { FORM } from '@src/constants/commons';
|
||||
|
||||
interface IBookingRow {
|
||||
booking: TBookingResponse;
|
||||
}
|
||||
|
||||
const BookingRow = ({ booking }: IBookingRow) => {
|
||||
const { checkOutBooking } = useCheckOut();
|
||||
const { id, users, startDate, endDate, rooms, amount, status } = booking;
|
||||
const formattedPrice = useMemo(() => formatCurrency(amount), [amount]);
|
||||
const renderEditBtn = useCallback(
|
||||
(onOpenModal: () => void) => (
|
||||
<Menus.Button
|
||||
onClick={onOpenModal}
|
||||
icon={<RiEditBoxFill />}
|
||||
disabled={!status}
|
||||
label={'Edit'}
|
||||
/>
|
||||
),
|
||||
[status]
|
||||
);
|
||||
|
||||
const renderCheckOutBtn = useCallback(
|
||||
(onOpenModal: () => void) => (
|
||||
<Menus.Button
|
||||
onClick={onOpenModal}
|
||||
icon={<ImExit />}
|
||||
disabled={!status}
|
||||
label={'Check out'}
|
||||
/>
|
||||
),
|
||||
[status]
|
||||
);
|
||||
|
||||
const handleClickCheckOutBtn = useCallback(() => {
|
||||
if (booking.status) {
|
||||
checkOutBooking({
|
||||
idBooking: booking.id,
|
||||
roomId: booking!.rooms!.id,
|
||||
userId: booking!.users!.id,
|
||||
});
|
||||
} else {
|
||||
toast.error('User already checkout!');
|
||||
}
|
||||
}, [booking, checkOutBooking]);
|
||||
|
||||
const renderRow = useCallback(
|
||||
(onCloseModal: () => void) => (
|
||||
<BookingForm
|
||||
booking={booking}
|
||||
key={booking.id}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
),
|
||||
[booking]
|
||||
);
|
||||
|
||||
const renderConfirmMessage = useCallback(
|
||||
(onCloseModal: () => void) => (
|
||||
<ConfirmMessage
|
||||
message={`Are you sure to checkout this user? '${booking.users?.name}'`}
|
||||
onConfirm={handleClickCheckOutBtn}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
),
|
||||
[booking.users?.name, handleClickCheckOutBtn]
|
||||
);
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<div>{users?.name}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{startDate} <TbArrowNarrowRight />
|
||||
{endDate}
|
||||
</div>
|
||||
<div>{rooms?.name}</div>
|
||||
<div>{formattedPrice}</div>
|
||||
<div>{status ? 'Check in' : 'Check out'}</div>
|
||||
|
||||
<div>
|
||||
<Modal>
|
||||
<Menus.Menu>
|
||||
<Menus.Toggle id={id.toString()} />
|
||||
|
||||
<Menus.List id={id.toString()}>
|
||||
<Modal.Open
|
||||
modalName={FORM.EDIT}
|
||||
renderChildren={renderEditBtn}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.CHECKOUT}
|
||||
renderChildren={renderCheckOutBtn}
|
||||
/>
|
||||
</Menus.List>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.EDIT}
|
||||
title="Edit Booking"
|
||||
renderChildren={renderRow}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.CHECKOUT}
|
||||
title="Checkout"
|
||||
renderChildren={renderConfirmMessage}
|
||||
/>
|
||||
</Menus.Menu>
|
||||
</Modal>
|
||||
</div>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookingRow;
|
||||
@@ -1,96 +0,0 @@
|
||||
// Components
|
||||
import Table from '@src/components/Table';
|
||||
import Message from '@src/components/Message';
|
||||
import Search from '@src/components/Search';
|
||||
|
||||
// Styled
|
||||
import { StyledOperationTable } from './styled';
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import Spinner from '@src/commons/styles/Spinner';
|
||||
|
||||
// Hooks
|
||||
import { useBookings } from '@src/hooks/bookings/useBookings';
|
||||
|
||||
// Types
|
||||
import { ColumnProps } from '@src/types/common';
|
||||
import { formatCurrency } from '@src/helpers/helper';
|
||||
import { useItemSelect } from '@src/hooks/useItemSelected';
|
||||
|
||||
interface IBookingTable {
|
||||
user: string;
|
||||
date: string[];
|
||||
room: string;
|
||||
amount: string;
|
||||
status: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const BookingTable = () => {
|
||||
const { dispatch } = useItemSelect();
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'user',
|
||||
title: 'User',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
title: 'Date',
|
||||
width: 25,
|
||||
isDateValue: true,
|
||||
},
|
||||
{
|
||||
key: 'room',
|
||||
title: 'Room',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
title: 'Amount',
|
||||
width: 15,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'Status',
|
||||
width: 20,
|
||||
},
|
||||
];
|
||||
|
||||
const { isLoading, bookings, count } = useBookings();
|
||||
|
||||
const tempBookings = bookings?.map((booking) => ({
|
||||
date: [booking.startDate, booking.endDate],
|
||||
amount: formatCurrency(booking.amount),
|
||||
room: booking.rooms!.name,
|
||||
user: booking.users!.name,
|
||||
status: booking.status ? 'Check in' : 'Check out',
|
||||
onClick: () => {
|
||||
dispatch!({type: 'setData', payload: booking})
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Direction>
|
||||
<StyledOperationTable>
|
||||
<Search setPlaceHolder="Search by name..." />
|
||||
</StyledOperationTable>
|
||||
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{tempBookings && tempBookings.length ? (
|
||||
<Table<IBookingTable>
|
||||
columns={columns}
|
||||
rows={tempBookings}
|
||||
count={count}
|
||||
/>
|
||||
) : (
|
||||
!isLoading && <Message>No data to show here!</Message>
|
||||
)}
|
||||
</Direction>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookingTable;
|
||||
@@ -1,37 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
// Types
|
||||
import { TBookingResponse } from '@src/types/booking';
|
||||
|
||||
// Components
|
||||
import BookingRow from '../BookingRow';
|
||||
|
||||
describe('BookingRow', () => {
|
||||
const tempBooking: TBookingResponse = {
|
||||
id: 1,
|
||||
startDate: '2024-11-21',
|
||||
endDate: '2024-11-22',
|
||||
amount: 1200,
|
||||
status: true,
|
||||
rooms: {
|
||||
id: 1,
|
||||
name: 'Room 1',
|
||||
},
|
||||
users: {
|
||||
id: 2,
|
||||
name: 'Room 2',
|
||||
},
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BookingRow booking={tempBooking} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
test('Should render correctly', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
// Components
|
||||
import BookingTable from '@src/pages/Booking/BookingTable';
|
||||
|
||||
describe('UserTable', () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<BookingTable />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
test('Should render correctly', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,42 +1,209 @@
|
||||
// Styled
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import { StyledBooking, Title } from './styled';
|
||||
import Button from '@src/commons/styles/Button';
|
||||
|
||||
// Components
|
||||
import Modal from '@src/components/Modal';
|
||||
import BookingTable from './BookingTable';
|
||||
import BookingForm from './BookingForm';
|
||||
import Modal from '@src/components/Modal';
|
||||
import ButtonIcon from '@src/components/ButtonIcon';
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import {FiEdit} from 'react-icons/fi';
|
||||
import {MdOutlineAddCircleOutline} from 'react-icons/md';
|
||||
import Table from '@src/components/Table';
|
||||
|
||||
// Constants
|
||||
import { FORM } from '@src/constants/commons';
|
||||
// Styled
|
||||
import {ActionTable, StyledBooking, Title} from './styled';
|
||||
import Spinner from '@src/commons/styles/Spinner.ts';
|
||||
|
||||
// Types
|
||||
import {ColumnProps} from '@src/types/common.ts';
|
||||
import {TBookingResponse} from '@src/types/booking.ts';
|
||||
|
||||
// Hooks
|
||||
import {useBookings} from '@src/hooks/bookings/useBookings.ts';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {FORM} from '@src/constants/commons.ts';
|
||||
import {findItemInListById, formatCurrency} from '@src/helpers/helper.ts';
|
||||
import ConfirmMessage from '@src/components/ConfirmMessage';
|
||||
import useCheckOut from '@src/hooks/bookings/useCheckout.ts';
|
||||
import toast from 'react-hot-toast';
|
||||
import {ImExit} from "react-icons/im";
|
||||
import Message from "@src/components/Message";
|
||||
|
||||
interface IBookingTable extends Omit<TBookingResponse, 'status' | 'amount'> {
|
||||
amount: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const Booking = () => {
|
||||
const {checkOutBooking} = useCheckOut();
|
||||
const [itemSelected, setItemSelected] = useState<IBookingTable>();
|
||||
let bookingSelected: TBookingResponse | undefined;
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'user',
|
||||
title: 'User',
|
||||
width: 10
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
title: 'Date',
|
||||
width: 25,
|
||||
isDateValue: true
|
||||
},
|
||||
{
|
||||
key: 'room',
|
||||
title: 'Room',
|
||||
width: 20
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
title: 'Amount',
|
||||
width: 15
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'Status',
|
||||
width: 20
|
||||
}
|
||||
];
|
||||
const {isLoading, bookings, count} = useBookings();
|
||||
|
||||
// Reset value when bookings changed.
|
||||
useEffect(() => {
|
||||
setItemSelected(undefined);
|
||||
}, [bookings]);
|
||||
|
||||
const tempBookings = bookings?.map((booking) => (
|
||||
{
|
||||
...booking,
|
||||
date: [booking.startDate, booking.endDate],
|
||||
amount: formatCurrency(booking.amount),
|
||||
room: booking.rooms!.name,
|
||||
user: booking.users!.name,
|
||||
status: booking.status
|
||||
? 'Check in'
|
||||
: 'Check out',
|
||||
}
|
||||
));
|
||||
|
||||
if (itemSelected && bookings) {
|
||||
bookingSelected = findItemInListById<TBookingResponse>(itemSelected.id, bookings);
|
||||
}
|
||||
|
||||
const handleClickCheckOutBtn = () => {
|
||||
if (itemSelected && itemSelected.status) {
|
||||
checkOutBooking({
|
||||
idBooking: itemSelected.id,
|
||||
roomId: itemSelected!.rooms!.id,
|
||||
userId: itemSelected!.users!.id
|
||||
});
|
||||
} else {
|
||||
toast.error('User already checkout!');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBooking>
|
||||
<Direction type="horizontal">
|
||||
<Title>List Booking</Title>
|
||||
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Modal>
|
||||
<Modal>
|
||||
<ActionTable>
|
||||
<Modal.Open
|
||||
modalName={FORM.BOOKING}
|
||||
modalName={FORM.ROOM}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<Button onClick={onCloseModal}>Add booking</Button>
|
||||
<ButtonIcon
|
||||
icon={<MdOutlineAddCircleOutline/>}
|
||||
text={'Add Booking'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'primary'}
|
||||
iconColor={'white'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.EDIT}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<FiEdit/>}
|
||||
text={'Edit'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'success'}
|
||||
iconColor={'white'}
|
||||
disabled={!itemSelected || itemSelected?.status === 'Check out'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.CHECKOUT}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<ImExit />}
|
||||
text={'Check out'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'danger'}
|
||||
iconColor={'white'}
|
||||
disabled={!itemSelected || itemSelected?.status === 'Check out'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.BOOKING}
|
||||
name={FORM.ROOM}
|
||||
title="Add form"
|
||||
renderChildren={(onCloseModal) => (
|
||||
<BookingForm onCloseModal={onCloseModal} />
|
||||
<BookingForm onCloseModal={onCloseModal}/>
|
||||
)}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.EDIT}
|
||||
title="Edit Booking"
|
||||
renderChildren={(onCloseModal) =>
|
||||
<BookingForm booking={bookingSelected} onCloseModal={onCloseModal}/>
|
||||
}/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.CHECKOUT}
|
||||
title="Check Out"
|
||||
renderChildren={(onCloseModal) =>
|
||||
<ConfirmMessage
|
||||
message={`Are you sure to check out this user? "${bookingSelected!.users!.name}"?`}
|
||||
onConfirm={handleClickCheckOutBtn}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
}/>
|
||||
|
||||
</ActionTable>
|
||||
</Modal>
|
||||
</Direction>
|
||||
|
||||
<BookingTable />
|
||||
{isLoading && <Spinner/>}
|
||||
|
||||
{
|
||||
tempBookings &&
|
||||
Boolean(tempBookings.length) &&
|
||||
<Table<IBookingTable>
|
||||
columns={columns}
|
||||
rows={tempBookings}
|
||||
count={count}
|
||||
searchPlaceHolder={'Search by name...'}
|
||||
stateSelected={{
|
||||
itemSelected,
|
||||
setItemSelected
|
||||
}}
|
||||
onRowClick={(data) => {
|
||||
setItemSelected(data);
|
||||
}}/>
|
||||
}
|
||||
|
||||
{tempBookings && Boolean(!tempBookings.length) && <Message>No data to show here!</Message>}
|
||||
</StyledBooking>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,14 +14,20 @@ const Title = styled.h2`
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
|
||||
const StyledOperationTable = styled.div`
|
||||
const StyledTableOption = styled.div`
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const ActionTable = styled.div`
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
`
|
||||
|
||||
export {
|
||||
StyledBooking,
|
||||
Title,
|
||||
StyledOperationTable
|
||||
StyledTableOption,
|
||||
ActionTable
|
||||
};
|
||||
|
||||
@@ -19,13 +19,20 @@ import { IRoom } from '@src/types/room.ts';
|
||||
// Hooks
|
||||
import { useRooms } from '@src/hooks/rooms/useRooms.ts';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {FORM, ROOM_PAGE} from '@src/constants/commons.ts';
|
||||
import { findItemInListById } from '@src/helpers/helper.ts';
|
||||
import ConfirmMessage from '@src/components/ConfirmMessage';
|
||||
import { useSetIsDeleteRoom } from '@src/hooks/rooms/useSetIsDeleteRoom.ts';
|
||||
import Message from "@src/components/Message";
|
||||
|
||||
interface IRoomTable extends Omit<IRoom, 'status'> {
|
||||
status: string;
|
||||
}
|
||||
|
||||
const Room = () => {
|
||||
const { setIsDeleteRoom } = useSetIsDeleteRoom();
|
||||
const [itemSelected, setItemSelected] = useState<IRoomTable>();
|
||||
let roomSelected: IRoom | undefined;
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
@@ -50,7 +57,7 @@ const Room = () => {
|
||||
}
|
||||
];
|
||||
const { isLoading, rooms, count } = useRooms();
|
||||
|
||||
|
||||
// Reset value when rooms changed.
|
||||
useEffect(() => {
|
||||
setItemSelected(undefined);
|
||||
@@ -63,6 +70,10 @@ const Room = () => {
|
||||
: 'Available'
|
||||
}));
|
||||
|
||||
if (itemSelected && rooms) {
|
||||
roomSelected = findItemInListById<IRoom>(itemSelected.id, rooms);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoom>
|
||||
<Direction type="horizontal">
|
||||
@@ -71,7 +82,7 @@ const Room = () => {
|
||||
<Modal>
|
||||
<ActionTable>
|
||||
<Modal.Open
|
||||
modalName="room-form"
|
||||
modalName={FORM.ROOM}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<MdOutlineAddCircleOutline />}
|
||||
@@ -84,32 +95,65 @@ const Room = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.EDIT}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<FiEdit />}
|
||||
text={'Edit'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'success'}
|
||||
iconColor={'white'}
|
||||
disabled={!itemSelected}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.DELETE}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<FaRegTrashAlt />}
|
||||
text={'Delete'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'danger'}
|
||||
iconColor={'white'}
|
||||
disabled={!itemSelected || itemSelected?.status === 'Available'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name="room-form"
|
||||
name={FORM.ROOM}
|
||||
title="Add form"
|
||||
renderChildren={(onCloseModal) => (
|
||||
<RoomForm onCloseModal={onCloseModal} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<ButtonIcon
|
||||
icon={<FiEdit />}
|
||||
text={'Edit'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'success'}
|
||||
iconColor={'white'}
|
||||
disabled={Boolean(!itemSelected)}
|
||||
/>
|
||||
<ButtonIcon
|
||||
icon={<FaRegTrashAlt />}
|
||||
text={'Delete'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'danger'}
|
||||
iconColor={'white'}
|
||||
disabled={Boolean(!itemSelected)}
|
||||
/>
|
||||
<Modal.Window
|
||||
name={FORM.EDIT}
|
||||
title="Edit Room"
|
||||
renderChildren={(onCloseModal) =>
|
||||
<RoomForm room={roomSelected} onCloseModal={onCloseModal} />
|
||||
} />
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.DELETE}
|
||||
title="Delete Room"
|
||||
renderChildren={(onCloseModal) =>
|
||||
<ConfirmMessage
|
||||
message={`Are you sure to delete ${roomSelected!.name}?`}
|
||||
onConfirm={() => setIsDeleteRoom(roomSelected!.id)}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
} />
|
||||
|
||||
</ActionTable>
|
||||
</Modal>
|
||||
</Direction>
|
||||
@@ -123,16 +167,19 @@ const Room = () => {
|
||||
columns={columns}
|
||||
rows={tempRooms}
|
||||
count={count}
|
||||
enabledSort={true}
|
||||
sortBy={ROOM_PAGE.SORTBY_OPTIONS}
|
||||
enabledOrder={true}
|
||||
enabledSearch={true}
|
||||
searchPlaceHolder={'Search by name...'}
|
||||
stateSelected={{
|
||||
itemSelected,
|
||||
setItemSelected,
|
||||
setItemSelected
|
||||
}}
|
||||
onRowClick={(data) => {
|
||||
setItemSelected(data);
|
||||
}} />}
|
||||
}} />
|
||||
}
|
||||
|
||||
{tempRooms && Boolean(!tempRooms.length) && <Message>No data to show here!</Message>}
|
||||
</StyledRoom>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
// Components
|
||||
import { RiEditBoxFill } from 'react-icons/ri';
|
||||
import Modal from '@src/components/Modal';
|
||||
import Table from '@src/components/Table';
|
||||
import Menus from '@src/components/Menus';
|
||||
import UserForm from './UserForm';
|
||||
import ConfirmMessage from '@src/components/ConfirmMessage';
|
||||
import { HiTrash } from 'react-icons/hi';
|
||||
|
||||
// Types
|
||||
import { IUser } from '@src/types/user';
|
||||
|
||||
// Constants
|
||||
import { FORM } from '@src/constants/commons';
|
||||
|
||||
// Hooks
|
||||
import { useIsDeleteUser } from '@src/hooks/users/useSetIsDeleteUser';
|
||||
|
||||
interface IUserRow {
|
||||
user: IUser;
|
||||
}
|
||||
|
||||
const UserRow = ({ user }: IUserRow) => {
|
||||
const { id, name, phone, isBooked } = user;
|
||||
const { setIsDeleteUser } = useIsDeleteUser();
|
||||
|
||||
const renderEditBtn = useCallback(
|
||||
(onOpenModal: () => void) => (
|
||||
<Menus.Button
|
||||
onClick={onOpenModal}
|
||||
icon={<RiEditBoxFill />}
|
||||
label={'Edit'}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const renderDeleteBtn = useCallback(
|
||||
(onOpenModal: () => void) => (
|
||||
<Menus.Button
|
||||
icon={<HiTrash />}
|
||||
onClick={onOpenModal}
|
||||
disabled={isBooked}
|
||||
label={'Delete'}
|
||||
/>
|
||||
),
|
||||
[isBooked]
|
||||
);
|
||||
|
||||
const renderRow = useCallback(
|
||||
(onCloseModal: () => void) => (
|
||||
<UserForm user={user} onCloseModal={onCloseModal} />
|
||||
),
|
||||
[user]
|
||||
);
|
||||
|
||||
const renderConfirmMessage = useCallback(
|
||||
(onCloseModal: () => void) => (
|
||||
<ConfirmMessage
|
||||
message={`Are you sure to delete ${name}?`}
|
||||
onConfirm={() => setIsDeleteUser(id)}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
),
|
||||
[id, name, setIsDeleteUser]
|
||||
);
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<div>{id}</div>
|
||||
<div>{name}</div>
|
||||
<div>{phone}</div>
|
||||
<div>{isBooked ? 'Yes' : 'No'}</div>
|
||||
<div>
|
||||
<Modal>
|
||||
<Menus.Menu>
|
||||
<Menus.Toggle id={id.toString()} />
|
||||
|
||||
<Menus.List id={id.toString()}>
|
||||
<Modal.Open
|
||||
modalName={FORM.EDIT}
|
||||
renderChildren={renderEditBtn}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.DELETE}
|
||||
renderChildren={renderDeleteBtn}
|
||||
/>
|
||||
</Menus.List>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.EDIT}
|
||||
title="Edit user"
|
||||
renderChildren={renderRow}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.DELETE}
|
||||
title="Delete User"
|
||||
renderChildren={renderConfirmMessage}
|
||||
/>
|
||||
</Menus.Menu>
|
||||
</Modal>
|
||||
</div>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserRow;
|
||||
@@ -1,81 +0,0 @@
|
||||
// Components
|
||||
import Table from '@src/components/Table';
|
||||
import Message from '@src/components/Message';
|
||||
import Search from '@src/components/Search';
|
||||
import SortBy from '@src/components/SortBy';
|
||||
import OrderBy from '@src/components/OrderBy';
|
||||
|
||||
// Constants
|
||||
import { ORDERBY_OPTIONS, USER_PAGE } from '@src/constants/commons';
|
||||
|
||||
// Styled
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import { StyledOperationTable } from './styled';
|
||||
import Spinner from '@src/commons/styles/Spinner';
|
||||
|
||||
// Hooks
|
||||
import { useUsers } from '@src/hooks/users/useUsers';
|
||||
import { ColumnProps } from '@src/types/common';
|
||||
import { IUser } from '@src/types/user';
|
||||
|
||||
interface IUserTable extends Omit<IUser, 'isBooked'>{
|
||||
isBooked: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const UserTable = () => {
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Id',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
width: 35,
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: 'Phone',
|
||||
width: 30,
|
||||
},
|
||||
{
|
||||
key: 'isBooked',
|
||||
title: 'Is Booked',
|
||||
width: 20,
|
||||
},
|
||||
];
|
||||
const { isLoading, users, count } = useUsers();
|
||||
|
||||
const tempUsers = users?.map((user) => ({
|
||||
...user,
|
||||
isBooked: user.isBooked
|
||||
? 'Yes'
|
||||
: 'No',
|
||||
onClick: () => console.log(user),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Direction>
|
||||
<StyledOperationTable>
|
||||
<OrderBy options={ORDERBY_OPTIONS} />
|
||||
|
||||
<SortBy options={USER_PAGE.SORTBY_OPTIONS} />
|
||||
<Search setPlaceHolder="Search by phone..." />
|
||||
</StyledOperationTable>
|
||||
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{tempUsers && tempUsers.length ? (
|
||||
<Table<IUserTable> columns={columns} rows={tempUsers} count={count}/>
|
||||
) : (
|
||||
!isLoading && <Message>No data to show here!</Message>
|
||||
)}
|
||||
</Direction>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTable;
|
||||
@@ -1,28 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
// Types
|
||||
import { IUser } from '@src/types/user';
|
||||
|
||||
// Components
|
||||
import UserRow from '../UserRow';
|
||||
|
||||
describe('UserRow', () => {
|
||||
const tempUser: IUser = {
|
||||
id: 1,
|
||||
name: 'Temp Room',
|
||||
phone: '0324421232',
|
||||
isBooked: false,
|
||||
isDelete: true,
|
||||
};
|
||||
const queryClient = new QueryClient();
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<UserRow user={tempUser} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
test('Should render correctly', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import UserTable from '@src/pages/User/UserTable.tsx';
|
||||
|
||||
// Components
|
||||
|
||||
describe('UserTable', () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<UserTable />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
test('Should render correctly', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,45 +1,188 @@
|
||||
// Components
|
||||
import UserTable from './UserTable';
|
||||
import Modal from '@src/components/Modal';
|
||||
import UserForm from './UserForm';
|
||||
import Modal from '@src/components/Modal';
|
||||
import ButtonIcon from '@src/components/ButtonIcon';
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import {FiEdit} from 'react-icons/fi';
|
||||
import {FaRegTrashAlt} from 'react-icons/fa';
|
||||
import {MdOutlineAddCircleOutline} from 'react-icons/md';
|
||||
import Table from '@src/components/Table';
|
||||
|
||||
// Styled
|
||||
import Button from '@src/commons/styles/Button';
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import { StyledUser, Title } from './styled';
|
||||
import {ActionTable, StyledUser, Title} from './styled';
|
||||
import Spinner from '@src/commons/styles/Spinner.ts';
|
||||
|
||||
// Constants
|
||||
import { FORM } from '@src/constants/commons';
|
||||
// Types
|
||||
import {ColumnProps} from '@src/types/common.ts';
|
||||
import {IUser} from '@src/types/user.ts';
|
||||
|
||||
// Hooks
|
||||
import {useUsers} from '@src/hooks/users/useUsers.ts';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {FORM, USER_PAGE} from '@src/constants/commons.ts';
|
||||
import {findItemInListById} from '@src/helpers/helper.ts';
|
||||
import ConfirmMessage from '@src/components/ConfirmMessage';
|
||||
import {useIsDeleteUser} from '@src/hooks/users/useSetIsDeleteUser.ts';
|
||||
import Message from "@src/components/Message";
|
||||
|
||||
interface IUserTable extends Omit<IUser, 'isBooked'> {
|
||||
isBooked: string;
|
||||
}
|
||||
|
||||
const User = () => {
|
||||
const TITLE = 'Add user';
|
||||
const {setIsDeleteUser} = useIsDeleteUser();
|
||||
const [itemSelected, setItemSelected] = useState<IUserTable>();
|
||||
let userSelected: IUser | undefined;
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Id',
|
||||
width: 10
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
width: 40
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: 'Phone',
|
||||
width: 20
|
||||
},
|
||||
{
|
||||
key: 'isBooked',
|
||||
title: 'Is Booked',
|
||||
width: 20
|
||||
}
|
||||
];
|
||||
const {isLoading, users, count} = useUsers();
|
||||
|
||||
// Reset value when users changed.
|
||||
useEffect(() => {
|
||||
setItemSelected(undefined);
|
||||
}, [users]);
|
||||
|
||||
const tempUsers = users?.map((user) => (
|
||||
{
|
||||
...user,
|
||||
isBooked: user.isBooked
|
||||
? 'Yes'
|
||||
: 'No'
|
||||
}
|
||||
));
|
||||
|
||||
if (itemSelected && users) {
|
||||
userSelected = findItemInListById<IUser>(itemSelected.id, users);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledUser>
|
||||
<Direction type="horizontal">
|
||||
<Title>List User</Title>
|
||||
<StyledUser>
|
||||
<Direction type="horizontal">
|
||||
<Title>List User</Title>
|
||||
|
||||
<Modal>
|
||||
<Modal>
|
||||
<ActionTable>
|
||||
<Modal.Open
|
||||
modalName={FORM.USER}
|
||||
modalName={FORM.ROOM}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<Button onClick={onCloseModal}>Add user</Button>
|
||||
<ButtonIcon
|
||||
icon={<MdOutlineAddCircleOutline/>}
|
||||
text={'Add User'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'primary'}
|
||||
iconColor={'white'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal.Window
|
||||
name={FORM.USER}
|
||||
title={TITLE}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<UserForm onCloseModal={onCloseModal} />
|
||||
)}
|
||||
/>
|
||||
</Modal>
|
||||
</Direction>
|
||||
|
||||
<UserTable />
|
||||
</StyledUser>
|
||||
</>
|
||||
<Modal.Open
|
||||
modalName={FORM.EDIT}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<FiEdit/>}
|
||||
text={'Edit'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'success'}
|
||||
iconColor={'white'}
|
||||
disabled={!itemSelected}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Open
|
||||
modalName={FORM.DELETE}
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<FaRegTrashAlt/>}
|
||||
text={'Delete'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'danger'}
|
||||
iconColor={'white'}
|
||||
disabled={!itemSelected || itemSelected?.isBooked === 'Yes'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.ROOM}
|
||||
title="Add form"
|
||||
renderChildren={(onCloseModal) => (
|
||||
<UserForm onCloseModal={onCloseModal}/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.EDIT}
|
||||
title="Edit User"
|
||||
renderChildren={(onCloseModal) =>
|
||||
<UserForm user={userSelected} onCloseModal={onCloseModal}/>
|
||||
}/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.DELETE}
|
||||
title="Delete User"
|
||||
renderChildren={(onCloseModal) =>
|
||||
<ConfirmMessage
|
||||
message={`Are you sure to delete ${userSelected!.name}?`}
|
||||
onConfirm={() => setIsDeleteUser(userSelected!.id)}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
}/>
|
||||
|
||||
</ActionTable>
|
||||
</Modal>
|
||||
</Direction>
|
||||
|
||||
{isLoading && <Spinner/>}
|
||||
|
||||
{
|
||||
tempUsers &&
|
||||
Boolean(tempUsers.length) &&
|
||||
<Table<IUserTable>
|
||||
columns={columns}
|
||||
rows={tempUsers}
|
||||
count={count}
|
||||
sortBy={USER_PAGE.SORTBY_OPTIONS}
|
||||
enabledOrder={true}
|
||||
searchPlaceHolder={'Search by phone...'}
|
||||
stateSelected={{
|
||||
itemSelected,
|
||||
setItemSelected
|
||||
}}
|
||||
onRowClick={(data) => {
|
||||
setItemSelected(data);
|
||||
}}/>
|
||||
}
|
||||
|
||||
{tempUsers && Boolean(!tempUsers.length) && <Message>No data to show here!</Message>}
|
||||
</StyledUser>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,14 +14,20 @@ const Title = styled.h2`
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
|
||||
const StyledOperationTable = styled.div`
|
||||
const StyledTableOption = styled.div`
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const ActionTable = styled.div`
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
`
|
||||
|
||||
export {
|
||||
StyledUser,
|
||||
Title,
|
||||
StyledOperationTable
|
||||
StyledTableOption,
|
||||
ActionTable
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
--primary-hover-color: #0010ba8c;
|
||||
|
||||
--header-table-color: #ebebeb;
|
||||
--item-selected: #f5f5f5;
|
||||
--item-hover: #f1f1f1;
|
||||
--item-selected: #ececec;
|
||||
--border-color: #8c8c8c;
|
||||
--footer-table-color: #ebebeb;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user