Refactor code

This commit is contained in:
2023-11-02 23:24:03 +07:00
parent cc147f071c
commit 6560c304c3
28 changed files with 163 additions and 157 deletions
@@ -10,8 +10,8 @@ import { Main, StyledAppLayout } from './styled';
const AppLayout = () => {
return (
<StyledAppLayout>
<Header />
<Sidebar />
<Header accountName='Admin'/>
<Sidebar heading='Hotel Management'/>
<Main>
<Outlet />
</Main>
@@ -1,11 +1,19 @@
import { forwardRef } from 'react';
// Components
import { IDialogProps } from '../../globals/interfaces';
// Styled
import { StyledBody, StyledDialog, StyledTitle } from './styled';
export interface IDialogProps<T> {
title?: string;
children?: JSX.Element[] | JSX.Element;
onClose?: () => void;
reload?: boolean;
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
ref?: React.MutableRefObject<HTMLDialogElement | undefined>;
data?: T | null;
isAdd?: boolean;
}
const Dialog = forwardRef((props, ref) => {
const { title, children, onClose } = props as IDialogProps<unknown>;
return (
@@ -4,10 +4,14 @@ import HeaderMenu from '../HeaderMenu';
// Styled
import { StyledHeader } from './styled';
const Header = () => {
interface IHeader {
accountName: string
}
const Header = ({accountName}: IHeader) => {
return (
<StyledHeader>
<p>Hi, Admin!</p>
<p>Hi, {accountName}!</p>
<HeaderMenu />
</StyledHeader>
);
@@ -5,12 +5,15 @@ import { HiEllipsisVertical } from 'react-icons/hi2';
import { useOutsideClick } from '../../hooks/useOutsideClick';
import { StyledMenu, StyledButton, StyledList, StyledToggle } from './styled';
// Interfaces
import { IButton } from '../../globals/interfaces';
// Contexts
import MenusContext from '../../contexts/MenuContext';
interface IButton {
children?: string;
icon?: JSX.Element;
onClick?: () => void;
}
const Menus = ({ children }: { children: JSX.Element }) => {
const [openId, setOpenId] = useState('');
@@ -30,7 +33,6 @@ const Toggle = ({ id }: { id: string }): React.JSX.Element => {
const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
// prettier-ignore
openId === '' || openId !== id
? open!(id)
: close!();
@@ -6,18 +6,21 @@ import { BsHouseDoorFill } from 'react-icons/bs';
// Styled
import { StyledNav, StyledNavLink } from './styled';
// Constants
import { DASHBOARD, ROOM, USER } from '../../constants/path';
const Nav = () => {
return (
<StyledNav>
<StyledNavLink to={'/dashboard'}>
<StyledNavLink to={DASHBOARD}>
<MdSpaceDashboard />
<span>Dashboard</span>
</StyledNavLink>
<StyledNavLink to={'/user'}>
<StyledNavLink to={USER}>
<HiUsers />
<span>User</span>
</StyledNavLink>
<StyledNavLink to={'/room'}>
<StyledNavLink to={ROOM}>
<BsHouseDoorFill />
<span>Room</span>
</StyledNavLink>
@@ -1,6 +1,9 @@
import { ISelectOptions } from '../../globals/interfaces';
import { StyledSelect } from './styled';
export interface ISelectOptions {
value: string;
label: string;
}
interface ISelect {
options: ISelectOptions[];
value: string;
@@ -4,10 +4,14 @@ import Nav from '../Nav';
// Styled
import { Heading, StyledSidebar } from './styled';
const Sidebar = () => {
interface ISidebar {
heading: string;
}
const Sidebar = ({heading}: ISidebar) => {
return (
<StyledSidebar>
<Heading>Hotel Management</Heading>
<Heading>{heading}</Heading>
<Nav />
</StyledSidebar>
);
@@ -1,8 +1,8 @@
import { useSearchParams } from 'react-router-dom';
import { memo } from 'react';
// Components
import Select from '../Select';
import { memo } from 'react';
interface ISortByProps {
options: {
@@ -3,24 +3,31 @@ import { ReactNode, useContext } from 'react';
// Components
import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled';
// Interfaces
import { ITable, ITableBody } from '../../globals/interfaces';
// Contexts
import TableContext from '../../contexts/TableContext';
interface ITable {
columns?: string;
children: React.ReactNode;
}
interface ITableBody<T> {
data?: T[];
render?: (value: T) => JSX.Element;
}
type CallbackMapFunc<T> = (value: T, index: number, array: T[]) => ReactNode;
const Table = ({ columns, children }: ITable) => {
return (
<TableContext.Provider value={columns!}>
<TableContext.Provider value={{ columns }}>
<StyledTable>{children}</StyledTable>
</TableContext.Provider>
);
};
const Header = ({ children }: ITable) => {
const columns = useContext(TableContext);
const { columns } = useContext(TableContext);
return <StyledHeader columns={columns}>{children}</StyledHeader>;
};
@@ -33,7 +40,7 @@ const Body = <T,>({ data, render }: ITableBody<T>) => {
};
const Row = ({ children }: ITable) => {
const columns = useContext(TableContext);
const { columns } = useContext(TableContext);
return <StyledRow columns={columns}>{children}</StyledRow>;
};
+36 -16
View File
@@ -1,26 +1,46 @@
import { Toaster } from 'react-hot-toast';
import { DefaultToastOptions, ToastOptions, ToastPosition, Toaster } from 'react-hot-toast';
import { CSSProperties } from 'styled-components';
const Toast = () => {
interface IToast {
position: ToastPosition;
gutter: number;
containerStyle: CSSProperties;
toastOptions: DefaultToastOptions;
success: ToastOptions;
error: ToastOptions;
style: CSSProperties | undefined;
}
const Toast = ({position, gutter, containerStyle, success, error, style}: IToast) => {
return (
<Toaster
position="top-center"
gutter={12}
containerStyle={{ margin: '8px', zIndex: 1 }}
position={position}
gutter={gutter}
containerStyle={containerStyle}
toastOptions={{
success: {
duration: 3000,
},
error: {
duration: 5000,
},
style: {
fontSize: '16px',
maxWidth: '500px',
padding: '16px 24px',
},
success,
error,
style
}}
/>
);
};
Toast.defaultProps = {
position: "top-center",
gutter: 12,
containerStyle: { margin: '8px', zIndex: 1 },
success: {
duration: 3000,
},
error:{
duration: 5000,
},
style: {
fontSize: '16px',
maxWidth: '500px',
padding: '16px 24px',
}
}
export default Toast;
+5 -5
View File
@@ -1,7 +1,7 @@
export const DASHBOARD: string = '/dashboard';
export const USER: string = '/user';
export const ROOM: string = '/room';
export const OTHER_PATH: string = '*';
export const BASE_URL: string = 'https://hotel-management-api.loiphan.com/';
export const DASHBOARD = '/dashboard';
export const USER = '/user';
export const ROOM = '/room';
export const OTHER_PATH = '*';
export const BASE_URL = 'https://hotel-management-api.loiphan.com/';
export const USER_PATH = 'users';
export const ROOM_PATH = 'rooms';
+10 -1
View File
@@ -74,7 +74,16 @@ const ROOM_PAGE = {
],
}
const INITIAL_STATE_SCHEMA = {
id: '',
name: '',
identifiedCode: '',
phone: '',
roomId: 'undefined',
address: '',
};
const VALUE = 'value';
const ERROR = 'error';
export { USER_PAGE, ROOM_PAGE, VALUE, ERROR };
export { USER_PAGE, ROOM_PAGE, VALUE, ERROR, INITIAL_STATE_SCHEMA };
+5 -2
View File
@@ -1,7 +1,10 @@
import { createContext } from 'react';
// Interfaces
import { IMenusContext } from '../globals/interfaces';
interface IMenusContext {
openId?: string;
close?: () => void;
open?: React.Dispatch<React.SetStateAction<string>>;
}
const MenusContext = createContext<IMenusContext>({});
@@ -1,5 +1,9 @@
import { createContext } from 'react';
const TableContext = createContext('');
interface ITableContext {
columns?: string;
}
const TableContext = createContext<ITableContext>({});
export default TableContext;
@@ -1,46 +0,0 @@
interface IMenusContext {
openId?: string;
close?: () => void;
open?: React.Dispatch<React.SetStateAction<string>>;
}
interface IButton {
children?: string;
icon?: JSX.Element;
onClick?: () => void;
}
interface ITable {
columns?: string;
children: React.ReactNode;
}
interface ITableBody<T> {
data?: T[];
render?: (value: T) => JSX.Element;
}
interface ISelectOptions {
value: string;
label: string;
}
interface IDialogProps<T> {
title?: string;
children?: JSX.Element[] | JSX.Element;
onClose?: () => void;
reload?: boolean;
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
ref?: React.MutableRefObject<HTMLDialogElement | undefined>;
data?: T | null;
isAdd?: boolean;
}
export type {
IMenusContext,
IButton,
ITable,
ITableBody,
IDialogProps,
ISelectOptions,
};
+1 -1
View File
@@ -124,7 +124,7 @@ const searchQuery = (
columnSearch: string,
keySearch: string,
sort: string,
order: string,
order: string
) => {
// prettier-ignore
const phoneParams = keySearch
@@ -13,7 +13,6 @@ const isValidNumber = (value: string): boolean => {
* @returns A boolean indicating whether or not the argument has valid
*/
const isValidDiscount = (value: string): boolean => {
console.log(Boolean(+value >= 0 && +value <= 100));
return +value >= 0 && +value <= 100;
};
+3 -3
View File
@@ -8,7 +8,7 @@ import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config';
import { searchQuery } from '../helpers/utils';
/**
*
* The function use to fetch data on server API.
* @param path The path of url
* @param columnSearch Column need to search
* @param keyWord The key word to search
@@ -23,7 +23,7 @@ const useFetch = (
keyWord: string = '',
tempSortBy?: string,
tempOrderBy?: string,
reload?: boolean,
reload?: boolean
) => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
@@ -56,7 +56,7 @@ const useFetch = (
if (!response.ok)
throw new Error(
`Error code: ${response.status} \n Messages: ${response.statusText}`,
`Error code: ${response.status} \n Messages: ${response.statusText}`
);
setIsPending(false);
+16 -15
View File
@@ -7,7 +7,7 @@ import { getPropValues, isObject, isRequired } from '../helpers/utils';
import { TKeyValue, TValidator } from '../globals/types';
// Constants
import { ERROR, VALUE } from '../constants/variables';
import { ERROR, INITIAL_STATE_SCHEMA, VALUE } from '../constants/variables';
/**
* Custom hooks to validate your Form...
@@ -21,11 +21,11 @@ const useForm = (
stateSchema = {},
stateValidatorSchema = {} as TValidator,
submitFormCallback: (values: TKeyValue) => void,
initialValue: string = '',
initialValue: string = ''
) => {
const [values, setValues] = useState(getPropValues(stateSchema, VALUE));
const [values, setValues] = useState<TKeyValue>(INITIAL_STATE_SCHEMA);
const [errors, setErrors] = useState(getPropValues(stateSchema, ERROR));
const [dirty, setDirty] = useState(getPropValues(stateSchema));
const [valid, isValid] = useState(getPropValues(stateSchema));
const [disable, setDisable] = useState(true);
const [isDirty, setIsDirty] = useState(false);
@@ -33,7 +33,8 @@ const useForm = (
useEffect(() => {
setInitialErrorState(initialValue);
setDisable(true);
setDirty(getPropValues(stateSchema));
isValid(getPropValues(stateSchema));
setValues(getPropValues(stateSchema, VALUE));
// If initial value true, setValues again from stateSchema
// and enabled button
@@ -56,7 +57,7 @@ const useForm = (
let error = '';
// Skip check id field
if (name !== 'id') {
if (name !== 'id' && name !== 'roomId') {
error = isRequired(value, field!.required);
if (isObject(field['validator']) && error === '') {
@@ -72,7 +73,7 @@ const useForm = (
return error;
},
[stateValidatorSchema],
[stateValidatorSchema]
);
// Set Initial Error State
@@ -85,10 +86,10 @@ const useForm = (
[name]: !initialValue // Skip error when initialValue have values
? validateFormFields(name, values[name] as string)
: '',
})),
}))
);
},
[errors, values, validateFormFields],
[errors, values, validateFormFields]
);
// Used to disable submit button if there's a value in errors
@@ -97,7 +98,7 @@ const useForm = (
// in every re-render in component
const validateErrorState = useCallback(
() => Object.values(errors).some((error) => error),
[errors],
[errors]
);
// For every changed in our state this will be fired
@@ -113,7 +114,7 @@ const useForm = (
(
event: ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>,
>
) => {
setIsDirty(true);
@@ -133,9 +134,9 @@ const useForm = (
setValues((prevState) => ({ ...prevState, [name]: value }));
setErrors((prevState) => ({ ...prevState, [name]: error }));
setDirty((prevState) => ({ ...prevState, [name]: true }));
isValid((prevState) => ({ ...prevState, [name]: true }));
},
[validateFormFields],
[validateFormFields]
);
const handleOnSubmit = useCallback(
@@ -151,7 +152,7 @@ const useForm = (
setDisable(true);
}
},
[validateErrorState, submitFormCallback, values],
[validateErrorState, submitFormCallback, values]
);
return {
@@ -162,7 +163,7 @@ const useForm = (
disable,
setValues,
setErrors,
dirty,
valid,
};
};
+1 -4
View File
@@ -1,12 +1,9 @@
import { forwardRef, useEffect } from 'react';
// Components
import Dialog from '../../components/Dialog';
import Dialog, { IDialogProps } from '../../components/Dialog';
import RoomForm from './Form';
// Interfaces
import { IDialogProps } from '../../globals/interfaces';
// Types
import { TRoom } from '../../globals/types';
+9 -9
View File
@@ -8,7 +8,7 @@ import TextArea from '../../commons/styles/TextArea.ts';
// Components
import Form from '../../components/Form/index.tsx';
import FormRow from '../../components/FormRow/index.tsx';
import FormRow from '../../components/LabelControl/index.tsx';
import Button from '../../commons/styles/Button.ts';
// Types
@@ -170,7 +170,7 @@ const RoomForm = ({
const response = await sendRequest(
ROOM_PATH,
JSON.stringify(data),
'POST',
'POST'
);
if (response.statusCode === STATUS_CODE.CREATE) {
@@ -185,7 +185,7 @@ const RoomForm = ({
const response = await sendRequest(
ROOM_PATH + `/${room!.id}`,
JSON.stringify(data),
'PUT',
'PUT'
);
if (response.statusCode == STATUS_CODE.OK) {
@@ -215,7 +215,7 @@ const RoomForm = ({
const {
values,
errors,
dirty,
valid,
handleOnChange,
handleOnSubmit,
disable } =
@@ -257,7 +257,7 @@ const RoomForm = ({
label="Name"
error={
// prettier-ignore
errors.name && dirty.name
errors.name && valid.name
? (errors.name as string)
: ''
}
@@ -274,7 +274,7 @@ const RoomForm = ({
label="Amount"
// prettier-ignore
error={
errors.amount && dirty.amount
errors.amount && valid.amount
? (errors.amount as string)
: ''
}
@@ -291,7 +291,7 @@ const RoomForm = ({
label="Price"
error={
// prettier-ignore
errors.price && dirty.price
errors.price && valid.price
? (errors.price as string)
: ''
}
@@ -308,7 +308,7 @@ const RoomForm = ({
label="Discount"
error={
// prettier-ignore
errors.discount && dirty.discount
errors.discount && valid.discount
? (errors.discount as string)
: ''
}
@@ -334,7 +334,7 @@ const RoomForm = ({
label="Description"
error={
// prettier-ignore
errors.description && dirty.description
errors.description && valid.description
? (errors.description as string)
: ''
}
+1 -1
View File
@@ -6,7 +6,7 @@ import toast from 'react-hot-toast';
import { HiSquare2Stack } from 'react-icons/hi2';
import { HiTrash } from 'react-icons/hi';
import { StyledOperationTable } from './styled';
import Menus from '../../components/Menus/Menus';
import Menus from '../../components/Menus';
import Table from '../../components/Table';
import Direction from '../../commons/styles/Direction';
import Message from '../../components/Message';
+1 -4
View File
@@ -1,12 +1,9 @@
import { forwardRef, useEffect } from 'react';
// Components
import Dialog from '../../components/Dialog';
import Dialog, { IDialogProps } from '../../components/Dialog';
import UserForm from './Form';
// Interfaces
import { IDialogProps } from '../../globals/interfaces';
// Types
import { TUser } from '../../globals/types';
+11 -20
View File
@@ -8,7 +8,7 @@ import TextArea from '../../commons/styles/TextArea';
// Components
import Form from '../../components/Form';
import FormRow from '../../components/FormRow';
import FormRow from '../../components/LabelControl/index.tsx';
import Button from '../../commons/styles/Button.ts';
// Types
@@ -39,12 +39,9 @@ import {
errorMsg,
} from '../../constants/messages.ts';
import { USER_PATH } from '../../constants/path.ts';
import Select from '../../components/Select/index.tsx';
import Select, { ISelectOptions } from '../../components/Select/index.tsx';
import { useFetch } from '../../hooks/useFetch.ts';
// Interfaces
import { ISelectOptions } from '../../globals/interfaces.ts';
const FormBtn = styled(Button)`
width: 100%;
@@ -130,7 +127,7 @@ const UserForm = ({
error: '',
},
roomId: {
value: roomIdValue || '',
value: roomIdValue || '' + (options && options[0].value),
error: '' ,
},
address: {
@@ -181,7 +178,7 @@ const UserForm = ({
const response = await sendRequest(
USER_PATH,
JSON.stringify(data),
'POST',
'POST'
);
if (response.statusCode === STATUS_CODE.CREATE) {
@@ -196,7 +193,7 @@ const UserForm = ({
const response = await sendRequest(
USER_PATH + `/${user!.id}`,
JSON.stringify(data),
'PUT',
'PUT'
);
if (response.statusCode == STATUS_CODE.OK) {
@@ -226,7 +223,7 @@ const UserForm = ({
const {
values,
errors,
dirty,
valid,
handleOnChange,
handleOnSubmit,
disable } =
@@ -266,7 +263,7 @@ const UserForm = ({
label="Full Name"
error={
// prettier-ignore
errors.name && dirty.name
errors.name && valid.name
? (errors.name as string)
: ''
}
@@ -282,7 +279,7 @@ const UserForm = ({
<FormRow
label="Identified Code"
error={
errors.identifiedCode && dirty.identifiedCode
errors.identifiedCode && valid.identifiedCode
? (errors.identifiedCode as string)
: ''
}
@@ -299,7 +296,7 @@ const UserForm = ({
label="Phone"
error={
// prettier-ignore
errors.phone && dirty.phone
errors.phone && valid.phone
? (errors.phone as string)
: ''
}
@@ -316,17 +313,11 @@ const UserForm = ({
label="Room"
error={
// prettier-ignore
errors.roomId && dirty.roomId
errors.roomId && valid.roomId
? (errors.roomId as string)
: ''
}
>
{/* <Input
type="text"
name="roomId"
value={roomId as string}
onChange={handleOnChange}
/> */}
<Select
name="roomId"
value={roomId as string}
@@ -339,7 +330,7 @@ const UserForm = ({
label="Address"
error={
// prettier-ignore
errors.address && dirty.address
errors.address && valid.address
? (errors.address as string)
: ''
}
+1 -1
View File
@@ -6,7 +6,7 @@ import toast from 'react-hot-toast';
import { HiSquare2Stack } from 'react-icons/hi2';
import { HiTrash } from 'react-icons/hi';
import { StyledOperationTable } from './styled';
import Menus from '../../components/Menus/Menus';
import Menus from '../../components/Menus';
import Table from '../../components/Table';
import Direction from '../../commons/styles/Direction';
import Message from '../../components/Message';