Fix comments

This commit is contained in:
2023-11-08 17:46:26 +07:00
parent a99d81a411
commit 8fce206f7f
35 changed files with 471 additions and 342 deletions
+2 -1
View File
@@ -49,6 +49,7 @@
"husky": "^8.0.0",
"lint-staged": "^15.0.1",
"typescript": "^5.0.2",
"vite": "^4.4.5"
"vite": "^4.4.5",
"vite-tsconfig-paths": "^4.2.1"
}
}
+37
View File
@@ -70,6 +70,9 @@ devDependencies:
vite:
specifier: ^4.4.5
version: 4.4.11
vite-tsconfig-paths:
specifier: ^4.2.1
version: 4.2.1(typescript@5.2.2)(vite@4.4.11)
packages:
@@ -1406,6 +1409,10 @@ packages:
slash: 3.0.0
dev: true
/globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
dev: true
/goober@2.1.13(csstype@3.1.2):
resolution: {integrity: sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==}
peerDependencies:
@@ -2088,6 +2095,19 @@ packages:
typescript: 5.2.2
dev: true
/tsconfck@2.1.2(typescript@5.2.2):
resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==}
engines: {node: ^14.13.1 || ^16 || >=18}
hasBin: true
peerDependencies:
typescript: ^4.3.5 || ^5.0.0
peerDependenciesMeta:
typescript:
optional: true
dependencies:
typescript: 5.2.2
dev: true
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: false
@@ -2132,6 +2152,23 @@ packages:
punycode: 2.3.0
dev: true
/vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@4.4.11):
resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==}
peerDependencies:
vite: '*'
peerDependenciesMeta:
vite:
optional: true
dependencies:
debug: 4.3.4
globrex: 0.1.2
tsconfck: 2.1.2(typescript@5.2.2)
vite: 4.4.11
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/vite@4.4.11:
resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -1,22 +0,0 @@
import styled from 'styled-components';
const ButtonIcon = styled.button`
background: none;
padding: 8px;
transition: all 0.2s;
border: none;
border-radius: var(--radius-md);
&:hover {
background-color: var(--hover-background-color);
}
& svg {
width: 25px;
height: 25px;
}
`;
export default ButtonIcon;
@@ -1,8 +1,8 @@
import { Outlet } from 'react-router-dom';
// Components
import Header from '../Header';
import Sidebar from '../Sidebar';
import Header from '@component/Header';
import Sidebar from '@component/Sidebar';
// Styled
import { Main, StyledAppLayout } from './styled';
@@ -0,0 +1,131 @@
import { COLOR, SIZE } from '@constant/styles';
import { ReactNode } from 'react';
import styled, { css } from 'styled-components';
interface IStyledButtonIcon {
isHaveChildren: boolean;
style?: {
fontSize?: string;
backgroundColor?: string;
color?: string;
};
iconStyle?: {
color?: string;
size?: string;
};
}
const StyledButtonIcon = styled.button<IStyledButtonIcon>`
background: none;
padding: 10px;
transition: all 0.2s;
border: none;
${(props) =>
props.style?.fontSize &&
css`
font-size: ${props.style.fontSize};
`}
${(props) =>
props.style?.fontSize &&
css`
font-size: ${props.style.fontSize};
`}
${(props) =>
props.color &&
css`
font-size: ${props.color};
`}
${(props) =>
props.isHaveChildren
? css`
display: flex;
align-items: center;
gap: 10px;
text-align: left;
width: 100%;
`
: css`
border-radius: var(--radius-md);
`}
&:hover {
background-color: var(--hover-background-color);
}
& svg {
${(props) =>
props.iconStyle?.size &&
css`
width: ${props.iconStyle.size};
height: ${props.iconStyle.size};
`}
${(props) =>
props.iconStyle?.color &&
css`
color: ${props.iconStyle.color};
`}
transition: all 0.3s;
}
`;
StyledButtonIcon.defaultProps = {
style: {
fontSize: SIZE.DEFAULT,
backgroundColor: COLOR.DEFAULT,
color: COLOR.BLACK,
},
iconStyle: {
color: COLOR.BLACK,
size: SIZE.DEFAULT,
},
};
interface IButtonIcon {
icon: ReactNode;
children?: ReactNode;
style?: {
fontSize?: string;
backgroundColor?: string;
color?: string;
};
iconStyle?: {
color?: string;
size?: string;
};
onClick?: () => void;
}
const ButtonIcon = ({
icon,
children,
style,
iconStyle,
onClick,
}: IButtonIcon) => {
const isHaveChildren = Boolean(children);
return (
<StyledButtonIcon
isHaveChildren={isHaveChildren}
style={{
fontSize: style?.fontSize,
backgroundColor: style?.backgroundColor,
color: style?.color,
}}
iconStyle={iconStyle}
onClick={onClick}
>
{icon} <span>{children}</span>
</StyledButtonIcon>
);
};
export default ButtonIcon;
@@ -1,7 +1,7 @@
// Components
import { IoPersonCircleOutline } from 'react-icons/io5';
import { HiOutlineLogout } from 'react-icons/hi';
import ButtonIcon from '../../commons/styles/ButtonIcon';
import ButtonIcon from '@component/ButtonIcon/ButtonIcon';
// Styled
import { StyledHeaderMenu } from './styled';
@@ -10,14 +10,18 @@ const HeaderMenu = () => {
return (
<StyledHeaderMenu>
<li>
<ButtonIcon aria-label="Profile">
<IoPersonCircleOutline />
</ButtonIcon>
<ButtonIcon
aria-label="Profile"
icon={<IoPersonCircleOutline />}
iconStyle={{ size: '23px' }}
/>
</li>
<li>
<ButtonIcon aria-label="Logout">
<HiOutlineLogout />
</ButtonIcon>
<ButtonIcon
aria-label="Logout"
icon={<HiOutlineLogout />}
iconStyle={{size: '23px' }}
/>
</li>
</StyledHeaderMenu>
);
@@ -4,16 +4,18 @@ import { MouseEvent, ReactNode, useContext, useState } from 'react';
import { HiEllipsisVertical } from 'react-icons/hi2';
// Hooks
import { useOutsideClick } from '../../hooks/useOutsideClick';
import { useOutsideClick } from '@hook/useOutsideClick';
// Styled
import { StyledMenu, StyledButton, StyledList, StyledToggle } from './styled';
import { StyledMenu, StyledList, StyledToggle } from './styled';
// Contexts
import MenusContext from '../../contexts/MenuContext';
import MenusContext from '@context/MenuContext';
// Types
import { Nullable } from '../../types/common';
import { Nullable } from '@type/common';
import ButtonIcon from '@component/ButtonIcon/ButtonIcon';
import { COLOR } from '@constant/styles';
interface IButton {
children?: string;
@@ -74,9 +76,14 @@ const Button = ({ children, icon, onClick }: IButton): ReactNode => {
return (
<li>
<StyledButton onClick={handleClick}>
{icon} <span>{children}</span>
</StyledButton>
<ButtonIcon
icon={icon}
onClick={handleClick}
iconStyle={{ color: COLOR.PRIMARY, size: '19px' }}
style={{ fontSize: '16px' }}
>
{children}
</ButtonIcon>
</li>
);
};
@@ -8,10 +8,10 @@ import {
import { createPortal } from 'react-dom';
// Hooks
import { useOutsideClick } from '../../hooks/useOutsideClick';
import { useOutsideClick } from '@hook/useOutsideClick';
// Contexts
import { ModalContext } from '../../contexts/ModalContext';
import { ModalContext } from '@context/ModalContext';
// Styled
import { Overlay, StyledModal, StyledModalContent, TitleModal } from './styled';
@@ -34,14 +34,14 @@ const Modal = ({ children }: IModal) => {
};
interface IOpen {
children: ReactElement;
renderChildren: (onCloseModal: () => void) => ReactNode;
modalName: string;
}
const Open = ({ children, modalName }: IOpen) => {
const Open = ({ renderChildren, modalName }: IOpen) => {
const { open } = useContext(ModalContext);
return cloneElement(children, { onClick: () => open!(modalName) });
return renderChildren(() => open!(modalName));
};
interface IWindow {
@@ -54,9 +54,6 @@ const Window = ({ children, name, title }: IWindow) => {
const { openName, close } = useContext(ModalContext);
const ref = useOutsideClick<HTMLDivElement>(close!);
console.log('name: ', name);
console.log('openName: ', openName);
if (name !== openName) return null;
return createPortal(
@@ -7,7 +7,7 @@ import { BsHouseDoorFill } from 'react-icons/bs';
import { StyledNav, StyledNavLink } from './styled';
// Constants
import { DASHBOARD, ROOM, USER } from '../../constants/path';
import { DASHBOARD, ROOM, USER } from '@constant/path';
const Nav = () => {
return (
@@ -1,4 +1,3 @@
import { memo } from 'react';
import { useSearchParams } from 'react-router-dom';
// Styled
@@ -11,7 +10,7 @@ interface IOrderProps {
}[];
}
const OrderBy = memo(({ options }: IOrderProps) => {
const OrderBy = ({ options }: IOrderProps) => {
const field = 'orderBy';
const [searchParams, setSearchParams] = useSearchParams();
const currentOrder = searchParams.get(field) || options[0].value;
@@ -35,6 +34,6 @@ const OrderBy = memo(({ options }: IOrderProps) => {
))}
</StyledOrder>
);
});
};
export default OrderBy;
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { StyledSearch } from './styled';
// Hooks
import { useDebounce } from '../../hooks/useDebounce';
import { useDebounce } from '@hook/useDebounce';
interface ISearch {
setPlaceHolder: string;
@@ -1,10 +1,9 @@
import {
FieldValues,
RegisterOptions,
UseFormRegister,
useFormContext,
} from 'react-hook-form';
import { ChangeEventHandler, ReactNode } from 'react';
import { ChangeEventHandler } from 'react';
// Styled
import { StyledSelect } from './styled';
@@ -22,46 +21,6 @@ interface ISelect {
ariaLabel: string;
}
interface IRender extends ISelect {
register: UseFormRegister<FieldValues>;
children: ReactNode[];
}
const RenderSelect = ({
options,
id,
value,
optionsConfigForm,
onChange,
register,
children,
ariaLabel,
}: IRender) => {
if (options && !register) {
return (
<StyledSelect
aria-label={ariaLabel}
id={id}
value={value}
onChange={onChange}
>
{children}
</StyledSelect>
);
}
return (
<StyledSelect
aria-label={ariaLabel}
id={id}
value={value}
{...register(id!, optionsConfigForm)}
>
{children}
</StyledSelect>
);
};
const Select = ({
options,
id,
@@ -75,21 +34,20 @@ const Select = ({
if (!options) return;
return (
<RenderSelect
register={register}
options={options}
optionsConfigForm={optionsConfigForm}
<StyledSelect
aria-label={ariaLabel}
id={id}
value={value}
onChange={onChange}
ariaLabel={ariaLabel}
{...(register
? { ...register(id!, optionsConfigForm) }
: { onChange: onChange })}
>
{options.map((option) => (
<option value={option.value} key={option.value}>
{option.label}
</option>
))}
</RenderSelect>
</StyledSelect>
);
};
@@ -1,5 +1,5 @@
// Components
import Nav from '../Nav';
import Nav from '@component/Nav';
// Styled
import { Heading, StyledSidebar } from './styled';
@@ -1,8 +1,8 @@
import { useSearchParams } from 'react-router-dom';
import { ChangeEvent, memo } from 'react';
import { ChangeEvent } from 'react';
// Components
import Select from '../Select';
import Select from '@component/Select';
interface ISortByProps {
options: {
@@ -11,7 +11,7 @@ interface ISortByProps {
}[];
}
const SortBy = memo(({ options }: ISortByProps) => {
const SortBy = ({ options }: ISortByProps) => {
const [searchParams, setSearchParams] = useSearchParams();
const sortBy = searchParams.get('sortBy') || '';
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
@@ -27,6 +27,6 @@ const SortBy = memo(({ options }: ISortByProps) => {
ariaLabel="Sort"
/>
);
});
};
export default SortBy;
@@ -4,13 +4,17 @@ import { ReactNode, useContext } from 'react';
import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled';
// Contexts
import TableContext from '../../contexts/TableContext';
import TableContext from '@context/TableContext';
export interface ITable {
columns?: string;
children: ReactNode;
}
export interface IHeader {
headerColumn: string[];
}
interface ITableBody<T> {
data?: T[];
render?: CallbackMapFunc<T>;
@@ -26,10 +30,12 @@ const Table = ({ columns, children }: ITable) => {
);
};
const Header = ({ children }: ITable) => {
const Header = ({ headerColumn }: IHeader) => {
const { columns } = useContext(TableContext);
return <StyledHeader columns={columns}>{children}</StyledHeader>;
return <StyledHeader columns={columns}>
{headerColumn.map((item) => <div key={item}>{item}</div>)}
</StyledHeader>;
};
const Body = <T,>({ data, render }: ITableBody<T>) => {
@@ -51,9 +51,6 @@ const ROOM_PAGE = {
],
};
const VALUE = 'value';
const ERROR = 'error';
const INIT_VALUE_USER_FORM = {
name: '',
id: 0,
@@ -68,11 +65,23 @@ const INIT_VALUE_ROOM_FORM = {
discount: 0,
};
const FORM = {
EDIT: 'edit',
USER: 'user-form',
ROOM: 'room-form',
};
const REGEX = {
PHONE: /(84|0[3|5|7|8|9])+([0-9]{8})\b/g,
NAME: /^[a-zA-Z]{2,}(?: [a-zA-Z]+){0,2}$/gm,
NUMBER: /^[0-9]*$/,
};
export {
USER_PAGE,
ROOM_PAGE,
VALUE,
ERROR,
FORM,
REGEX,
ORDERBY_OPTIONS,
INIT_VALUE_USER_FORM,
INIT_VALUE_ROOM_FORM,
+11
View File
@@ -0,0 +1,11 @@
const COLOR = {
PRIMARY: '#0010ba',
BLACK: '#000',
DEFAULT: 'transparent',
};
const SIZE = {
DEFAULT: '14px',
};
export { COLOR, SIZE };
+2 -2
View File
@@ -1,8 +1,8 @@
// Constants
import { BASE_URL } from '../constants/path';
import { BASE_URL } from '@constant/path';
// Types
import { IResponse } from '../types/responses';
import { IResponse } from '@type/responses';
type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
+7 -32
View File
@@ -1,20 +1,12 @@
/**
* Check value is valid name or not
* @param value Values need to be checked
* @returns A boolean indicating whether or not the argument has valid
*/
const isValidName = (value: string): boolean => {
return /^[a-zA-Z]{2,}(?: [a-zA-Z]+){0,2}$/gm.test(value);
};
/**
* Check value is valid number or not
* Compare the regex with value
* @param regex The regex to compare
* @param value Value need to be checked
* @returns A boolean indicating whether or not the argument has valid
* @returns A boolean indicating whether of not the argument has valid
*/
const isValidNumber = (value: string): boolean => {
return /^[0-9]*$/.test(value);
};
const isValidRegex = (regex: RegExp, value: string): boolean => {
return regex.test(value);
}
/**
* Check value is valid discount or not
@@ -25,14 +17,6 @@ const isValidDiscount = (value: number): boolean => {
return value >= 0 && value <= 100;
};
/**
* Check value is valid phone number or not
* @param value Value need to be checked
* @returns A boolean indicating whether or not the argument has valid
*/
const isValidPhoneNumber = (value: string): boolean => {
return /(84|0[3|5|7|8|9])+([0-9]{8})\b/g.test(value);
};
/**
* Check value is valid string or not
@@ -43,12 +27,6 @@ const isValidString = (value: string): boolean => {
return value.trim().length >= 5;
};
/**
* A method skip check value
* @returns Return true
*/
const skipCheck = () => true;
/**
* Check object is empty or not
* @param obj Object need to be check
@@ -59,11 +37,8 @@ const isEmptyObj = (obj: object) => {
};
export {
isValidName,
isValidNumber,
isValidPhoneNumber,
isValidRegex,
isValidString,
skipCheck,
isValidDiscount,
isEmptyObj,
};
+4 -4
View File
@@ -1,14 +1,14 @@
import { useEffect, useState } from 'react';
// Constants
import { BASE_URL } from '../constants/path';
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config';
import { BASE_URL } from '@constant/path';
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '@constant/config';
// Helpers
import { searchQuery } from '../helpers/helper';
import { searchQuery } from '@helper/helper';
// Types
import { Nullable } from '../types/common';
import { Nullable } from '@type/common';
/**
* The function use to fetch data on server API.
+1 -1
View File
@@ -1,7 +1,7 @@
import { ForwardedRef, useEffect, useRef } from 'react';
// Types
import { Nullable } from '../types/common';
import { Nullable } from '@type/common';
const useForwardRef = <T>(
ref: ForwardedRef<T>,
@@ -1,7 +1,7 @@
import { MutableRefObject, useEffect, useRef } from 'react';
// Types
import { Nullable } from '../types/common';
import { Nullable } from '@type/common';
/**
* Custom hook to call handler when click outside element
+16 -19
View File
@@ -2,25 +2,26 @@ import { useSearchParams } from 'react-router-dom';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
// Components
import { StyledOperationTable } from './styled';
import Menus from '../../components/Menus';
import Table from '../../components/Table';
import Direction from '../../commons/styles/Direction';
import Message from '../../components/Message';
import Search from '../../components/Search';
import SortBy from '../../components/SortBy';
import OrderBy from '../../components/OrderBy';
import Menus from '@component/Menus';
import Table from '@component/Table';
import Message from '@component/Message';
import Search from '@component/Search';
import SortBy from '@component/SortBy';
import OrderBy from '@component/OrderBy';
import RoomRow from './RoomRow';
// Types
import { IRoom } from '../../types/rooms';
import { IRoom } from '@type/rooms';
// Constants
import { useFetch } from '../../hooks/useFetch';
import { ORDERBY_OPTIONS, ROOM_PAGE } from '../../constants/variables';
import { useFetch } from '@hook/useFetch';
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons';
// Styled
import Spinner from '../../commons/styles/Spinner';
import RoomRow from './RoomRow';
import { StyledOperationTable } from './styled';
import Direction from '@commonStyle/Direction';
import Spinner from '@commonStyle/Spinner';
interface IRoomTable {
reload: boolean;
@@ -28,6 +29,7 @@ interface IRoomTable {
}
const RoomTable = ({ reload, setReload }: IRoomTable) => {
const columnName = ['Id', 'Name', 'Price', 'Status'];
const [nameSearch, setNameSearch] = useState('');
const [searchParams] = useSearchParams();
const [rooms, setRooms] = useState<IRoom[]>([]);
@@ -77,12 +79,7 @@ const RoomTable = ({ reload, setReload }: IRoomTable) => {
{rooms.length ? (
<Menus>
<Table columns="10% 40% 20% 20% 5%">
<Table.Header>
<div>Id</div>
<div>Name</div>
<div>Price</div>
<div>Status</div>
</Table.Header>
<Table.Header headerColumn={columnName}/>
<Table.Body<IRoom>
data={rooms}
render={(room: IRoom) => (
+34 -36
View File
@@ -6,36 +6,35 @@ import styled from 'styled-components';
import { useForm } from 'react-hook-form';
// Styled
import Button from '../../commons/styles/Button.ts';
import Input from '../../commons/styles/Input.ts';
import Button from '@commonStyle/Button.ts';
import Input from '@commonStyle/Input.ts';
// Types
import { Nullable } from '../../types/common.ts';
import { IRoom } from '../../types/rooms.ts';
import { Nullable } from '@type/common.ts';
import { IRoom } from '@type/rooms.ts';
// Constants
import {
ADD_SUCCESS,
EDIT_SUCCESS,
} from '../../constants/messages.ts';
import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts';
import {
INVALID_DISCOUNT,
INVALID_FIELD,
REQUIRED_FIELD_ERROR,
} from '../../constants/formValidateMessage.ts';
} from '@constant/formValidateMessage.ts';
import { INIT_VALUE_ROOM_FORM, REGEX } from '../../constants/commons.ts';
// Helpers
import {
isValidDiscount,
isValidNumber,
isValidRegex,
isValidString,
} from '../../helpers/validators.ts';
} from '@helper/validators.ts';
// Components
import FormRow from '../../components/LabelControl/index.tsx';
import Form from '../../components/Form/index.tsx';
import { addRoom, updateRoom } from '../../services/roomServices.ts';
import { INIT_VALUE_ROOM_FORM } from '../../constants/variables.ts';
import FormRow from '@component/LabelControl/index.tsx';
import Form from '@component/Form/index.tsx';
// Services
import { addRoom, updateRoom } from '@service/roomServices.ts';
const FormBtn = styled(Button)`
width: 100%;
@@ -82,30 +81,25 @@ const RoomForm = ({
// Calculate final price
room.finalPrice = room.price - (room.price * room.discount) / 100;
try {
if (!roomEdit) {
// Add request
const response = await addRoom(room);
if(response) {
toast.success(ADD_SUCCESS);
}
} else {
// Edit request
const response = await updateRoom(room);
if (!roomEdit) {
// Add request
const response = await addRoom(room);
if (response) {
toast.success(EDIT_SUCCESS);
}
if (response) {
toast.success(ADD_SUCCESS);
}
// Reload table data
setReload!(!reload);
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(error.message);
} else {
// Edit request
const response = await updateRoom(room);
if (response) {
toast.success(EDIT_SUCCESS);
}
}
// Reload table data
setReload!(!reload);
reset();
onCloseModal!();
};
@@ -136,7 +130,7 @@ const RoomForm = ({
required: REQUIRED_FIELD_ERROR,
validate: {
checkIdentifiedCode: (v) =>
isValidNumber(v.toString()) || INVALID_FIELD,
isValidRegex(REGEX.NUMBER, v.toString()) || INVALID_FIELD,
},
onChange: () => trigger('price'),
})}
@@ -159,7 +153,11 @@ const RoomForm = ({
</FormRow>
<Form.Action>
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid || isSubmitting}>
<FormBtn
type="submit"
name="submit"
disabled={!isDirty || !isValid || isSubmitting}
>
{
!roomEdit
? 'Add'
+21 -14
View File
@@ -2,22 +2,22 @@ import { Dispatch, SetStateAction } from 'react';
import toast from 'react-hot-toast';
// Constants
import { ROOM_PATH } from '../../constants/path';
import { STATUS_CODE } from '../../constants/responseStatus';
import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages';
import { ROOM_PATH } from '@constant/path';
import { STATUS_CODE } from '@constant/responseStatus';
import { CONFIRM_DELETE, DELETE_SUCCESS } from '@constant/messages';
// Helpers
import { sendRequest } from '../../helpers/sendRequest';
import { formatCurrency } from '../../helpers/helper';
import Modal from '../../components/Modal';
import RoomForm from './RoomForm';
import { sendRequest } from '@helper/sendRequest';
import { formatCurrency } from '@helper/helper';
// Types
import { IRoom } from '../../types/rooms';
import { IRoom } from '@type/rooms';
// Components
import Table from '../../components/Table';
import Menus from '../../components/Menus';
import RoomForm from './RoomForm';
import Modal from '@component/Modal';
import Table from '@component/Table';
import Menus from '@component/Menus';
import { RiEditBoxFill } from 'react-icons/ri';
import { HiTrash } from 'react-icons/hi';
@@ -43,7 +43,9 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => {
const { id, name, finalPrice, status } = room;
const statusText = status ? 'Unavailable' : 'Available';
const statusText = status
? 'Unavailable'
: 'Available';
return (
<Table.Row>
@@ -58,9 +60,14 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => {
<Menus.Toggle id={id.toString()} />
<Menus.List id={id.toString()}>
<Modal.Open modalName="edit">
<Menus.Button icon={<RiEditBoxFill />}>Edit</Menus.Button>
</Modal.Open>
<Modal.Open
modalName="edit"
renderChildren={(onCloseModal) => (
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}>
Edit
</Menus.Button>
)}
/>
<Menus.Button
icon={<HiTrash />}
+11 -11
View File
@@ -2,15 +2,15 @@ import { useState } from 'react';
// Components
import RoomTable from './RomTable';
import RoomForm from './RoomForm';
// Styled
import { StyledRoom, Title } from './styled';
import Direction from '../../commons/styles/Direction';
import Button from '../../commons/styles/Button';
import Direction from '@commonStyle/Direction.ts';
import Button from '@commonStyle/Button';
// Types
import Modal from '../../components/Modal';
import RoomForm from './RoomForm';
import Modal from '@component/Modal';
const Room = () => {
const [reload, setReload] = useState(true);
@@ -22,19 +22,19 @@ const Room = () => {
<Title>List Room</Title>
<Modal>
<Modal.Open modalName="room-form">
<Button>Add room</Button>
</Modal.Open>
<Modal.Open
modalName="room-form"
renderChildren={(onCloseModal) => (
<Button onClick={onCloseModal}>Add room</Button>
)}
/>
<Modal.Window name="room-form" title="Add form">
<RoomForm setReload={setReload} reload={reload} />
</Modal.Window>
</Modal>
</Direction>
<RoomTable
reload={reload}
setReload={setReload}
/>
<RoomTable reload={reload} setReload={setReload} />
</StyledRoom>
</>
);
+36 -45
View File
@@ -11,41 +11,38 @@ import toast from 'react-hot-toast';
import { FormProvider, useForm } from 'react-hook-form';
// Styled
import Input from '../../commons/styles/Input.ts';
import Input from '@commonStyle/Input.ts';
// Components
import Form from '../../components/Form/index.tsx';
import FormRow from '../../components/LabelControl/index.tsx';
import Select, { ISelectOptions } from '../../components/Select/index.tsx';
import Form from '@component/Form/index.tsx';
import FormRow from '@component/LabelControl/index.tsx';
import Select, { ISelectOptions } from '@component/Select/index.tsx';
// Helpers
import {
isEmptyObj,
isValidName,
isValidNumber,
isValidPhoneNumber,
} from '../../helpers/validators.ts';
isEmptyObj, isValidRegex,
} from '@helper/validators.ts';
// Constants
import { ADD_SUCCESS, EDIT_SUCCESS } from '../../constants/messages.ts';
import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts';
import {
INVALID_FIELD,
INVALID_PHONE,
REQUIRED_FIELD_ERROR,
} from '../../constants/formValidateMessage.ts';
import { INIT_VALUE_USER_FORM } from '../../constants/variables.ts';
} from '@constant/formValidateMessage.ts';
import { INIT_VALUE_USER_FORM, REGEX } from '@constant/commons.ts';
// Styled
import { FormBtn } from './styled.ts';
// Services
import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts';
import { getAllRoom, updateRoomStatus } from '@service/roomServices.ts';
import { createUser, updateUser } from '@service/userServices.ts';
// Types
import { Nullable } from '../../types/common.ts';
import { IUser } from '../../types/users.ts';
import { IRoom } from '../../types/rooms.ts';
import { createUser, updateUser } from '../../services/userServices.ts';
import { Nullable } from '@type/common.ts';
import { IUser } from '@type/users.ts';
import { IRoom } from '@type/rooms.ts';
interface IUserFormProp {
onCloseModal?: () => void;
@@ -109,35 +106,29 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
// Submit form
const onSubmit = useCallback(
async (newUser: IUser) => {
try {
if (!user) {
// Add request
const response = await createUser(newUser);
if (!user) {
// Add request
const response = await createUser(newUser);
if (response) {
toast.success(ADD_SUCCESS);
}
// Update room status
updateRoomStatus(newUser.roomId, true);
} else {
// Edit request
const response = await updateUser(newUser);
if (response) {
toast.success(EDIT_SUCCESS);
}
// Update room status
updateRoomStatus(user!.roomId, true, newUser.roomId);
if (response) {
toast.success(ADD_SUCCESS);
}
// Reload table data
setReload(!reload);
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(error.message);
// Update room status
updateRoomStatus(newUser.roomId, true);
} else {
// Edit request
const response = await updateUser(newUser);
if (response) {
toast.success(EDIT_SUCCESS);
}
// Update room status
updateRoomStatus(user!.roomId, true, newUser.roomId);
}
// Reload table data
setReload(!reload);
reset();
onCloseModal!();
@@ -169,7 +160,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
{...register('name', {
required: REQUIRED_FIELD_ERROR,
validate: {
checkValidName: (value) => isValidName(value) || INVALID_FIELD,
checkValidName: (value) => isValidRegex(REGEX.NAME, value) || INVALID_FIELD,
},
onChange: () => trigger('name'),
})}
@@ -187,7 +178,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
required: REQUIRED_FIELD_ERROR,
validate: {
checkIdentifiedCode: (v) =>
isValidNumber(v.toString()) || INVALID_FIELD,
isValidRegex(REGEX.NUMBER, v.toString()) || INVALID_FIELD,
},
onChange: () => trigger('identifiedCode'),
})}
@@ -201,7 +192,7 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
{...register('phone', {
required: REQUIRED_FIELD_ERROR,
validate: {
checkPhoneNum: (v) => isValidPhoneNumber(v) || INVALID_PHONE,
checkPhoneNum: (v) => isValidRegex(REGEX.PHONE, v) || INVALID_PHONE,
},
onChange: () => trigger('phone'),
})}
+18 -27
View File
@@ -2,31 +2,27 @@ import { useSearchParams } from 'react-router-dom';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
// Components
import Menus from '../../components/Menus';
import Table from '../../components/Table';
import Direction from '../../commons/styles/Direction';
import Message from '../../components/Message';
import Search from '../../components/Search';
import SortBy from '../../components/SortBy';
import OrderBy from '../../components/OrderBy';
// Types
import { IUser } from '../../types/users';
// Hooks
import { useFetch } from '../../hooks/useFetch';
// Constants
import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables';
// Styled
import { StyledOperationTable } from './styled';
import Spinner from '../../commons/styles/Spinner';
import Menus from '@component/Menus';
import Table from '@component/Table';
import Message from '@component/Message';
import Search from '@component/Search';
import SortBy from '@component/SortBy';
import OrderBy from '@component/OrderBy';
import UserRow from './UserRow';
// Types
import { IUser } from '@type/users';
// Hooks
import { useFetch } from '@hook/useFetch';
// Constants
import { ORDERBY_OPTIONS, USER_PAGE } from '@constant/commons';
// Styled
import Direction from '@commonStyle/Direction';
import { StyledOperationTable } from './styled';
import Spinner from '@commonStyle/Spinner';
interface IUserTable {
reload: boolean;
@@ -34,6 +30,7 @@ interface IUserTable {
}
const UserTable = ({ reload, setReload }: IUserTable) => {
const columnName = ['Id', 'Name', 'Identified Code', 'Phone', 'Room Id'];
const [users, setUsers] = useState<IUser[]>([]);
const [phoneSearch, setPhoneSearch] = useState('');
const [searchParams] = useSearchParams();
@@ -83,13 +80,7 @@ const UserTable = ({ reload, setReload }: IUserTable) => {
{users.length ? (
<Menus>
<Table columns="10% 30% 20% 20% 10% 5%">
<Table.Header>
<div>Id</div>
<div>Name</div>
<div>Identified Code</div>
<div>Phone</div>
<div>Room Id</div>
</Table.Header>
<Table.Header headerColumn={columnName} />
<Table.Body<IUser>
data={users}
render={(user: IUser) => (
+23 -13
View File
@@ -4,21 +4,21 @@ import { Dispatch, SetStateAction } from 'react';
// Components
import { RiEditBoxFill } from 'react-icons/ri';
import { IoExit } from 'react-icons/io5';
import Modal from '../../components/Modal';
import Modal from '@component/Modal';
import Table from '@component/Table';
import Menus from '@component/Menus';
import UserForm from './FormUser';
import Table from '../../components/Table';
import Menus from '../../components/Menus';
// Constants
import { STATUS_CODE } from '../../constants/responseStatus';
import { CONFIRM_MESSAGE, DENIED_ACTION } from '../../constants/messages';
import { STATUS_CODE } from '@constant/responseStatus';
import { CONFIRM_MESSAGE, DENIED_ACTION } from '@constant/messages';
// Services
import { updateRoomStatus } from '../../services/roomServices';
import { checkOutUser } from '../../services/userServices';
import { updateRoomStatus } from '@service/roomServices';
import { checkOutUser } from '@service/userServices';
// Types
import { IUser } from '../../types/users';
import { IUser } from '@type/users';
interface IUserRow {
user: IUser;
@@ -56,17 +56,27 @@ const UserRow = ({ user, reload, setReload }: IUserRow) => {
<div>{name}</div>
<div>{identifiedCode}</div>
<div>{phone}</div>
<div>{roomId ? roomId : 'None'}</div>
<div>
{
roomId
? roomId
: 'None'
}
</div>
<div>
<Modal>
<Menus.Menu>
<Menus.Toggle id={id.toString()} />
<Menus.List id={id.toString()}>
<Modal.Open modalName="edit">
<Menus.Button icon={<RiEditBoxFill />}>Edit</Menus.Button>
</Modal.Open>
<Modal.Open
modalName="edit"
renderChildren={(onCloseModal) => (
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}>
Edit
</Menus.Button>
)}
/>
<Menus.Button
icon={<IoExit />}
onClick={() => handleCheckOut(user)}
+14 -7
View File
@@ -4,15 +4,19 @@ import { useState } from 'react';
import UserTable from './TableUser';
// Styled
import Button from '../../commons/styles/Button';
import Direction from '../../commons/styles/Direction';
import Button from '@commonStyle/Button';
import Direction from '@commonStyle/Direction.ts';
import { StyledUser, Title } from './styled';
// Types
import Modal from '../../components/Modal';
import Modal from '@component/Modal';
import UserForm from './FormUser';
// Constants
import { FORM } from '@constant/commons';
const User = () => {
const ADD_ROOM = 'Add room';
const [reload, setReload] = useState(true);
return (
@@ -22,10 +26,13 @@ const User = () => {
<Title>List User</Title>
<Modal>
<Modal.Open modalName="user-form">
<Button>Add user</Button>
</Modal.Open>
<Modal.Window name="user-form" title="Add form">
<Modal.Open
modalName={FORM.USER}
renderChildren={(onCloseModal) => (
<Button onClick={onCloseModal}>Add user</Button>
)}
/>
<Modal.Window name={FORM.USER} title={ADD_ROOM}>
<UserForm setReload={setReload} reload={reload} />
</Modal.Window>
</Modal>
@@ -1,17 +1,17 @@
import toast from 'react-hot-toast';
// Types
import { Nullable } from '../types/common';
import { IResponse } from '../types/responses';
import { IRoom } from '../types/rooms';
import { Nullable } from '@type/common';
import { IResponse } from '@type/responses';
import { IRoom } from '@type/rooms';
// Helpers
import { sendRequest } from '../helpers/sendRequest';
import { errorMsg } from '../helpers/helper';
import { sendRequest } from '@helper/sendRequest';
import { errorMsg } from '@helper/helper';
// Constants
import { STATUS_CODE, RESPONSE_MESSAGE } from '../constants/responseStatus';
import { ROOM_PATH } from '../constants/path';
import { STATUS_CODE, RESPONSE_MESSAGE } from '@constant/responseStatus';
import { ROOM_PATH } from '@constant/path';
/**
* Get all rooms from server
@@ -1,17 +1,17 @@
import toast from 'react-hot-toast';
// Constants
import { USER_PATH } from '../constants/path';
import { STATUS_CODE } from '../constants/responseStatus';
import { USER_PATH } from '@constant/path';
import { STATUS_CODE } from '@constant/responseStatus';
// Types
import { Nullable } from '../types/common';
import { IResponse } from '../types/responses';
import { IUser } from '../types/users';
import { Nullable } from '@type/common';
import { IResponse } from '@type/responses';
import { IUser } from '@type/users';
// Helpers
import { sendRequest } from '../helpers/sendRequest';
import { errorMsg } from '../helpers/helper';
import { sendRequest } from '@helper/sendRequest';
import { errorMsg } from '@helper/helper';
/**
* Create user to the server
+15 -1
View File
@@ -5,6 +5,7 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"types": ["node"],
/* Bundler mode */
"moduleResolution": "bundler",
@@ -18,7 +19,20 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
/* Alias */
"paths": {
"@service/*": ["./src/services/*"],
"@constant/*": ["./src/constants/*"],
"@hook/*": ["./src/hooks/*"],
"@helper/*": ["./src/helpers/*"],
"@context/*": ["./src/contexts/*"],
"@component/*": ["./src/components/*"],
"@commonStyle/*": ["./src/commons/styles/*"],
"@type/*": ["./src/types/*"],
"@page/*": ["./src/pages/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
+2 -1
View File
@@ -1,7 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), tsconfigPaths()],
})