mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Refactor code, fix UI and logic code
This commit is contained in:
@@ -1,33 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
// Components
|
|
||||||
import Header from './Header';
|
|
||||||
import Sidebar from './Sidebar';
|
|
||||||
|
|
||||||
const StyledAppLayout = styled.div`
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 300px 1fr;
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
height: 100vh;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Main = styled.main`
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
overflow: scroll;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const AppLayout = () => {
|
|
||||||
return (
|
|
||||||
<StyledAppLayout>
|
|
||||||
<Header />
|
|
||||||
<Sidebar />
|
|
||||||
<Main>
|
|
||||||
<Outlet />
|
|
||||||
</Main>
|
|
||||||
</StyledAppLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppLayout;
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import Header from '../Header';
|
||||||
|
import Sidebar from '../Sidebar';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { Main, StyledAppLayout } from './styled';
|
||||||
|
|
||||||
|
const AppLayout = () => {
|
||||||
|
return (
|
||||||
|
<StyledAppLayout>
|
||||||
|
<Header />
|
||||||
|
<Sidebar />
|
||||||
|
<Main>
|
||||||
|
<Outlet />
|
||||||
|
</Main>
|
||||||
|
</StyledAppLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledAppLayout = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
height: 100vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Main = styled.main`
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
overflow: scroll;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledAppLayout, Main };
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Styled
|
||||||
import { StyledActionBtn, StyledForm } from './styled';
|
import { StyledActionBtn, StyledForm } from './styled';
|
||||||
|
|
||||||
interface IFormProps {
|
interface IFormProps {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Styled
|
||||||
import { Error, Label, StyledFormRow } from './styled';
|
import { Error, Label, StyledFormRow } from './styled';
|
||||||
|
|
||||||
interface IFormRow {
|
interface IFormRow {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const Error = styled.p`
|
|||||||
color: var(--error-text);
|
color: var(--error-text);
|
||||||
font-size: var(--fs-sm-2x);
|
font-size: var(--fs-sm-2x);
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
padding-inline: 5px;
|
||||||
|
width: 220px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledFormRow, Label, Error };
|
export { StyledFormRow, Label, Error };
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Components
|
||||||
|
import HeaderMenu from '../HeaderMenu';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { StyledHeader } from './styled';
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
return (
|
||||||
|
<StyledHeader>
|
||||||
|
<p>Hi, Admin!</p>
|
||||||
|
<HeaderMenu />
|
||||||
|
</StyledHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
+1
-13
@@ -1,8 +1,5 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
// Components
|
|
||||||
import HeaderMenu from './HeaderMenu';
|
|
||||||
|
|
||||||
const StyledHeader = styled.header`
|
const StyledHeader = styled.header`
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
@@ -13,13 +10,4 @@ const StyledHeader = styled.header`
|
|||||||
gap: 30px;
|
gap: 30px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = () => {
|
export { StyledHeader };
|
||||||
return (
|
|
||||||
<StyledHeader>
|
|
||||||
<p>Hi, Admin!</p>
|
|
||||||
<HeaderMenu />
|
|
||||||
</StyledHeader>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
+3
-7
@@ -1,14 +1,10 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { IoPersonCircleOutline } from 'react-icons/io5';
|
import { IoPersonCircleOutline } from 'react-icons/io5';
|
||||||
import { HiOutlineLogout } from 'react-icons/hi';
|
import { HiOutlineLogout } from 'react-icons/hi';
|
||||||
import ButtonIcon from '../commons/styles/ButtonIcon';
|
import ButtonIcon from '../../commons/styles/ButtonIcon';
|
||||||
|
|
||||||
const StyledHeaderMenu = styled.ul`
|
// Styled
|
||||||
display: flex;
|
import { StyledHeaderMenu } from './styled';
|
||||||
gap: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeaderMenu = () => {
|
const HeaderMenu = () => {
|
||||||
return (
|
return (
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledHeaderMenu = styled.ul`
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledHeaderMenu };
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Styled
|
||||||
|
import { StyledMessage } from './styled';
|
||||||
|
|
||||||
|
const Message = ({ children }: { children: string }) => {
|
||||||
|
return <StyledMessage>{children}</StyledMessage>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Message;
|
||||||
+1
-5
@@ -6,8 +6,4 @@ const StyledMessage = styled.p`
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Message = ({ children }: { children: string }) => {
|
export { StyledMessage };
|
||||||
return <StyledMessage>{children}</StyledMessage>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Message;
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Components
|
||||||
|
import { MdSpaceDashboard } from 'react-icons/md';
|
||||||
|
import { HiUsers } from 'react-icons/hi2';
|
||||||
|
import { BsHouseDoorFill } from 'react-icons/bs';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { StyledNav, StyledNavLink } from './styled';
|
||||||
|
|
||||||
|
const Nav = () => {
|
||||||
|
return (
|
||||||
|
<StyledNav>
|
||||||
|
<StyledNavLink to={'/dashboard'}>
|
||||||
|
<MdSpaceDashboard />
|
||||||
|
<span>Dashboard</span>
|
||||||
|
</StyledNavLink>
|
||||||
|
<StyledNavLink to={'/user'}>
|
||||||
|
<HiUsers />
|
||||||
|
<span>User</span>
|
||||||
|
</StyledNavLink>
|
||||||
|
<StyledNavLink to={'/room'}>
|
||||||
|
<BsHouseDoorFill />
|
||||||
|
<span>Room</span>
|
||||||
|
</StyledNavLink>
|
||||||
|
</StyledNav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Nav;
|
||||||
+1
-25
@@ -1,11 +1,6 @@
|
|||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
// Components
|
|
||||||
import { MdSpaceDashboard } from 'react-icons/md';
|
|
||||||
import { HiUsers } from 'react-icons/hi2';
|
|
||||||
import { BsHouseDoorFill } from 'react-icons/bs';
|
|
||||||
|
|
||||||
const StyledNav = styled.nav`
|
const StyledNav = styled.nav`
|
||||||
margin-top: 70px;
|
margin-top: 70px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -38,23 +33,4 @@ const StyledNavLink = styled(NavLink)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Nav = () => {
|
export { StyledNav, StyledNavLink };
|
||||||
return (
|
|
||||||
<StyledNav>
|
|
||||||
<StyledNavLink to={'/dashboard'}>
|
|
||||||
<MdSpaceDashboard />
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</StyledNavLink>
|
|
||||||
<StyledNavLink to={'/user'}>
|
|
||||||
<HiUsers />
|
|
||||||
<span>User</span>
|
|
||||||
</StyledNavLink>
|
|
||||||
<StyledNavLink to={'/room'}>
|
|
||||||
<BsHouseDoorFill />
|
|
||||||
<span>Room</span>
|
|
||||||
</StyledNavLink>
|
|
||||||
</StyledNav>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Nav;
|
|
||||||
+2
-33
@@ -1,39 +1,8 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
|
|
||||||
const StyledOrder = styled.div`
|
// Styled
|
||||||
border: 1px solid var(--border-color);
|
import { OrderButton, StyledOrder } from './styled';
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 5px;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface IOrderBtn {
|
|
||||||
active: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OrderButton = styled.button<IOrderBtn>`
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
${(props) =>
|
|
||||||
props.active &&
|
|
||||||
css`
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: var(--light-text);
|
|
||||||
`}
|
|
||||||
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: var(--fs-sm-x);
|
|
||||||
padding: 5px 10px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background-color: var(--hover-dark-background-color);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface IOrderProps {
|
interface IOrderProps {
|
||||||
options: {
|
options: {
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
const StyledOrder = styled.div`
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface IOrderBtn {
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderButton = styled.button<IOrderBtn>`
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.active &&
|
||||||
|
css`
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--light-text);
|
||||||
|
cursor: no-drop;
|
||||||
|
`}
|
||||||
|
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
padding: 5px 10px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: var(--hover-dark-background-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledOrder, OrderButton };
|
||||||
+1
-8
@@ -1,12 +1,5 @@
|
|||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import { StyledSearch } from './styled';
|
||||||
|
|
||||||
const StyledSearch = styled.input`
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
font-size: var(--fs-sm-x);
|
|
||||||
padding: 5px 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ISearch {
|
interface ISearch {
|
||||||
setPlaceHolder: string;
|
setPlaceHolder: string;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledSearch = styled.input`
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
padding: 5px 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledSearch };
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
interface ISelect {
|
|
||||||
options: {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
}[];
|
|
||||||
value: string;
|
|
||||||
onChange: React.ChangeEventHandler<HTMLSelectElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledSelect = styled.select`
|
|
||||||
font-size: var(--fs-sm-x);
|
|
||||||
padding: 5px 10px;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Select = ({ options, value, onChange }: ISelect) => {
|
|
||||||
return (
|
|
||||||
<StyledSelect value={value} onChange={onChange} aria-label="Sort">
|
|
||||||
{options.map((option) => (
|
|
||||||
<option value={option.value} key={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</StyledSelect>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Select;
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { ISelectOptions } from '../../globals/interfaces';
|
||||||
|
import { StyledSelect } from './styled';
|
||||||
|
|
||||||
|
interface ISelect {
|
||||||
|
options: ISelectOptions[];
|
||||||
|
value: string;
|
||||||
|
onChange: React.ChangeEventHandler<HTMLSelectElement>;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Select = ({ options, value, onChange, name }: ISelect) => {
|
||||||
|
if (!options) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledSelect
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
aria-label="Sort"
|
||||||
|
name={name}
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<option value={option.value} key={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</StyledSelect>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Select;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledSelect = styled.select`
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledSelect };
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Components
|
||||||
|
import Nav from '../Nav';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { Heading, StyledSidebar } from './styled';
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
return (
|
||||||
|
<StyledSidebar>
|
||||||
|
<Heading>Hotel Management</Heading>
|
||||||
|
<Nav />
|
||||||
|
</StyledSidebar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
+1
-13
@@ -1,8 +1,5 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
// Components
|
|
||||||
import Nav from './Nav';
|
|
||||||
|
|
||||||
const StyledSidebar = styled.aside`
|
const StyledSidebar = styled.aside`
|
||||||
padding: 50px 20px;
|
padding: 50px 20px;
|
||||||
|
|
||||||
@@ -17,13 +14,4 @@ const Heading = styled.h1`
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Sidebar = () => {
|
export { StyledSidebar, Heading };
|
||||||
return (
|
|
||||||
<StyledSidebar>
|
|
||||||
<Heading>Hotel Management</Heading>
|
|
||||||
<Nav />
|
|
||||||
</StyledSidebar>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Sidebar;
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Select from './Select';
|
import Select from '../Select';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
interface ISortByProps {
|
interface ISortByProps {
|
||||||
@@ -11,6 +11,8 @@ const EDIT_SUCCESS = 'Edit success';
|
|||||||
const CONFIRM_DELETE = 'Are you sure to delete it?';
|
const CONFIRM_DELETE = 'Are you sure to delete it?';
|
||||||
const DELETE_SUCCESS = 'Delete success';
|
const DELETE_SUCCESS = 'Delete success';
|
||||||
const REQUIRED_FIELD_ERROR = 'This is required field';
|
const REQUIRED_FIELD_ERROR = 'This is required field';
|
||||||
|
const DISCOUNT_FIELD_ERROR =
|
||||||
|
'Discount should be greater than 0 and less than 100';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
invalidFormatMsg,
|
invalidFormatMsg,
|
||||||
@@ -20,4 +22,5 @@ export {
|
|||||||
CONFIRM_DELETE,
|
CONFIRM_DELETE,
|
||||||
DELETE_SUCCESS,
|
DELETE_SUCCESS,
|
||||||
REQUIRED_FIELD_ERROR,
|
REQUIRED_FIELD_ERROR,
|
||||||
|
DISCOUNT_FIELD_ERROR,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ export const DASHBOARD: string = '/dashboard';
|
|||||||
export const USER: string = '/user';
|
export const USER: string = '/user';
|
||||||
export const ROOM: string = '/room';
|
export const ROOM: string = '/room';
|
||||||
export const OTHER_PATH: string = '*';
|
export const OTHER_PATH: string = '*';
|
||||||
export const BASE_URL: string = 'https://hotel-management-api.loiphan.com/';
|
export const BASE_URL: string = 'http://localhost:3000/';
|
||||||
export const USER_PATH = 'users';
|
export const USER_PATH = 'users';
|
||||||
export const ROOM_PATH = 'rooms';
|
export const ROOM_PATH = 'rooms';
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ interface ITableBody<T> {
|
|||||||
render?: (value: T) => JSX.Element;
|
render?: (value: T) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ISelectOptions {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface IDialogProps<T> {
|
interface IDialogProps<T> {
|
||||||
title?: string;
|
title?: string;
|
||||||
children?: JSX.Element[] | JSX.Element;
|
children?: JSX.Element[] | JSX.Element;
|
||||||
@@ -31,4 +36,11 @@ interface IDialogProps<T> {
|
|||||||
isAdd?: boolean;
|
isAdd?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { IMenusContext, IButton, ITable, ITableBody, IDialogProps };
|
export type {
|
||||||
|
IMenusContext,
|
||||||
|
IButton,
|
||||||
|
ITable,
|
||||||
|
ITableBody,
|
||||||
|
IDialogProps,
|
||||||
|
ISelectOptions,
|
||||||
|
};
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => {
|
|||||||
|
|
||||||
type TValidator = {
|
type TValidator = {
|
||||||
validatorFunc: (value: string) => boolean;
|
validatorFunc: (value: string) => boolean;
|
||||||
prop: string;
|
prop?: string;
|
||||||
|
customErrorMsg?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,12 +66,20 @@ type TValidator = {
|
|||||||
* @param param0 Pass TValidator object
|
* @param param0 Pass TValidator object
|
||||||
* @returns An object contains condition validator
|
* @returns An object contains condition validator
|
||||||
*/
|
*/
|
||||||
const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => {
|
const addValidator = ({
|
||||||
|
validatorFunc,
|
||||||
|
prop = '',
|
||||||
|
customErrorMsg = '',
|
||||||
|
required = true,
|
||||||
|
}: TValidator) => {
|
||||||
return {
|
return {
|
||||||
required,
|
required,
|
||||||
validator: {
|
validator: {
|
||||||
func: validatorFunc,
|
func: validatorFunc,
|
||||||
error: invalidFormatMsg(prop),
|
// prettier-ignore
|
||||||
|
error: customErrorMsg
|
||||||
|
? customErrorMsg
|
||||||
|
: invalidFormatMsg(prop),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ const isValidNumber = (value: string): boolean => {
|
|||||||
return /^[0-9]*$/.test(value);
|
return /^[0-9]*$/.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check value is valid discount or not
|
||||||
|
* @param value Value need to be checked
|
||||||
|
* @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;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check value is valid phone number or not
|
* Check value is valid phone number or not
|
||||||
* @param value Value need to be checked
|
* @param value Value need to be checked
|
||||||
@@ -31,4 +41,10 @@ const isValidString = (value: string): boolean => {
|
|||||||
*/
|
*/
|
||||||
const skipCheck = () => true;
|
const skipCheck = () => true;
|
||||||
|
|
||||||
export { isValidNumber, isValidPhoneNumber, isValidString, skipCheck };
|
export {
|
||||||
|
isValidNumber,
|
||||||
|
isValidPhoneNumber,
|
||||||
|
isValidString,
|
||||||
|
skipCheck,
|
||||||
|
isValidDiscount,
|
||||||
|
};
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ import { searchQuery } from '../helpers/utils';
|
|||||||
*/
|
*/
|
||||||
const useFetch = (
|
const useFetch = (
|
||||||
path: string,
|
path: string,
|
||||||
columnSearch: string,
|
columnSearch: string = '',
|
||||||
keyWord: string,
|
keyWord: string = '',
|
||||||
tempSortBy: string,
|
tempSortBy?: string,
|
||||||
tempOrderBy: string,
|
tempOrderBy?: string,
|
||||||
reload?: boolean,
|
reload?: boolean,
|
||||||
) => {
|
) => {
|
||||||
const [data, setData] = useState(null);
|
const [data, setData] = useState(null);
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
const [errorFetchMsg, setErrorFetchMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Clear data
|
// Clear data
|
||||||
@@ -61,16 +61,16 @@ const useFetch = (
|
|||||||
|
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
setData(json);
|
setData(json);
|
||||||
setErrorMsg(null);
|
setErrorFetchMsg(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorMsg(`Could not fetch data.\n ${error}`);
|
setErrorFetchMsg(`Could not fetch data.\n ${error}`);
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [path, reload, columnSearch, keyWord, tempOrderBy, tempSortBy]);
|
}, [path, reload, columnSearch, keyWord, tempOrderBy, tempSortBy]);
|
||||||
return { data, isPending, errorMsg };
|
return { data, isPending, errorFetchMsg };
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useFetch };
|
export { useFetch };
|
||||||
|
|||||||
@@ -110,7 +110,11 @@ const useForm = (
|
|||||||
|
|
||||||
// Event handler for handling changes in input.
|
// Event handler for handling changes in input.
|
||||||
const handleOnChange = useCallback(
|
const handleOnChange = useCallback(
|
||||||
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
(
|
||||||
|
event: ChangeEvent<
|
||||||
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||||
|
>,
|
||||||
|
) => {
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
|
|
||||||
let error = '';
|
let error = '';
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import useForm from '../../hooks/useForm.ts';
|
|||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import {
|
import {
|
||||||
|
isValidDiscount,
|
||||||
isValidNumber,
|
isValidNumber,
|
||||||
isValidString,
|
isValidString,
|
||||||
skipCheck,
|
skipCheck,
|
||||||
@@ -35,6 +36,7 @@ import { sendRequest } from '../../helpers/sendRequest.ts';
|
|||||||
import { STATUS_CODE } from '../../constants/statusCode.ts';
|
import { STATUS_CODE } from '../../constants/statusCode.ts';
|
||||||
import {
|
import {
|
||||||
ADD_SUCCESS,
|
ADD_SUCCESS,
|
||||||
|
DISCOUNT_FIELD_ERROR,
|
||||||
EDIT_SUCCESS,
|
EDIT_SUCCESS,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
} from '../../constants/messages.ts';
|
} from '../../constants/messages.ts';
|
||||||
@@ -46,6 +48,7 @@ const FormBtn = styled(Button)`
|
|||||||
&:disabled,
|
&:disabled,
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
background-color: var(--disabled-btn-color);
|
background-color: var(--disabled-btn-color);
|
||||||
|
cursor: no-drop;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -134,8 +137,8 @@ const RoomForm = ({
|
|||||||
prop: 'price',
|
prop: 'price',
|
||||||
}),
|
}),
|
||||||
discount: addValidator({
|
discount: addValidator({
|
||||||
validatorFunc: isValidNumber,
|
validatorFunc: isValidDiscount,
|
||||||
prop: 'discount'
|
customErrorMsg: DISCOUNT_FIELD_ERROR
|
||||||
}),
|
}),
|
||||||
status: addValidator({
|
status: addValidator({
|
||||||
validatorFunc: skipCheck,
|
validatorFunc: skipCheck,
|
||||||
@@ -305,8 +308,8 @@ const RoomForm = ({
|
|||||||
label="Discount"
|
label="Discount"
|
||||||
error={
|
error={
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
errors.roomId && dirty.roomId
|
errors.discount && dirty.discount
|
||||||
? (errors.roomId as string)
|
? (errors.discount as string)
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -350,7 +353,7 @@ const RoomForm = ({
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
isAdd
|
isAdd
|
||||||
? 'Add'
|
? 'Add'
|
||||||
: 'Edit'
|
: 'Save'
|
||||||
}
|
}
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ const RoomTable = ({
|
|||||||
? searchParams.get('orderBy')!
|
? searchParams.get('orderBy')!
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const { data, isPending, errorMsg } = useFetch(
|
const { data, isPending, errorFetchMsg } = useFetch(
|
||||||
'rooms',
|
'rooms',
|
||||||
'name',
|
'name',
|
||||||
nameSearch,
|
nameSearch,
|
||||||
@@ -141,10 +141,10 @@ const RoomTable = ({
|
|||||||
setRooms([]);
|
setRooms([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorFetchMsg) {
|
||||||
console.error(errorMsg);
|
console.error(errorFetchMsg);
|
||||||
}
|
}
|
||||||
}, [data, errorMsg]);
|
}, [data, errorFetchMsg]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ import {
|
|||||||
errorMsg,
|
errorMsg,
|
||||||
} from '../../constants/messages.ts';
|
} from '../../constants/messages.ts';
|
||||||
import { USER_PATH } from '../../constants/path.ts';
|
import { USER_PATH } from '../../constants/path.ts';
|
||||||
|
import Select from '../../components/Select/index.tsx';
|
||||||
|
import { useFetch } from '../../hooks/useFetch.ts';
|
||||||
|
|
||||||
|
// Interfaces
|
||||||
|
import { ISelectOptions } from '../../globals/interfaces.ts';
|
||||||
|
|
||||||
const FormBtn = styled(Button)`
|
const FormBtn = styled(Button)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -46,6 +51,7 @@ const FormBtn = styled(Button)`
|
|||||||
&:disabled,
|
&:disabled,
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
background-color: var(--disabled-btn-color);
|
background-color: var(--disabled-btn-color);
|
||||||
|
cursor: no-drop;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -65,6 +71,27 @@ const UserForm = ({
|
|||||||
isAdd,
|
isAdd,
|
||||||
}: IUserFormProp) => {
|
}: IUserFormProp) => {
|
||||||
const [reset, setReset] = useState(true);
|
const [reset, setReset] = useState(true);
|
||||||
|
const [options, setOptions] = useState<ISelectOptions[]>();
|
||||||
|
const { data, errorFetchMsg } = useFetch('rooms');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
const tempData = data as TKeyValue[];
|
||||||
|
const tempOptions: ISelectOptions[] = [];
|
||||||
|
tempData.forEach((item) => {
|
||||||
|
tempOptions.push({
|
||||||
|
label: item.name! as string,
|
||||||
|
value: item.id! as string,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setOptions(tempOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorFetchMsg) {
|
||||||
|
toast.error(errorFetchMsg);
|
||||||
|
}
|
||||||
|
}, [data, errorFetchMsg]);
|
||||||
|
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
// If this is add form, reset value
|
// If this is add form, reset value
|
||||||
@@ -294,11 +321,17 @@ const UserForm = ({
|
|||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input
|
{/* <Input
|
||||||
type="text"
|
type="text"
|
||||||
name="roomId"
|
name="roomId"
|
||||||
value={roomId as string}
|
value={roomId as string}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
|
/> */}
|
||||||
|
<Select
|
||||||
|
name="roomId"
|
||||||
|
value={roomId as string}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
options={options!}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
@@ -325,7 +358,7 @@ const UserForm = ({
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
isAdd
|
isAdd
|
||||||
? 'Add'
|
? 'Add'
|
||||||
: 'Edit'
|
: 'Save'
|
||||||
}
|
}
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ import { useFetch } from '../../hooks/useFetch';
|
|||||||
import { USER_PATH } from '../../constants/path';
|
import { USER_PATH } from '../../constants/path';
|
||||||
import { STATUS_CODE } from '../../constants/statusCode';
|
import { STATUS_CODE } from '../../constants/statusCode';
|
||||||
import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages';
|
import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages';
|
||||||
|
import { USER_PAGE } from '../../constants/variables';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import Spinner from '../../commons/styles/Spinner';
|
import Spinner from '../../commons/styles/Spinner';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { sendRequest } from '../../helpers/sendRequest';
|
import { sendRequest } from '../../helpers/sendRequest';
|
||||||
import { USER_PAGE } from '../../constants/variables';
|
|
||||||
|
|
||||||
interface IUserRow {
|
interface IUserRow {
|
||||||
user: TUser;
|
user: TUser;
|
||||||
@@ -119,7 +119,7 @@ const UserTable = ({
|
|||||||
? searchParams.get('orderBy')!
|
? searchParams.get('orderBy')!
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const { data, isPending, errorMsg } = useFetch(
|
const { data, isPending, errorFetchMsg } = useFetch(
|
||||||
'users',
|
'users',
|
||||||
'phone',
|
'phone',
|
||||||
phoneSearch,
|
phoneSearch,
|
||||||
@@ -135,10 +135,10 @@ const UserTable = ({
|
|||||||
setUsers([]);
|
setUsers([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorFetchMsg) {
|
||||||
console.error(errorMsg);
|
console.error(errorFetchMsg);
|
||||||
}
|
}
|
||||||
}, [data, errorMsg]);
|
}, [data, errorFetchMsg]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -163,7 +163,7 @@ const UserTable = ({
|
|||||||
<div>Name</div>
|
<div>Name</div>
|
||||||
<div>Identified Code</div>
|
<div>Identified Code</div>
|
||||||
<div>Phone</div>
|
<div>Phone</div>
|
||||||
<div>Room</div>
|
<div>Room Id</div>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body<TUser>
|
<Table.Body<TUser>
|
||||||
data={users}
|
data={users}
|
||||||
|
|||||||
Reference in New Issue
Block a user