mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Update table component, remove unnecessary code
This commit is contained in:
@@ -2,7 +2,6 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
import { StyleSheetManager } from 'styled-components';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo, useReducer } from 'react';
|
||||
|
||||
// Components
|
||||
import AppLayout from './components/AppLayout';
|
||||
@@ -21,87 +20,33 @@ import Account from '@src/pages/Account';
|
||||
// Constants
|
||||
import * as PATH from './constants/path';
|
||||
|
||||
// Hooks
|
||||
import {
|
||||
UserRoomAvailableContext,
|
||||
initialState,
|
||||
reducer,
|
||||
} from '@src/contexts/UserRoomAvailableContext';
|
||||
|
||||
// Services
|
||||
import { getAllUsers } from '@src/services/userServices';
|
||||
import { getAllRooms } from '@src/services/roomServices';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function App() {
|
||||
const [{ usersAvailable, roomsAvailable }, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState
|
||||
);
|
||||
|
||||
// Init list user and room available
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const tempUser = await getAllUsers({
|
||||
sortBy: 'id',
|
||||
orderBy: 'asc',
|
||||
phoneSearch: '',
|
||||
page: 0,
|
||||
});
|
||||
|
||||
if (tempUser) {
|
||||
dispatch({ type: 'initUser', payload: tempUser.data });
|
||||
}
|
||||
|
||||
const tempRoom = await getAllRooms({
|
||||
sortBy: 'id',
|
||||
orderBy: 'asc',
|
||||
roomSearch: '',
|
||||
page: 0,
|
||||
});
|
||||
|
||||
if (tempRoom) {
|
||||
dispatch({ type: 'initRoom', payload: tempRoom.data });
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const store = useMemo(() => {
|
||||
return { roomsAvailable, usersAvailable, dispatch };
|
||||
}, [roomsAvailable, usersAvailable]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
|
||||
<UserRoomAvailableContext.Provider value={store}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<RootLayout />}>
|
||||
<Route
|
||||
element={
|
||||
<RouteProtected>
|
||||
<AppLayout />
|
||||
</RouteProtected>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={<Navigate replace to={PATH.BOOKING} />}
|
||||
/>
|
||||
<Route path={PATH.BOOKING} element={<Booking />} />
|
||||
<Route path={PATH.USER} element={<User />} />
|
||||
<Route path={PATH.ROOM} element={<Room />} />
|
||||
<Route path={PATH.ACCOUNT} element={<Account />} />
|
||||
</Route>
|
||||
<Route path={PATH.LOGIN} element={<Login />} />
|
||||
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<RootLayout />}>
|
||||
<Route
|
||||
element={
|
||||
<RouteProtected>
|
||||
<AppLayout />
|
||||
</RouteProtected>
|
||||
}
|
||||
>
|
||||
<Route index element={<Navigate replace to={PATH.BOOKING} />} />
|
||||
<Route path={PATH.BOOKING} element={<Booking />} />
|
||||
<Route path={PATH.USER} element={<User />} />
|
||||
<Route path={PATH.ROOM} element={<Room />} />
|
||||
<Route path={PATH.ACCOUNT} element={<Account />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</UserRoomAvailableContext.Provider>
|
||||
<Route path={PATH.LOGIN} element={<Login />} />
|
||||
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</StyleSheetManager>
|
||||
|
||||
<Toast />
|
||||
|
||||
@@ -16,7 +16,6 @@ const Spinner = styled.div`
|
||||
top/10px 10px no-repeat,
|
||||
conic-gradient(#0000 30%, var(--primary-color));
|
||||
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 10px), #000 0);
|
||||
mask: none;
|
||||
animation: ${rotate} 1.5s infinite linear;
|
||||
`;
|
||||
|
||||
|
||||
@@ -10,17 +10,62 @@ import { Main, StyledAppLayout } from './styled';
|
||||
// Hooks
|
||||
import { useAccount } from '@src/hooks/authentication/useAccount';
|
||||
|
||||
// Contexts
|
||||
import { initialState, reducer, UserRoomAvailableContext } from '@src/contexts/UserRoomAvailableContext.ts';
|
||||
import { useEffect, useReducer } from 'react';
|
||||
|
||||
// Services
|
||||
import { getAllUsers } from '@src/services/userServices.ts';
|
||||
import { getAllRooms } from '@src/services/roomServices.ts';
|
||||
|
||||
const AppLayout = () => {
|
||||
const { account } = useAccount();
|
||||
|
||||
const [{ usersAvailable, roomsAvailable }, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState
|
||||
);
|
||||
|
||||
// Init list user and room available
|
||||
// Init list user and room available
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const tempUser = await getAllUsers({
|
||||
sortBy: 'id',
|
||||
orderBy: 'asc',
|
||||
phoneSearch: '',
|
||||
page: 0,
|
||||
});
|
||||
|
||||
if (tempUser) {
|
||||
dispatch({ type: 'initUser', payload: tempUser.data });
|
||||
}
|
||||
|
||||
const tempRoom = await getAllRooms({
|
||||
sortBy: 'id',
|
||||
orderBy: 'asc',
|
||||
roomSearch: '',
|
||||
page: 0,
|
||||
});
|
||||
|
||||
if (tempRoom) {
|
||||
dispatch({ type: 'initRoom', payload: tempRoom.data });
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyledAppLayout>
|
||||
<Header accountName={account?.user_metadata.fullName} />
|
||||
<Sidebar heading="Hotel Management" />
|
||||
<Main>
|
||||
<Outlet />
|
||||
</Main>
|
||||
</StyledAppLayout>
|
||||
<UserRoomAvailableContext.Provider value={{usersAvailable, roomsAvailable}}>
|
||||
<StyledAppLayout>
|
||||
<Header accountName={account?.user_metadata.fullName} />
|
||||
<Sidebar heading="Hotel Management" />
|
||||
<Main>
|
||||
<Outlet />
|
||||
</Main>
|
||||
</StyledAppLayout>
|
||||
</UserRoomAvailableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ interface IButtonIcon extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon: ReactNode;
|
||||
iconSize?: string;
|
||||
iconColor?: string;
|
||||
fontSize?: string;
|
||||
text?: string;
|
||||
variations?: string;
|
||||
}
|
||||
|
||||
const ButtonIcon = ({
|
||||
@@ -15,12 +17,16 @@ const ButtonIcon = ({
|
||||
iconSize,
|
||||
iconColor,
|
||||
text,
|
||||
fontSize,
|
||||
variations,
|
||||
...props
|
||||
}: IButtonIcon) => {
|
||||
return (
|
||||
<StyledButtonIcon
|
||||
iconColor={iconColor}
|
||||
iconSize={iconSize}
|
||||
fontSize={fontSize}
|
||||
variations={variations}
|
||||
{...props}
|
||||
>
|
||||
{icon} {text && <span>{text}</span>}
|
||||
|
||||
@@ -1,10 +1,56 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import styled, { css, RuleSet } from 'styled-components';
|
||||
|
||||
interface IStyledButtonIcon {
|
||||
iconSize?: string;
|
||||
iconColor?: string;
|
||||
fontSize?: string;
|
||||
variations?: string;
|
||||
}
|
||||
|
||||
export interface IVariations {
|
||||
[key: string]: RuleSet<object>;
|
||||
}
|
||||
|
||||
const variations: IVariations = {
|
||||
success: css`
|
||||
background-color: var(--success-btn-color);
|
||||
color: var(--light-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--success-btn-hover-color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--success-btn-hover-color);
|
||||
}
|
||||
`,
|
||||
primary: css`
|
||||
background-color: var(--primary-color);
|
||||
color: var(--light-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-hover-color);
|
||||
}
|
||||
`,
|
||||
danger: css`
|
||||
background-color: var(--danger-btn-color);
|
||||
color: var(--light-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--danger-btn-hover-color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--danger-btn-hover-color);
|
||||
}
|
||||
`,
|
||||
default: css`
|
||||
&:hover {
|
||||
background-color: var(--hover-background-color);
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const StyledButtonIcon = styled.button<IStyledButtonIcon>`
|
||||
background: none;
|
||||
|
||||
@@ -12,18 +58,24 @@ const StyledButtonIcon = styled.button<IStyledButtonIcon>`
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
font-size: var(--fs-sm-2x);
|
||||
${(props) =>
|
||||
props.fontSize &&
|
||||
css`
|
||||
font-size: ${props.fontSize};
|
||||
`}
|
||||
|
||||
padding: 10px;
|
||||
padding: 10px 20px;
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--hover-background-color);
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
${(props) => variations[props.variations!]}
|
||||
|
||||
& svg {
|
||||
${(props) =>
|
||||
props.iconSize &&
|
||||
@@ -45,6 +97,8 @@ const StyledButtonIcon = styled.button<IStyledButtonIcon>`
|
||||
StyledButtonIcon.defaultProps = {
|
||||
iconSize: '16px',
|
||||
iconColor: '#000',
|
||||
fontSize: 'var(--fs-sm-2x)',
|
||||
variations: 'default',
|
||||
};
|
||||
|
||||
export { StyledButtonIcon };
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
// Types
|
||||
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 OrderBy from '@src/components/OrderBy';
|
||||
import { ORDERBY_OPTIONS, ROOM_PAGE } 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';
|
||||
|
||||
const parseWidthString = (columnsWidth: number[]): string => {
|
||||
let result: string = '';
|
||||
@@ -23,61 +32,86 @@ const parseWidthString = (columnsWidth: number[]): string => {
|
||||
type Props<T> = {
|
||||
columns: ColumnProps[];
|
||||
rows: T[];
|
||||
stateSelected: {
|
||||
itemSelected: T | undefined;
|
||||
setItemSelected: Dispatch<SetStateAction<T | undefined>>
|
||||
};
|
||||
count?: number | null;
|
||||
onRowClick?: (rowData: T) => void;
|
||||
enabledOrder?: boolean;
|
||||
enabledSort?: boolean;
|
||||
enabledSearch?: boolean;
|
||||
};
|
||||
|
||||
const Table = <T,>({ rows, columns, count }: Props<T>) => {
|
||||
const Table = <T, >({
|
||||
rows,
|
||||
columns,
|
||||
count,
|
||||
onRowClick,
|
||||
stateSelected,
|
||||
enabledSort = false,
|
||||
enabledSearch = false,
|
||||
enabledOrder = false
|
||||
}: Props<T>) => {
|
||||
const width = parseWidthString(columns.map((item) => item.width));
|
||||
const { itemSelected, setItemSelected } = stateSelected;
|
||||
|
||||
const headers = columns.map((column, index) => {
|
||||
return <div key={`headCell-${index}`}>{column.title}</div>;
|
||||
});
|
||||
|
||||
const renderRows = rows.map((row, rowIndex) => {
|
||||
const handleRowClick = () => {
|
||||
if (onRowClick) {
|
||||
onRowClick(row);
|
||||
setItemSelected(row);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledRow
|
||||
key={`row-${rowIndex}`}
|
||||
width={width}
|
||||
onClick={
|
||||
row[
|
||||
'onClick' as keyof typeof row
|
||||
] as React.MouseEventHandler<HTMLDivElement>
|
||||
}
|
||||
>
|
||||
onDoubleClick={handleRowClick}
|
||||
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 (
|
||||
<StyledTable>
|
||||
return (<Direction>
|
||||
<StyledTableOption>
|
||||
{enabledOrder && <OrderBy options={ORDERBY_OPTIONS} />}
|
||||
{enabledSort && <SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />}
|
||||
{enabledSearch && <Search setPlaceHolder="Search by name..." />}
|
||||
</StyledTableOption>
|
||||
|
||||
{rows && rows.length ? (<StyledTable>
|
||||
<StyledHeader width={width}>{headers}</StyledHeader>
|
||||
|
||||
<StyledBody>{renderRows}</StyledBody>
|
||||
|
||||
{Boolean(count) && (
|
||||
<StyledFooter>
|
||||
<Pagination count={count!} />
|
||||
</StyledFooter>
|
||||
)}
|
||||
</StyledTable>
|
||||
);
|
||||
{Boolean(count) && (<StyledFooter>
|
||||
<Pagination count={count!} />
|
||||
</StyledFooter>)}
|
||||
</StyledTable>) : (<Message>No data to show here!</Message>)}
|
||||
</Direction>);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
|
||||
@@ -26,8 +26,8 @@ const StyledBody = styled.div`
|
||||
const StyledRow = styled(CommonRow)`
|
||||
padding: 20px;
|
||||
|
||||
&.active {
|
||||
background-color: red;
|
||||
&.selected {
|
||||
background-color: var(--item-selected);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
// Helpers
|
||||
import { formatCurrency } from '@src/helpers/helper';
|
||||
|
||||
// Types
|
||||
import { IRoom } from '@src/types/room';
|
||||
|
||||
// Components
|
||||
import RoomForm from './RoomForm';
|
||||
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 { HiTrash } from 'react-icons/hi';
|
||||
import ConfirmMessage from '@src/components/ConfirmMessage';
|
||||
|
||||
// Hooks
|
||||
import { useSetIsDeleteRoom } from '@src/hooks/rooms/useSetIsDeleteRoom';
|
||||
|
||||
// Constants
|
||||
import { FORM } from '@src/constants/commons';
|
||||
|
||||
interface IRoomRow {
|
||||
room: IRoom;
|
||||
}
|
||||
|
||||
const RoomRow = ({ room }: IRoomRow) => {
|
||||
const { id, name, price, status } = room;
|
||||
const { setIsDeleteRoom } = useSetIsDeleteRoom();
|
||||
const formattedPrice = useMemo(() => formatCurrency(price), [price]);
|
||||
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={status}
|
||||
label={'Delete'}
|
||||
/>
|
||||
),
|
||||
[status]
|
||||
);
|
||||
|
||||
const renderRow = useCallback(
|
||||
(onCloseModal: () => void) => (
|
||||
<RoomForm room={room} onCloseModal={onCloseModal} />
|
||||
),
|
||||
[room]
|
||||
);
|
||||
|
||||
const renderConfirmMessage = useCallback(
|
||||
(onCloseModal: () => void) => (
|
||||
<ConfirmMessage
|
||||
message={`Are you sure to delete ${name}?`}
|
||||
onConfirm={() => setIsDeleteRoom(id)}
|
||||
onCloseModal={onCloseModal}
|
||||
/>
|
||||
),
|
||||
[id, name, setIsDeleteRoom]
|
||||
);
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<div>{id}</div>
|
||||
<div>{name}</div>
|
||||
<div>{formattedPrice}</div>
|
||||
<div>{status ? 'Unavailable' : 'Available'}</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 Room"
|
||||
renderChildren={renderRow}
|
||||
/>
|
||||
|
||||
<Modal.Window
|
||||
name={FORM.DELETE}
|
||||
title="Delete Room"
|
||||
renderChildren={renderConfirmMessage}
|
||||
/>
|
||||
</Menus.Menu>
|
||||
</Modal>
|
||||
</div>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomRow;
|
||||
@@ -1,83 +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';
|
||||
|
||||
// Types
|
||||
import { IRoom } from '@src/types/room';
|
||||
import { ColumnProps } from '@src/types/common';
|
||||
|
||||
// Constants
|
||||
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@src/constants/commons';
|
||||
|
||||
// Styled
|
||||
import { StyledOperationTable } from './styled';
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import Spinner from '@src/commons/styles/Spinner';
|
||||
|
||||
// Hooks
|
||||
import { useRooms } from '@src/hooks/rooms/useRooms';
|
||||
|
||||
interface IRoomTable extends Omit<IRoom, 'status'>{
|
||||
status: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const RoomTable = () => {
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Id',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
width: 40,
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
title: 'Price',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'Status',
|
||||
width: 20,
|
||||
},
|
||||
];
|
||||
const { isLoading, rooms, count } = useRooms();
|
||||
|
||||
const tempRooms = rooms?.map((room) => ({
|
||||
...room,
|
||||
status: room.status
|
||||
? 'Unavailable'
|
||||
: 'Available',
|
||||
onClick: () => console.log(room),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Direction>
|
||||
<StyledOperationTable>
|
||||
<OrderBy options={ORDERBY_OPTIONS} />
|
||||
|
||||
<SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />
|
||||
<Search setPlaceHolder="Search by name..." />
|
||||
</StyledOperationTable>
|
||||
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{tempRooms && tempRooms.length ? (
|
||||
<Table<IRoomTable> columns={columns} rows={tempRooms} count={count}/>
|
||||
) : (
|
||||
!isLoading && <Message>No data to show here!</Message>
|
||||
)}
|
||||
</Direction>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomTable;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
// Types
|
||||
import { IRoom } from '@src/types/room';
|
||||
|
||||
// Components
|
||||
import RoomRow from '../RoomRow';
|
||||
|
||||
describe('RoomRow', () => {
|
||||
const tempRoom: IRoom = {
|
||||
id: 1,
|
||||
name: 'Temp Room',
|
||||
price: 2399,
|
||||
status: false,
|
||||
isDelete: true,
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RoomRow room={tempRoom} />
|
||||
</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 RoomTable from '@src/pages/Room/RoomTable';
|
||||
|
||||
describe('UserTable', () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<RoomTable />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
test('Should render correctly', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,37 +1,138 @@
|
||||
// Components
|
||||
import RoomTable from './RoomTable';
|
||||
import RoomForm from './RoomForm';
|
||||
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 { StyledRoom, Title } from './styled';
|
||||
import Direction from '@src/commons/styles/Direction';
|
||||
import Button from '@src/commons/styles/Button';
|
||||
import { ActionTable, StyledRoom, Title } from './styled';
|
||||
import Spinner from '@src/commons/styles/Spinner.ts';
|
||||
|
||||
// Types
|
||||
import { ColumnProps } from '@src/types/common.ts';
|
||||
import { IRoom } from '@src/types/room.ts';
|
||||
|
||||
// Hooks
|
||||
import { useRooms } from '@src/hooks/rooms/useRooms.ts';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface IRoomTable extends Omit<IRoom, 'status'> {
|
||||
status: string;
|
||||
}
|
||||
|
||||
const Room = () => {
|
||||
const [itemSelected, setItemSelected] = useState<IRoomTable>();
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Id',
|
||||
width: 10
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
width: 40
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
title: 'Price',
|
||||
width: 20
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'Status',
|
||||
width: 20
|
||||
}
|
||||
];
|
||||
const { isLoading, rooms, count } = useRooms();
|
||||
|
||||
// Reset value when rooms changed.
|
||||
useEffect(() => {
|
||||
setItemSelected(undefined);
|
||||
}, [rooms]);
|
||||
|
||||
const tempRooms = rooms?.map((room) => ({
|
||||
...room,
|
||||
status: room.status
|
||||
? 'Unavailable'
|
||||
: 'Available'
|
||||
}));
|
||||
|
||||
return (
|
||||
<StyledRoom>
|
||||
<Direction type="horizontal">
|
||||
<Title>List Room</Title>
|
||||
|
||||
<Modal>
|
||||
<Modal.Open
|
||||
modalName="room-form"
|
||||
renderChildren={(onCloseModal) => (
|
||||
<Button onClick={onCloseModal}>Add room</Button>
|
||||
)}
|
||||
/>
|
||||
<Modal.Window
|
||||
name="room-form"
|
||||
title="Add form"
|
||||
renderChildren={(onCloseModal) => (
|
||||
<RoomForm onCloseModal={onCloseModal} />
|
||||
)}
|
||||
/>
|
||||
<ActionTable>
|
||||
<Modal.Open
|
||||
modalName="room-form"
|
||||
renderChildren={(onCloseModal) => (
|
||||
<ButtonIcon
|
||||
icon={<MdOutlineAddCircleOutline />}
|
||||
text={'Add Room'}
|
||||
iconSize={'18px'}
|
||||
fontSize={'var(--fs-sm)'}
|
||||
variations={'primary'}
|
||||
iconColor={'white'}
|
||||
onClick={onCloseModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal.Window
|
||||
name="room-form"
|
||||
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)}
|
||||
/>
|
||||
</ActionTable>
|
||||
</Modal>
|
||||
</Direction>
|
||||
|
||||
<RoomTable />
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{
|
||||
tempRooms &&
|
||||
Boolean(tempRooms.length) &&
|
||||
<Table<IRoomTable>
|
||||
columns={columns}
|
||||
rows={tempRooms}
|
||||
count={count}
|
||||
enabledSort={true}
|
||||
enabledOrder={true}
|
||||
enabledSearch={true}
|
||||
stateSelected={{
|
||||
itemSelected,
|
||||
setItemSelected,
|
||||
}}
|
||||
onRowClick={(data) => {
|
||||
setItemSelected(data);
|
||||
}} />}
|
||||
</StyledRoom>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
StyledRoom,
|
||||
Title,
|
||||
StyledOperationTable
|
||||
StyledTableOption,
|
||||
ActionTable
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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
|
||||
import RoomTable from '@src/pages/Room/RoomTable';
|
||||
|
||||
describe('UserTable', () => {
|
||||
const queryClient = new QueryClient();
|
||||
@@ -11,7 +11,7 @@ describe('UserTable', () => {
|
||||
const wrapper = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<RoomTable />
|
||||
<UserTable />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ERROR_DELETE_ROOM,
|
||||
ERROR_FETCHING_ROOM,
|
||||
ERROR_UPDATE_ROOM,
|
||||
ROOMS_TABLE,
|
||||
ROOMS_TABLE
|
||||
} from '@src/constants/messages';
|
||||
|
||||
interface IGetAllRooms {
|
||||
@@ -26,11 +26,11 @@ interface IGetAllRooms {
|
||||
* @returns Return all rooms in database
|
||||
*/
|
||||
const getAllRooms = async ({
|
||||
sortBy,
|
||||
orderBy,
|
||||
roomSearch,
|
||||
page,
|
||||
}: IGetAllRooms): Promise<{ data: IRoom[]; count: number | null }> => {
|
||||
sortBy,
|
||||
orderBy,
|
||||
roomSearch,
|
||||
page
|
||||
}: IGetAllRooms): Promise<{ data: IRoom[]; count: number | null }> => {
|
||||
const from = (page - 1) * DEFAULT_PAGE_SIZE;
|
||||
const to = from + DEFAULT_PAGE_SIZE - 1;
|
||||
|
||||
@@ -157,5 +157,5 @@ export {
|
||||
createRoom,
|
||||
setIsDeleteRoom,
|
||||
getRoomById,
|
||||
updateRoomStatus,
|
||||
updateRoomStatus
|
||||
};
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
:root {
|
||||
--primary-color: #0010ba;
|
||||
--primary-hover-color: #0010ba8c;
|
||||
|
||||
--header-table-color: #ebebeb;
|
||||
--item-selected: #f5f5f5;
|
||||
--border-color: #8c8c8c;
|
||||
--footer-table-color: #ebebeb;
|
||||
|
||||
--secondary-btn-color: #d1d1d1;
|
||||
--disabled-btn-color: #7f82a6;
|
||||
|
||||
--danger-btn-color: #ff4f4f;
|
||||
--danger-btn-hover-color: #ff4f4f8c;
|
||||
|
||||
--success-btn-color: #228b22;
|
||||
--success-btn-hover-color: #228b2299;
|
||||
|
||||
--hover-background-color: #f3f4f6;
|
||||
--hover-dark-background-color: #d0d0d0;
|
||||
|
||||
Reference in New Issue
Block a user