mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 20:01:59 +00:00
Remove unnecessary code, refactor, fix bug and add section comment
This commit is contained in:
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -14,7 +14,7 @@ import RootLayout from '@component/RootLayout';
|
|||||||
import User from './pages/User';
|
import User from './pages/User';
|
||||||
import Booking from './pages/Booking';
|
import Booking from './pages/Booking';
|
||||||
import Room from './pages/Room';
|
import Room from './pages/Room';
|
||||||
import NotFound from './pages/NotFound';
|
import NotFound from './pages/NotFound/NotFound';
|
||||||
import Login from '@page/Login';
|
import Login from '@page/Login';
|
||||||
import Account from '@page/Account';
|
import Account from '@page/Account';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import { COLOR, SIZE } from '@constant/styles';
|
|
||||||
|
|
||||||
interface IStyledButtonIcon {
|
interface IStyledButtonIcon {
|
||||||
isHaveChildren: boolean;
|
isHaveChildren: boolean;
|
||||||
@@ -77,13 +76,13 @@ const StyledButtonIcon = styled.button<IStyledButtonIcon>`
|
|||||||
|
|
||||||
StyledButtonIcon.defaultProps = {
|
StyledButtonIcon.defaultProps = {
|
||||||
style: {
|
style: {
|
||||||
fontSize: SIZE.DEFAULT,
|
fontSize: '14px',
|
||||||
backgroundColor: COLOR.DEFAULT,
|
backgroundColor: 'var(--primary-color)',
|
||||||
color: COLOR.BLACK,
|
color: '#000',
|
||||||
},
|
},
|
||||||
iconStyle: {
|
iconStyle: {
|
||||||
color: COLOR.BLACK,
|
color: '#000',
|
||||||
size: SIZE.DEFAULT,
|
size: '14px',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ interface IConfirmMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmMessage = memo(
|
const ConfirmMessage = memo(
|
||||||
({ message, onConfirm, onCloseModal }: IConfirmMessage) => {
|
({
|
||||||
|
message,
|
||||||
|
onConfirm,
|
||||||
|
onCloseModal
|
||||||
|
}: IConfirmMessage) => {
|
||||||
const handleConfirmBtn = useCallback(() => {
|
const handleConfirmBtn = useCallback(() => {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
onCloseModal!();
|
onCloseModal!();
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ const Form = ({ children, onSubmit, id }: IFormProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormRow = ({ label, error, children, direction }: IFormRow) => {
|
const FormRow = ({
|
||||||
|
label,
|
||||||
|
error,
|
||||||
|
children,
|
||||||
|
direction
|
||||||
|
}: IFormRow) => {
|
||||||
return (
|
return (
|
||||||
<StyledFormRow direction={direction}>
|
<StyledFormRow direction={direction}>
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
|
|||||||
@@ -65,4 +65,11 @@ StyledFormRow.defaultProps = {
|
|||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
};
|
};
|
||||||
|
|
||||||
export { StyledForm, StyledActionBtn, StyledFormRow, Label, Error, FormBtn };
|
export {
|
||||||
|
StyledForm,
|
||||||
|
StyledActionBtn,
|
||||||
|
StyledFormRow,
|
||||||
|
Label,
|
||||||
|
Error,
|
||||||
|
FormBtn
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ import MenusContext from '@context/MenuContext';
|
|||||||
// Types
|
// Types
|
||||||
import { Nullable } from '@type/common';
|
import { Nullable } from '@type/common';
|
||||||
|
|
||||||
// Constants
|
|
||||||
import { COLOR } from '@constant/styles';
|
|
||||||
|
|
||||||
interface IButton {
|
interface IButton {
|
||||||
children?: string;
|
children?: string;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
@@ -32,18 +29,28 @@ const Menus = ({ children }: { children: ReactNode }) => {
|
|||||||
const open = setOpenId;
|
const open = setOpenId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenusContext.Provider value={{ openId, close, open }}>
|
<MenusContext.Provider
|
||||||
|
value={{
|
||||||
|
openId,
|
||||||
|
close,
|
||||||
|
open,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</MenusContext.Provider>
|
</MenusContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Toggle = ({ id }: { id: string }): ReactNode => {
|
const Toggle = ({ id }: { id: string }): ReactNode => {
|
||||||
const { openId, close, open } = useContext(MenusContext);
|
const {
|
||||||
|
openId,
|
||||||
|
close,
|
||||||
|
open
|
||||||
|
} = useContext(MenusContext);
|
||||||
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
openId === '' || openId !== id
|
openId !== id
|
||||||
? open!(id)
|
? open!(id)
|
||||||
: close!();
|
: close!();
|
||||||
};
|
};
|
||||||
@@ -82,7 +89,7 @@ const Button = ({ children, icon, onClick, disabled }: IButton): ReactNode => {
|
|||||||
<ButtonIcon
|
<ButtonIcon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
iconStyle={{ color: COLOR.PRIMARY, size: '19px' }}
|
iconStyle={{ color: 'var(--primary-color)', size: '19px' }}
|
||||||
style={{ fontSize: '16px' }}
|
style={{ fontSize: '16px' }}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -63,4 +63,9 @@ const StyledList = styled.ul`
|
|||||||
width: max-content;
|
width: max-content;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledMenu, StyledButton, StyledList, StyledToggle };
|
export {
|
||||||
|
StyledMenu,
|
||||||
|
StyledButton,
|
||||||
|
StyledList,
|
||||||
|
StyledToggle
|
||||||
|
};
|
||||||
|
|||||||
@@ -39,4 +39,9 @@ const TitleModal = styled.p`
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export { StyledModal, Overlay, StyledModalContent, TitleModal };
|
export {
|
||||||
|
StyledModal,
|
||||||
|
Overlay,
|
||||||
|
StyledModalContent,
|
||||||
|
TitleModal
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
@@ -22,35 +21,24 @@ const Pagination = ({ count }: IPagination) => {
|
|||||||
|
|
||||||
const totalPage = Math.ceil(count / DEFAULT_PAGE_SIZE);
|
const totalPage = Math.ceil(count / DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const nextPage = useCallback(() => {
|
const nextPage = () => {
|
||||||
const next = currentPage === totalPage
|
const next = currentPage === totalPage ? currentPage : currentPage + 1;
|
||||||
? currentPage
|
|
||||||
: currentPage + 1;
|
|
||||||
|
|
||||||
searchParams.set('page', next.toString());
|
searchParams.set('page', next.toString());
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
}, [currentPage, searchParams, totalPage, setSearchParams]);
|
};
|
||||||
|
|
||||||
const previousPage = useCallback(() => {
|
const previousPage = () => {
|
||||||
const previous = currentPage === 1
|
const previous = currentPage === 1 ? currentPage : currentPage - 1;
|
||||||
? currentPage
|
|
||||||
: currentPage - 1;
|
|
||||||
|
|
||||||
searchParams.set('page', previous.toString());
|
searchParams.set('page', previous.toString());
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
}, [currentPage, searchParams, setSearchParams]);
|
};
|
||||||
|
|
||||||
const fromIndex = useMemo(
|
const fromIndex = (currentPage - 1) * DEFAULT_PAGE_SIZE + 1;
|
||||||
() => (currentPage - 1) * DEFAULT_PAGE_SIZE + 1,
|
|
||||||
[currentPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const toIndex = useMemo(
|
const toIndex =
|
||||||
() => (currentPage === totalPage
|
currentPage === totalPage ? count : currentPage * DEFAULT_PAGE_SIZE;
|
||||||
? count
|
|
||||||
: currentPage * DEFAULT_PAGE_SIZE),
|
|
||||||
[currentPage, count, totalPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
totalPage > 1 && (
|
totalPage > 1 && (
|
||||||
|
|||||||
@@ -47,4 +47,8 @@ const PaginationBtn = styled.button<IPaginationBtn>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledPagination, PaginationBtn, Buttons };
|
export {
|
||||||
|
StyledPagination,
|
||||||
|
PaginationBtn,
|
||||||
|
Buttons
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent, memo } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Select from '@component/Select';
|
import Select from '@component/Select';
|
||||||
@@ -11,7 +11,7 @@ interface ISortByProps {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortBy = ({ options }: ISortByProps) => {
|
const SortBy = memo(({ options }: ISortByProps) => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const sortBy = searchParams.get('sortBy') || '';
|
const sortBy = searchParams.get('sortBy') || '';
|
||||||
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
@@ -27,6 +27,6 @@ const SortBy = ({ options }: ISortByProps) => {
|
|||||||
ariaLabel="Sort"
|
ariaLabel="Sort"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default SortBy;
|
export default SortBy;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ const BOOKINGS_TABLE = 'bookings';
|
|||||||
const ERROR_FETCHING_BOOKING = "Can't fetch booking data!";
|
const ERROR_FETCHING_BOOKING = "Can't fetch booking data!";
|
||||||
const ERROR_UPDATE_BOOKING = "Can't update booking!";
|
const ERROR_UPDATE_BOOKING = "Can't update booking!";
|
||||||
const ERROR_CREATE_BOOKING = "Can't create booking!";
|
const ERROR_CREATE_BOOKING = "Can't create booking!";
|
||||||
const ERROR_DELETE_BOOKING = "Can't delete booking!";
|
|
||||||
const CHECKOUT_SUCCESS = 'Check out success!';
|
const CHECKOUT_SUCCESS = 'Check out success!';
|
||||||
const ERROR_CHECKOUT = "Can't checkout!";
|
const ERROR_CHECKOUT = "Can't checkout!";
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ export {
|
|||||||
ERROR_FETCHING_BOOKING,
|
ERROR_FETCHING_BOOKING,
|
||||||
ERROR_UPDATE_BOOKING,
|
ERROR_UPDATE_BOOKING,
|
||||||
ERROR_CREATE_BOOKING,
|
ERROR_CREATE_BOOKING,
|
||||||
ERROR_DELETE_BOOKING,
|
|
||||||
ROOMS_TABLE,
|
ROOMS_TABLE,
|
||||||
ERROR_FETCHING_ROOM,
|
ERROR_FETCHING_ROOM,
|
||||||
ERROR_UPDATE_ROOM,
|
ERROR_UPDATE_ROOM,
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
const COLOR = {
|
|
||||||
PRIMARY: '#0010ba',
|
|
||||||
BLACK: '#000',
|
|
||||||
DEFAULT: 'transparent',
|
|
||||||
};
|
|
||||||
|
|
||||||
const SIZE = {
|
|
||||||
DEFAULT: '14px',
|
|
||||||
};
|
|
||||||
|
|
||||||
export { COLOR, SIZE };
|
|
||||||
@@ -34,4 +34,8 @@ const convertCurrencyToNumber = (currency: string) => {
|
|||||||
return Number(currency.replace(/[^0-9.-]+/g,""))
|
return Number(currency.replace(/[^0-9.-]+/g,""))
|
||||||
}
|
}
|
||||||
|
|
||||||
export { formatCurrency, getDayDiff, convertCurrencyToNumber };
|
export {
|
||||||
|
formatCurrency,
|
||||||
|
getDayDiff,
|
||||||
|
convertCurrencyToNumber
|
||||||
|
};
|
||||||
|
|||||||
@@ -26,8 +26,19 @@ const isValidString = (value: string): boolean => {
|
|||||||
return value.trim().length >= 5;
|
return value.trim().length >= 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two days
|
||||||
|
* @param startDate The start date
|
||||||
|
* @param endDate The end date
|
||||||
|
* @returns Return true if start date greater than end date
|
||||||
|
*/
|
||||||
const compareTwoDates = (startDate: Date, endDate: Date): boolean => {
|
const compareTwoDates = (startDate: Date, endDate: Date): boolean => {
|
||||||
return startDate > endDate;
|
return startDate > endDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { isValidRegex, isValidString, isValidDiscount, compareTwoDates };
|
export {
|
||||||
|
isValidRegex,
|
||||||
|
isValidString,
|
||||||
|
isValidDiscount,
|
||||||
|
compareTwoDates
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
// Services
|
// Services
|
||||||
import { getCurrentAccount } from '@service/authenticationService';
|
import { getCurrentAccount } from '@service/authenticationService';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data of account currently login
|
||||||
|
* @returns The data of account currently login
|
||||||
|
*/
|
||||||
const useAccount = () => {
|
const useAccount = () => {
|
||||||
let isAuthenticated: boolean = false;
|
let isAuthenticated: boolean = false;
|
||||||
const { data: account, isPending } = useQuery({
|
const { data: account, isPending } = useQuery({
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import { login as loginFn } from '@service/authenticationService';
|
|||||||
// Types
|
// Types
|
||||||
import { ILogin } from '@type/common';
|
import { ILogin } from '@type/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login web
|
||||||
|
* @returns The login function and isPending boolean
|
||||||
|
*/
|
||||||
const useLogin = () => {
|
const useLogin = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
// Services
|
// Services
|
||||||
import { logout as logoutFn } from '@service/authenticationService';
|
import { logout as logoutFn } from '@service/authenticationService';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout web
|
||||||
|
* @returns The logout function and isPending boolean
|
||||||
|
*/
|
||||||
const useLogout = () => {
|
const useLogout = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { updateAccount as updateAccountFn } from '@service/authenticationService
|
|||||||
// Constants
|
// Constants
|
||||||
import { UPDATE_ACCOUNT_SUCCESSFUL } from '@constant/messages';
|
import { UPDATE_ACCOUNT_SUCCESSFUL } from '@constant/messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update account currently login.
|
||||||
|
* @returns Return updateAccount function and isUpdating boolean
|
||||||
|
*/
|
||||||
const useUpdateAccount = () => {
|
const useUpdateAccount = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
// Services
|
|
||||||
import { deleteBooking as deleteBookingFn } from '@service/bookingServices';
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
import { DELETE_SUCCESS } from '@constant/messages';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the booking from database
|
|
||||||
* @returns The boolean of status deleting and deleteBooking function
|
|
||||||
*/
|
|
||||||
const useDeleteBooking = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const {
|
|
||||||
isPending: isDeleting,
|
|
||||||
mutate: deleteBooking,
|
|
||||||
isSuccess,
|
|
||||||
} = useMutation({
|
|
||||||
mutationFn: deleteBookingFn,
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(DELETE_SUCCESS);
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ['bookings'],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (err) => toast.error(err.message),
|
|
||||||
});
|
|
||||||
|
|
||||||
return { isDeleting, deleteBooking, isSuccess };
|
|
||||||
};
|
|
||||||
|
|
||||||
export { useDeleteBooking };
|
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The debounce hooks use to execute the callback function every x delay time
|
||||||
|
* @param value The value need to be pass
|
||||||
|
* @param delay The number of time want to delay
|
||||||
|
* @returns Return the value after delay number
|
||||||
|
*/
|
||||||
const useDebounce = <T>(value: T, delay: number): T => {
|
const useDebounce = <T>(value: T, delay: number): T => {
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { useContext } from 'react';
|
|||||||
// Contexts
|
// Contexts
|
||||||
import { UserRoomAvailableContext } from '@context/UserRoomAvailableContext';
|
import { UserRoomAvailableContext } from '@context/UserRoomAvailableContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hooks check the place call is in UserRoomAvailableProvider or not
|
||||||
|
* @returns The context of UserRoomAvailableContext
|
||||||
|
*/
|
||||||
const useUserRoomAvailable = () => {
|
const useUserRoomAvailable = () => {
|
||||||
const context = useContext(UserRoomAvailableContext);
|
const context = useContext(UserRoomAvailableContext);
|
||||||
|
|
||||||
|
|||||||
@@ -33,4 +33,9 @@ const FormArea = styled.div`
|
|||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export { Title, StyledAccount, FormArea, SubTitle };
|
export {
|
||||||
|
Title,
|
||||||
|
StyledAccount,
|
||||||
|
FormArea,
|
||||||
|
SubTitle
|
||||||
|
};
|
||||||
|
|||||||
@@ -32,15 +32,17 @@ interface IBookingFormProp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
||||||
const { roomsAvailable, usersAvailable, dispatch } = useUserRoomAvailable();
|
const {
|
||||||
|
roomsAvailable,
|
||||||
|
usersAvailable,
|
||||||
|
dispatch
|
||||||
|
} = useUserRoomAvailable();
|
||||||
const { isCreating, createBooking } = useCreateBooking();
|
const { isCreating, createBooking } = useCreateBooking();
|
||||||
const { isUpdating, updateBooking } = useUpdateBooking();
|
const { isUpdating, updateBooking } = useUpdateBooking();
|
||||||
const isLoading = isCreating || isUpdating;
|
const isLoading = isCreating || isUpdating;
|
||||||
const { id: editId, ...editValues } = { ...booking };
|
const { id: editId, ...editValues } = { ...booking };
|
||||||
const [amountValue, setAmountValue] = useState(
|
const [amountValue, setAmountValue] = useState(
|
||||||
editValues.amount
|
editValues.amount ? formatCurrency(editValues.amount) : '$0.00'
|
||||||
? formatCurrency(editValues.amount)
|
|
||||||
: '$0.00'
|
|
||||||
);
|
);
|
||||||
const formMethods = useForm<IBooking>({
|
const formMethods = useForm<IBooking>({
|
||||||
defaultValues: editId
|
defaultValues: editId
|
||||||
@@ -182,10 +184,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
}
|
}
|
||||||
}, [setValue, getValues]);
|
}, [setValue, getValues]);
|
||||||
|
|
||||||
const startDateValidate = useMemo(
|
const startDateValidate = new Date().toISOString().split('T')[0];
|
||||||
() => new Date().toISOString().split('T')[0],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const endDateValidate = () => {
|
const endDateValidate = () => {
|
||||||
if (getValues('startDate')) {
|
if (getValues('startDate')) {
|
||||||
@@ -201,7 +200,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
return (
|
return (
|
||||||
<FormProvider {...formMethods}>
|
<FormProvider {...formMethods}>
|
||||||
<Form onSubmit={handleSubmit(onSubmit)}>
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Form.Row label="User" error={errors?.userId?.message}>
|
<Form.Row label="User:" error={errors?.userId?.message}>
|
||||||
{userOptions.length ? (
|
{userOptions.length ? (
|
||||||
<Select
|
<Select
|
||||||
ariaLabel="user-select"
|
ariaLabel="user-select"
|
||||||
@@ -218,7 +217,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
)}
|
)}
|
||||||
</Form.Row>
|
</Form.Row>
|
||||||
|
|
||||||
<Form.Row label="Room" error={errors?.roomId?.message}>
|
<Form.Row label="Room:" error={errors?.roomId?.message}>
|
||||||
{roomOptions.length ? (
|
{roomOptions.length ? (
|
||||||
<Select
|
<Select
|
||||||
ariaLabel="user-select"
|
ariaLabel="user-select"
|
||||||
@@ -234,7 +233,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
)}
|
)}
|
||||||
</Form.Row>
|
</Form.Row>
|
||||||
|
|
||||||
<Form.Row label="Start Date" error={errors?.startDate?.message}>
|
<Form.Row label="Start Date:" error={errors?.startDate?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
id="startDate"
|
id="startDate"
|
||||||
@@ -251,7 +250,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
/>
|
/>
|
||||||
</Form.Row>
|
</Form.Row>
|
||||||
|
|
||||||
<Form.Row label="End Date" error={errors?.endDate?.message}>
|
<Form.Row label="End Date:" error={errors?.endDate?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
id="endDate"
|
id="endDate"
|
||||||
@@ -269,7 +268,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
/>
|
/>
|
||||||
</Form.Row>
|
</Form.Row>
|
||||||
|
|
||||||
<Form.Row label="Amount">
|
<Form.Row label="Amount:">
|
||||||
<Input
|
<Input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
id="amount"
|
id="amount"
|
||||||
@@ -289,7 +288,11 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
|
|||||||
name="submit"
|
name="submit"
|
||||||
disabled={!isDirty || !isValid || isLoading}
|
disabled={!isDirty || !isValid || isLoading}
|
||||||
>
|
>
|
||||||
{!editId ? 'Add' : 'Save'}
|
{
|
||||||
|
!editId
|
||||||
|
? 'Add'
|
||||||
|
: 'Save'
|
||||||
|
}
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
<Form.Button
|
<Form.Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -29,8 +29,15 @@ interface IBookingRow {
|
|||||||
|
|
||||||
const BookingRow = ({ booking }: IBookingRow) => {
|
const BookingRow = ({ booking }: IBookingRow) => {
|
||||||
const { checkOutBooking } = useCheckOut();
|
const { checkOutBooking } = useCheckOut();
|
||||||
const { id, users, startDate, endDate, rooms, amount, status } = booking;
|
const {
|
||||||
const statusText = status ? 'Check in' : 'Check out';
|
id,
|
||||||
|
users,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
rooms,
|
||||||
|
amount,
|
||||||
|
status
|
||||||
|
} = booking;
|
||||||
const formattedPrice = useMemo(() => formatCurrency(amount), [amount]);
|
const formattedPrice = useMemo(() => formatCurrency(amount), [amount]);
|
||||||
const renderEditBtn = useCallback(
|
const renderEditBtn = useCallback(
|
||||||
(onCloseModal: () => void) => (
|
(onCloseModal: () => void) => (
|
||||||
@@ -79,7 +86,11 @@ const BookingRow = ({ booking }: IBookingRow) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>{rooms?.name}</div>
|
<div>{rooms?.name}</div>
|
||||||
<div>{formattedPrice}</div>
|
<div>{formattedPrice}</div>
|
||||||
<div>{statusText}</div>
|
<div>{
|
||||||
|
status
|
||||||
|
? 'Check in'
|
||||||
|
: 'Check out'
|
||||||
|
}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Modal>
|
<Modal>
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ const StyledOperationTable = styled.div`
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledBooking, Title, StyledOperationTable };
|
export {
|
||||||
|
StyledBooking,
|
||||||
|
Title,
|
||||||
|
StyledOperationTable
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { Navigate } from 'react-router-dom';
|
|||||||
// Styled
|
// Styled
|
||||||
import { LoginLayout, StyledLogo, TitleStyled } from './styled';
|
import { LoginLayout, StyledLogo, TitleStyled } from './styled';
|
||||||
|
|
||||||
// Assets
|
|
||||||
import logo from '@assets/images/logo.png';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import LoginForm from './LoginForm';
|
import LoginForm from './LoginForm';
|
||||||
|
|
||||||
@@ -21,7 +18,7 @@ const Login = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LoginLayout>
|
<LoginLayout>
|
||||||
<StyledLogo src={logo} />
|
<StyledLogo src={'./assets/images/logo.png'} />
|
||||||
<TitleStyled>Log In</TitleStyled>
|
<TitleStyled>Log In</TitleStyled>
|
||||||
|
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
|||||||
@@ -40,4 +40,10 @@ const FieldInput = styled.input`
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export { LoginLayout, StyledLogo, TitleStyled, StyledLoginForm, FieldInput };
|
export {
|
||||||
|
LoginLayout,
|
||||||
|
StyledLogo,
|
||||||
|
TitleStyled,
|
||||||
|
StyledLoginForm,
|
||||||
|
FieldInput
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { Button, Heading, StyledNotFound } from '.';
|
||||||
|
|
||||||
|
|
||||||
|
const NotFound = () => {
|
||||||
|
const useMoveBack = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return () => navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledNotFound>
|
||||||
|
<Heading>The page not found!</Heading>
|
||||||
|
<Button onClick={useMoveBack}>Go back!</Button>
|
||||||
|
</StyledNotFound>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFound;
|
||||||
+6
-19
@@ -1,5 +1,4 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import styled from "styled-components";
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const StyledNotFound = styled.div`
|
const StyledNotFound = styled.div`
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -27,20 +26,8 @@ const Button = styled.button`
|
|||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const useMoveBack = () => {
|
export {
|
||||||
const navigate = useNavigate();
|
StyledNotFound,
|
||||||
return () => navigate(-1);
|
Heading,
|
||||||
};
|
Button,
|
||||||
|
}
|
||||||
const NotFound = () => {
|
|
||||||
const onBack = useMoveBack();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledNotFound>
|
|
||||||
<Heading>The page not found!</Heading>
|
|
||||||
<Button onClick={onBack}>Go back!</Button>
|
|
||||||
</StyledNotFound>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NotFound;
|
|
||||||
@@ -26,11 +26,13 @@ interface IRoomRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RoomRow = ({ room }: IRoomRow) => {
|
const RoomRow = ({ room }: IRoomRow) => {
|
||||||
const { id, name, price, status } = room;
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
status
|
||||||
|
} = room;
|
||||||
const { setIsDeleteRoom } = useSetIsDeleteRoom();
|
const { setIsDeleteRoom } = useSetIsDeleteRoom();
|
||||||
const statusText = status
|
|
||||||
? 'Unavailable'
|
|
||||||
: 'Available';
|
|
||||||
const formattedPrice = useMemo(() => formatCurrency(price), [price]);
|
const formattedPrice = useMemo(() => formatCurrency(price), [price]);
|
||||||
const renderEditBtn = useCallback(
|
const renderEditBtn = useCallback(
|
||||||
(onCloseModal: () => void) => (
|
(onCloseModal: () => void) => (
|
||||||
@@ -42,11 +44,11 @@ const RoomRow = ({ room }: IRoomRow) => {
|
|||||||
);
|
);
|
||||||
const renderDeleteBtn = useCallback(
|
const renderDeleteBtn = useCallback(
|
||||||
(onCloseModal: () => void) => (
|
(onCloseModal: () => void) => (
|
||||||
<Menus.Button icon={<HiTrash />} onClick={onCloseModal}>
|
<Menus.Button icon={<HiTrash />} onClick={onCloseModal} disabled={status}>
|
||||||
Delete
|
Delete
|
||||||
</Menus.Button>
|
</Menus.Button>
|
||||||
),
|
),
|
||||||
[]
|
[status]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -54,7 +56,11 @@ const RoomRow = ({ room }: IRoomRow) => {
|
|||||||
<div>{id}</div>
|
<div>{id}</div>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
<div>{formattedPrice}</div>
|
<div>{formattedPrice}</div>
|
||||||
<div>{statusText}</div>
|
<div>{
|
||||||
|
status
|
||||||
|
? 'Unavailable'
|
||||||
|
: 'Available'
|
||||||
|
}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Modal>
|
<Modal>
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ import Pagination from '@component/Pagination';
|
|||||||
|
|
||||||
const RoomTable = () => {
|
const RoomTable = () => {
|
||||||
const columnName = ['Id', 'Name', 'Price', 'Status'];
|
const columnName = ['Id', 'Name', 'Price', 'Status'];
|
||||||
const { isLoading, rooms, count } = useRooms();
|
const {
|
||||||
|
isLoading,
|
||||||
|
rooms,
|
||||||
|
count
|
||||||
|
} = useRooms();
|
||||||
|
|
||||||
const renderRoomRow = useCallback(
|
const renderRoomRow = useCallback(
|
||||||
(room: IRoom) => <RoomRow room={room} key={room.id} />,
|
(room: IRoom) => <RoomRow room={room} key={room.id} />,
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ const StyledOperationTable = styled.div`
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledRoom, Title, StyledOperationTable };
|
export {
|
||||||
|
StyledRoom,
|
||||||
|
Title,
|
||||||
|
StyledOperationTable
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ interface IUserRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UserRow = ({ user }: IUserRow) => {
|
const UserRow = ({ user }: IUserRow) => {
|
||||||
const { id, name, phone, isBooked } = user;
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
phone,
|
||||||
|
isBooked
|
||||||
|
} = user;
|
||||||
const { setIsDeleteUser } = useIsDeleteUser();
|
const { setIsDeleteUser } = useIsDeleteUser();
|
||||||
|
|
||||||
const renderEditBtn = useCallback(
|
const renderEditBtn = useCallback(
|
||||||
@@ -32,11 +37,11 @@ const UserRow = ({ user }: IUserRow) => {
|
|||||||
|
|
||||||
const renderDeleteBtn = useCallback(
|
const renderDeleteBtn = useCallback(
|
||||||
(onCloseModal: () => void) => (
|
(onCloseModal: () => void) => (
|
||||||
<Menus.Button icon={<HiTrash />} onClick={onCloseModal}>
|
<Menus.Button icon={<HiTrash />} onClick={onCloseModal} disabled={isBooked}>
|
||||||
Delete
|
Delete
|
||||||
</Menus.Button>
|
</Menus.Button>
|
||||||
),
|
),
|
||||||
[]
|
[isBooked]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
|
|||||||
@@ -25,8 +25,17 @@ import { useUsers } from '@hook/users/useUsers';
|
|||||||
import Pagination from '@component/Pagination';
|
import Pagination from '@component/Pagination';
|
||||||
|
|
||||||
const UserTable = () => {
|
const UserTable = () => {
|
||||||
const columnName = ['Id', 'Name', 'Phone', 'Is Booked'];
|
const columnName = [
|
||||||
const { isLoading, users, count } = useUsers();
|
'Id',
|
||||||
|
'Name',
|
||||||
|
'Phone',
|
||||||
|
'Is Booked'
|
||||||
|
];
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
users,
|
||||||
|
count
|
||||||
|
} = useUsers();
|
||||||
|
|
||||||
const renderUserRow = useCallback(
|
const renderUserRow = useCallback(
|
||||||
(user: IUser) => (
|
(user: IUser) => (
|
||||||
@@ -52,7 +61,7 @@ const UserTable = () => {
|
|||||||
|
|
||||||
{users && users.length ? (
|
{users && users.length ? (
|
||||||
<Menus>
|
<Menus>
|
||||||
<Table columns="10% 35% 30% 15% 10%">
|
<Table columns="10% 35% 30% 15% 5%">
|
||||||
<Table.Header headerColumn={columnName} />
|
<Table.Header headerColumn={columnName} />
|
||||||
<Table.Body<IUser> data={users} render={renderUserRow} />
|
<Table.Body<IUser> data={users} render={renderUserRow} />
|
||||||
<Table.Footer>
|
<Table.Footer>
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ const StyledOperationTable = styled.div`
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledUser, Title, StyledOperationTable };
|
export {
|
||||||
|
StyledUser,
|
||||||
|
Title,
|
||||||
|
StyledOperationTable
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import supabase from './supabaseService';
|
|||||||
// Types
|
// Types
|
||||||
import { IAccount } from '@type/account';
|
import { IAccount } from '@type/account';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login services
|
||||||
|
* @param param0 The object contains email and password to login
|
||||||
|
* @returns The data of account login if success
|
||||||
|
*/
|
||||||
const login = async ({ email, password }: ILogin) => {
|
const login = async ({ email, password }: ILogin) => {
|
||||||
const { data, error } = await supabase.auth.signInWithPassword({
|
const { data, error } = await supabase.auth.signInWithPassword({
|
||||||
email,
|
email,
|
||||||
@@ -20,6 +25,10 @@ const login = async ({ email, password }: ILogin) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data of account current login
|
||||||
|
* @returns The data of account currently login
|
||||||
|
*/
|
||||||
const getCurrentAccount = async () => {
|
const getCurrentAccount = async () => {
|
||||||
const { data } = await supabase.auth.getSession();
|
const { data } = await supabase.auth.getSession();
|
||||||
|
|
||||||
@@ -36,6 +45,9 @@ const getCurrentAccount = async () => {
|
|||||||
return accountData.user;
|
return accountData.user;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout services
|
||||||
|
*/
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
const { error } = await supabase.auth.signOut();
|
const { error } = await supabase.auth.signOut();
|
||||||
|
|
||||||
@@ -44,6 +56,11 @@ const logout = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update account currently login
|
||||||
|
* @param param0 The object contain fullName, password to be updated
|
||||||
|
* @returns The data of account after updated
|
||||||
|
*/
|
||||||
const updateAccount = async ({ fullName, password }: IAccount) => {
|
const updateAccount = async ({ fullName, password }: IAccount) => {
|
||||||
let accountUpdate: UserAttributes | null = null;
|
let accountUpdate: UserAttributes | null = null;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
BOOKINGS_TABLE,
|
BOOKINGS_TABLE,
|
||||||
ERROR_CHECKOUT,
|
ERROR_CHECKOUT,
|
||||||
ERROR_CREATE_BOOKING,
|
ERROR_CREATE_BOOKING,
|
||||||
ERROR_DELETE_BOOKING,
|
|
||||||
ERROR_FETCHING_BOOKING,
|
ERROR_FETCHING_BOOKING,
|
||||||
ERROR_UPDATE_BOOKING,
|
ERROR_UPDATE_BOOKING,
|
||||||
} from '@constant/messages';
|
} from '@constant/messages';
|
||||||
@@ -100,22 +99,6 @@ const createBooking = async (booking: IBooking): Promise<IBooking> => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete booking in database
|
|
||||||
* @param idRoom The id of booking need to delete
|
|
||||||
*/
|
|
||||||
const deleteBooking = async (idBooking: number) => {
|
|
||||||
const { error } = await supabase
|
|
||||||
.from(BOOKINGS_TABLE)
|
|
||||||
.delete()
|
|
||||||
.eq('id', idBooking);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error(error.message);
|
|
||||||
throw new Error(ERROR_DELETE_BOOKING);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check out booking services
|
* Check out booking services
|
||||||
* @param param0 The ICheckOutBooking object
|
* @param param0 The ICheckOutBooking object
|
||||||
@@ -150,6 +133,5 @@ export {
|
|||||||
createBooking,
|
createBooking,
|
||||||
getAllBookings,
|
getAllBookings,
|
||||||
updateBooking,
|
updateBooking,
|
||||||
deleteBooking,
|
|
||||||
checkOutBooking,
|
checkOutBooking,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { IUser } from '@type/user';
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
import supabase from './supabaseService';
|
import supabase from './supabaseService';
|
||||||
import { IDataState } from '@type/common';
|
|
||||||
|
// Constants
|
||||||
import { DEFAULT_PAGE_SIZE } from '@constant/config';
|
import { DEFAULT_PAGE_SIZE } from '@constant/config';
|
||||||
import {
|
import {
|
||||||
ERROR_CREATE_USER,
|
ERROR_CREATE_USER,
|
||||||
@@ -118,19 +119,6 @@ const getAllUsers = async ({
|
|||||||
return { data, count };
|
return { data, count };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserNotBooked = async (): Promise<IDataState[]> => {
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from(USERS_TABLE)
|
|
||||||
.select('id, name, isBooked');
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error(error.message);
|
|
||||||
throw new Error(ERROR_FETCHING_USER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateUserBookedStatus = async (
|
const updateUserBookedStatus = async (
|
||||||
id: number,
|
id: number,
|
||||||
isBooked: boolean
|
isBooked: boolean
|
||||||
@@ -153,6 +141,5 @@ export {
|
|||||||
createUser,
|
createUser,
|
||||||
getAllUsers,
|
getAllUsers,
|
||||||
setIsDeleteUser,
|
setIsDeleteUser,
|
||||||
getUserNotBooked,
|
|
||||||
updateUserBookedStatus,
|
updateUserBookedStatus,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Cabin';
|
font-family: 'Cabin';
|
||||||
src: url('../../assets/fonts/Cabin.woff2') format('woff2'),
|
src: url('./assets/fonts/Cabin.woff2') format('woff2'),
|
||||||
url('../../assets/fonts/Cabin.ttf') format('truetype');
|
url('./assets/fonts/Cabin.ttf') format('truetype');
|
||||||
|
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
"@commonStyle/*": ["./src/commons/styles/*"],
|
"@commonStyle/*": ["./src/commons/styles/*"],
|
||||||
"@type/*": ["./src/types/*"],
|
"@type/*": ["./src/types/*"],
|
||||||
"@page/*": ["./src/pages/*"],
|
"@page/*": ["./src/pages/*"],
|
||||||
"@assets/*": ["./src/assets/*"],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "test"],
|
"include": ["src", "test"],
|
||||||
|
|||||||
Reference in New Issue
Block a user