From e2ff52d864a3cb3f56f21161a2c7830adb7166f5 Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Thu, 14 Dec 2023 19:50:33 +0700 Subject: [PATCH] Fix sort, order, search function --- .../src/components/AppLayout/index.tsx | 2 +- .../src/components/Search/index.tsx | 8 +- .../src/components/Table/Table.test.tsx | 60 ------ .../src/components/Table/index.tsx | 126 +++++++---- .../src/components/Table/styled.ts | 6 + hotel-management/src/helpers/helper.ts | 5 + .../src/pages/Booking/BookingForm.tsx | 3 +- .../src/pages/Booking/BookingRow.tsx | 138 ------------ .../src/pages/Booking/BookingTable.tsx | 96 --------- .../Booking/__tests__/BookingRow.test.tsx | 37 ---- .../Booking/__tests__/BookingTable.test.tsx | 22 -- hotel-management/src/pages/Booking/index.tsx | 203 ++++++++++++++++-- hotel-management/src/pages/Booking/styled.ts | 10 +- hotel-management/src/pages/Room/index.tsx | 97 ++++++--- hotel-management/src/pages/User/UserRow.tsx | 111 ---------- hotel-management/src/pages/User/UserTable.tsx | 81 ------- .../src/pages/User/__tests__/UserRow.test.tsx | 28 --- .../pages/User/__tests__/UserTable.test.tsx | 22 -- hotel-management/src/pages/User/index.tsx | 197 ++++++++++++++--- hotel-management/src/pages/User/styled.ts | 10 +- .../src/styles/abstracts/variables.css | 3 +- 21 files changed, 546 insertions(+), 719 deletions(-) delete mode 100644 hotel-management/src/components/Table/Table.test.tsx delete mode 100644 hotel-management/src/pages/Booking/BookingRow.tsx delete mode 100644 hotel-management/src/pages/Booking/BookingTable.tsx delete mode 100644 hotel-management/src/pages/Booking/__tests__/BookingRow.test.tsx delete mode 100644 hotel-management/src/pages/Booking/__tests__/BookingTable.test.tsx delete mode 100644 hotel-management/src/pages/User/UserRow.tsx delete mode 100644 hotel-management/src/pages/User/UserTable.tsx delete mode 100644 hotel-management/src/pages/User/__tests__/UserRow.test.tsx delete mode 100644 hotel-management/src/pages/User/__tests__/UserTable.test.tsx diff --git a/hotel-management/src/components/AppLayout/index.tsx b/hotel-management/src/components/AppLayout/index.tsx index e3fc6cc..74bb6f7 100644 --- a/hotel-management/src/components/AppLayout/index.tsx +++ b/hotel-management/src/components/AppLayout/index.tsx @@ -57,7 +57,7 @@ const AppLayout = () => { }, []); return ( - +
diff --git a/hotel-management/src/components/Search/index.tsx b/hotel-management/src/components/Search/index.tsx index d23489b..da50c11 100644 --- a/hotel-management/src/components/Search/index.tsx +++ b/hotel-management/src/components/Search/index.tsx @@ -17,7 +17,12 @@ interface ISearch { const Search = ({ setPlaceHolder }: ISearch) => { const field = 'search'; const [searchParams, setSearchParams] = useSearchParams(); - const [query, setQuery] = useState>(null); + const [query, setQuery] = useState>( + searchParams.get(field) + ? searchParams.get(field) + : '', + ); + const debounceValue = useDebounce>(query, 700); useEffect(() => { @@ -32,6 +37,7 @@ const Search = ({ setPlaceHolder }: ISearch) => { setQuery(e.target.value)} placeholder={setPlaceHolder} + value={query!} /> ); }; diff --git a/hotel-management/src/components/Table/Table.test.tsx b/hotel-management/src/components/Table/Table.test.tsx deleted file mode 100644 index 337c56a..0000000 --- a/hotel-management/src/components/Table/Table.test.tsx +++ /dev/null @@ -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 ( - -
{id}
-
{name}
-
{phone}
-
{isBooked}
-
- ); - }; - const renderRow = (user: IUser) => ; - - const wrapper = render( - - - data={tempUser} render={renderRow} /> -
- ); - - test('Should render correctly', () => { - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/hotel-management/src/components/Table/index.tsx b/hotel-management/src/components/Table/index.tsx index fd5fd11..9de0afa 100644 --- a/hotel-management/src/components/Table/index.tsx +++ b/hotel-management/src/components/Table/index.tsx @@ -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 = { rows: T[]; stateSelected: { itemSelected: T | undefined; - setItemSelected: Dispatch> + setItemSelected: Dispatch>; }; count?: number | null; onRowClick?: (rowData: T) => void; enabledOrder?: boolean; - enabledSort?: boolean; - enabledSearch?: boolean; + sortBy?: { + value: string; + label: string; + }[]; + searchPlaceHolder?: string; }; const Table = ({ @@ -49,18 +61,24 @@ const Table = ({ count, onRowClick, stateSelected, - enabledSort = false, - enabledSearch = false, - enabledOrder = false + sortBy, + searchPlaceHolder, + enabledOrder = false, }: Props) => { 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
{column.title}
; }); - const renderRows = rows.map((row, rowIndex) => { + const renderRows = rows.map(( + row, + rowIndex + ) => { const handleRowClick = () => { if (onRowClick) { onRowClick(row); @@ -73,45 +91,61 @@ const Table = ({ 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 (
- {value[0]}   -   {value[1]} -
); + return ( +
+ {value[0]}   +   {value[1]} +
+ ); } return
{value}
; })} - ); + + ); }); - return ( - - {enabledOrder && } - {enabledSort && } - {enabledSearch && } - + return ( + + + {enabledOrder && } + {Boolean(sortBy?.length) && } + {Boolean(searchPlaceHolder) && } + - {rows && rows.length ? ( - {headers} + {rows && rows.length && + ( + + {headers} - {renderRows} + {renderRows} - {Boolean(count) && ( - - )} - ) : (No data to show here!)} - ); -}; + {Boolean(count) && ( + + + + )} + + ) + } + + ); +} export default Table; diff --git a/hotel-management/src/components/Table/styled.ts b/hotel-management/src/components/Table/styled.ts index 7cc00d9..57d76bd 100644 --- a/hotel-management/src/components/Table/styled.ts +++ b/hotel-management/src/components/Table/styled.ts @@ -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); diff --git a/hotel-management/src/helpers/helper.ts b/hotel-management/src/helpers/helper.ts index 7b0c324..56f7f58 100644 --- a/hotel-management/src/helpers/helper.ts +++ b/hotel-management/src/helpers/helper.ts @@ -29,7 +29,12 @@ const getDayDiff = (startDate: Date, endDate: Date): number => { return 0; } +const findItemInListById = (id: number, listItem: T[]): T | undefined => { + return listItem.find((item) => item["id" as keyof typeof item] === id); +} + export { formatCurrency, getDayDiff, + findItemInListById, }; diff --git a/hotel-management/src/pages/Booking/BookingForm.tsx b/hotel-management/src/pages/Booking/BookingForm.tsx index 34394d0..fe948d0 100644 --- a/hotel-management/src/pages/Booking/BookingForm.tsx +++ b/hotel-management/src/pages/Booking/BookingForm.tsx @@ -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); diff --git a/hotel-management/src/pages/Booking/BookingRow.tsx b/hotel-management/src/pages/Booking/BookingRow.tsx deleted file mode 100644 index 53f8e95..0000000 --- a/hotel-management/src/pages/Booking/BookingRow.tsx +++ /dev/null @@ -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) => ( - } - disabled={!status} - label={'Edit'} - /> - ), - [status] - ); - - const renderCheckOutBtn = useCallback( - (onOpenModal: () => void) => ( - } - 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) => ( - - ), - [booking] - ); - - const renderConfirmMessage = useCallback( - (onCloseModal: () => void) => ( - - ), - [booking.users?.name, handleClickCheckOutBtn] - ); - - return ( - -
{users?.name}
-
- {startDate}   -   {endDate} -
-
{rooms?.name}
-
{formattedPrice}
-
{status ? 'Check in' : 'Check out'}
- -
- - - - - - - - - - - - - - - -
-
- ); -}; - -export default BookingRow; diff --git a/hotel-management/src/pages/Booking/BookingTable.tsx b/hotel-management/src/pages/Booking/BookingTable.tsx deleted file mode 100644 index 7521e49..0000000 --- a/hotel-management/src/pages/Booking/BookingTable.tsx +++ /dev/null @@ -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 ( - <> - - - - - - {isLoading && } - - {tempBookings && tempBookings.length ? ( - - columns={columns} - rows={tempBookings} - count={count} - /> - ) : ( - !isLoading && No data to show here! - )} - - - ); -}; - -export default BookingTable; diff --git a/hotel-management/src/pages/Booking/__tests__/BookingRow.test.tsx b/hotel-management/src/pages/Booking/__tests__/BookingRow.test.tsx deleted file mode 100644 index 25daabc..0000000 --- a/hotel-management/src/pages/Booking/__tests__/BookingRow.test.tsx +++ /dev/null @@ -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( - - - - ); - - test('Should render correctly', () => { - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/hotel-management/src/pages/Booking/__tests__/BookingTable.test.tsx b/hotel-management/src/pages/Booking/__tests__/BookingTable.test.tsx deleted file mode 100644 index aff86f0..0000000 --- a/hotel-management/src/pages/Booking/__tests__/BookingTable.test.tsx +++ /dev/null @@ -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( - - - - - - ); - - test('Should render correctly', () => { - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/hotel-management/src/pages/Booking/index.tsx b/hotel-management/src/pages/Booking/index.tsx index 57f78b6..6a9a122 100644 --- a/hotel-management/src/pages/Booking/index.tsx +++ b/hotel-management/src/pages/Booking/index.tsx @@ -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 { + amount: string; + status: string; +} const Booking = () => { + const {checkOutBooking} = useCheckOut(); + const [itemSelected, setItemSelected] = useState(); + 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(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 ( List Booking -
- + + ( - + } + text={'Add Booking'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'primary'} + iconColor={'white'} + onClick={onCloseModal} + /> )} /> + + ( + } + text={'Edit'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'success'} + iconColor={'white'} + disabled={!itemSelected || itemSelected?.status === 'Check out'} + onClick={onCloseModal} + /> + )} + /> + + ( + } + text={'Check out'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'danger'} + iconColor={'white'} + disabled={!itemSelected || itemSelected?.status === 'Check out'} + onClick={onCloseModal} + /> + )} + /> + ( - + )} /> - -
+ + + + }/> + + + + }/> + + +
- + {isLoading && } + + { + tempBookings && + Boolean(tempBookings.length) && + + columns={columns} + rows={tempBookings} + count={count} + searchPlaceHolder={'Search by name...'} + stateSelected={{ + itemSelected, + setItemSelected + }} + onRowClick={(data) => { + setItemSelected(data); + }}/> + } + + {tempBookings && Boolean(!tempBookings.length) && No data to show here!}
); }; diff --git a/hotel-management/src/pages/Booking/styled.ts b/hotel-management/src/pages/Booking/styled.ts index 7ea7730..7595e3a 100644 --- a/hotel-management/src/pages/Booking/styled.ts +++ b/hotel-management/src/pages/Booking/styled.ts @@ -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 }; diff --git a/hotel-management/src/pages/Room/index.tsx b/hotel-management/src/pages/Room/index.tsx index 51736c9..a97a32c 100644 --- a/hotel-management/src/pages/Room/index.tsx +++ b/hotel-management/src/pages/Room/index.tsx @@ -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 { status: string; } const Room = () => { + const { setIsDeleteRoom } = useSetIsDeleteRoom(); const [itemSelected, setItemSelected] = useState(); + 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(itemSelected.id, rooms); + } + return ( @@ -71,7 +82,7 @@ const Room = () => { ( } @@ -84,32 +95,65 @@ const Room = () => { /> )} /> + + ( + } + text={'Edit'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'success'} + iconColor={'white'} + disabled={!itemSelected} + onClick={onCloseModal} + /> + )} + /> + + ( + } + text={'Delete'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'danger'} + iconColor={'white'} + disabled={!itemSelected || itemSelected?.status === 'Available'} + onClick={onCloseModal} + /> + )} + /> + ( )} /> - } - text={'Edit'} - iconSize={'18px'} - fontSize={'var(--fs-sm)'} - variations={'success'} - iconColor={'white'} - disabled={Boolean(!itemSelected)} - /> - } - text={'Delete'} - iconSize={'18px'} - fontSize={'var(--fs-sm)'} - variations={'danger'} - iconColor={'white'} - disabled={Boolean(!itemSelected)} - /> + + + } /> + + + setIsDeleteRoom(roomSelected!.id)} + onCloseModal={onCloseModal} + /> + } /> + @@ -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) && No data to show here!} ); }; diff --git a/hotel-management/src/pages/User/UserRow.tsx b/hotel-management/src/pages/User/UserRow.tsx deleted file mode 100644 index e8ecbbe..0000000 --- a/hotel-management/src/pages/User/UserRow.tsx +++ /dev/null @@ -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) => ( - } - label={'Edit'} - /> - ), - [] - ); - - const renderDeleteBtn = useCallback( - (onOpenModal: () => void) => ( - } - onClick={onOpenModal} - disabled={isBooked} - label={'Delete'} - /> - ), - [isBooked] - ); - - const renderRow = useCallback( - (onCloseModal: () => void) => ( - - ), - [user] - ); - - const renderConfirmMessage = useCallback( - (onCloseModal: () => void) => ( - setIsDeleteUser(id)} - onCloseModal={onCloseModal} - /> - ), - [id, name, setIsDeleteUser] - ); - - return ( - -
{id}
-
{name}
-
{phone}
-
{isBooked ? 'Yes' : 'No'}
-
- - - - - - - - - - - - - - - -
-
- ); -}; - -export default UserRow; diff --git a/hotel-management/src/pages/User/UserTable.tsx b/hotel-management/src/pages/User/UserTable.tsx deleted file mode 100644 index 81eb5f5..0000000 --- a/hotel-management/src/pages/User/UserTable.tsx +++ /dev/null @@ -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{ - 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 ( - <> - - - - - - - - - {isLoading && } - - {tempUsers && tempUsers.length ? ( - columns={columns} rows={tempUsers} count={count}/> - ) : ( - !isLoading && No data to show here! - )} - - - ); -}; - -export default UserTable; diff --git a/hotel-management/src/pages/User/__tests__/UserRow.test.tsx b/hotel-management/src/pages/User/__tests__/UserRow.test.tsx deleted file mode 100644 index e0cb208..0000000 --- a/hotel-management/src/pages/User/__tests__/UserRow.test.tsx +++ /dev/null @@ -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( - - - - ); - - test('Should render correctly', () => { - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/hotel-management/src/pages/User/__tests__/UserTable.test.tsx b/hotel-management/src/pages/User/__tests__/UserTable.test.tsx deleted file mode 100644 index 397fa01..0000000 --- a/hotel-management/src/pages/User/__tests__/UserTable.test.tsx +++ /dev/null @@ -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( - - - - - - ); - - test('Should render correctly', () => { - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/hotel-management/src/pages/User/index.tsx b/hotel-management/src/pages/User/index.tsx index 1b450c3..d52fe54 100644 --- a/hotel-management/src/pages/User/index.tsx +++ b/hotel-management/src/pages/User/index.tsx @@ -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 { + isBooked: string; +} const User = () => { - const TITLE = 'Add user'; + const {setIsDeleteUser} = useIsDeleteUser(); + const [itemSelected, setItemSelected] = useState(); + 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(itemSelected.id, users); + } return ( - <> - - - List User + + + List User - + + ( - + } + text={'Add User'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'primary'} + iconColor={'white'} + onClick={onCloseModal} + /> )} /> - ( - - )} - /> - - - - - + ( + } + text={'Edit'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'success'} + iconColor={'white'} + disabled={!itemSelected} + onClick={onCloseModal} + /> + )} + /> + + ( + } + text={'Delete'} + iconSize={'18px'} + fontSize={'var(--fs-sm)'} + variations={'danger'} + iconColor={'white'} + disabled={!itemSelected || itemSelected?.isBooked === 'Yes'} + onClick={onCloseModal} + /> + )} + /> + + ( + + )} + /> + + + + }/> + + + setIsDeleteUser(userSelected!.id)} + onCloseModal={onCloseModal} + /> + }/> + + + + + + {isLoading && } + + { + tempUsers && + Boolean(tempUsers.length) && + + 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) && No data to show here!} + ); }; diff --git a/hotel-management/src/pages/User/styled.ts b/hotel-management/src/pages/User/styled.ts index 3b1775b..f163b2e 100644 --- a/hotel-management/src/pages/User/styled.ts +++ b/hotel-management/src/pages/User/styled.ts @@ -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 }; diff --git a/hotel-management/src/styles/abstracts/variables.css b/hotel-management/src/styles/abstracts/variables.css index 303edd7..2777805 100644 --- a/hotel-management/src/styles/abstracts/variables.css +++ b/hotel-management/src/styles/abstracts/variables.css @@ -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;