Fix comments

This commit is contained in:
2023-11-30 22:28:13 +07:00
parent 3f2747379c
commit 60b4723ddf
21 changed files with 274 additions and 140 deletions
@@ -23,6 +23,7 @@ interface IButton {
children?: string;
icon?: ReactNode;
onClick?: () => void;
disabled?: boolean;
}
const Menus = ({ children }: { children: ReactNode }) => {
@@ -69,7 +70,7 @@ const List = ({
return <StyledList ref={ref}>{children}</StyledList>;
};
const Button = ({ children, icon, onClick }: IButton): ReactNode => {
const Button = ({ children, icon, onClick, disabled }: IButton): ReactNode => {
const { close } = useContext(MenusContext);
const handleClick = () => {
onClick?.();
@@ -83,6 +84,7 @@ const Button = ({ children, icon, onClick }: IButton): ReactNode => {
onClick={handleClick}
iconStyle={{ color: COLOR.PRIMARY, size: '19px' }}
style={{ fontSize: '16px' }}
disabled={disabled}
>
{children}
</ButtonIcon>
@@ -4,7 +4,7 @@ import renderer from 'react-test-renderer';
import Table from '.';
// Types
import { IUser } from '@type/users';
import { IUser } from '@type/user';
interface ITableRow {
user: IUser;
@@ -16,7 +16,9 @@ type TAction =
| 'addUser'
| 'updateStatusUser'
| 'updateUserName'
| 'updateRoomName';
| 'updateRoomName'
| 'removeUser'
| 'removeRoom';
interface IAction {
type: TAction;
@@ -96,6 +98,12 @@ const reducer = (state: IUserRoomState, action: IAction) => {
};
}
case 'removeUser':
return {
...state,
usersAvailable: state.usersAvailable.filter((user) => user.id !== action.payload[0].id)
}
case 'addRoom': {
const tempArr = state.roomsAvailable;
const itemExist = state.roomsAvailable.find(
@@ -142,6 +150,12 @@ const reducer = (state: IUserRoomState, action: IAction) => {
};
}
case 'removeRoom':
return {
...state,
roomsAvailable: state.roomsAvailable.filter((room) => room.id !== action.payload[0].id)
}
default:
throw new Error('Action unknown');
}
+5 -1
View File
@@ -26,4 +26,8 @@ const isValidString = (value: string): boolean => {
return value.trim().length >= 5;
};
export { isValidRegex, isValidString, isValidDiscount };
const compareTwoDates = (startDate: Date, endDate: Date): boolean => {
return startDate > endDate;
};
export { isValidRegex, isValidString, isValidDiscount, compareTwoDates };
@@ -2,34 +2,38 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
// Services
import { deleteRoom as deleteRoomFn } from '@service/roomServices';
import { setIsDeleteRoom as setIsDeleteRoomFn } from '@service/roomServices';
// Constants
import * as messages from '@constant/messages';
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
/**
* Delete the room from database
* @returns The boolean of status deleting and deleteRoom function
*/
const useDeleteRoom = () => {
const useSetIsDeleteRoom = () => {
const queryClient = useQueryClient();
const { dispatch } = useUserRoomAvailable();
const {
isPending: isDeleting,
mutate: deleteRoom,
isSuccess
mutate: setIsDeleteRoom,
isSuccess,
} = useMutation({
mutationFn: deleteRoomFn,
onSuccess: () => {
mutationFn: setIsDeleteRoomFn,
onSuccess: (room) => {
toast.success(messages.DELETE_SUCCESS);
queryClient.invalidateQueries({
queryKey: ['rooms'],
});
dispatch?.({ type: 'removeRoom', payload: [{ id: room.id }] });
},
onError: (err) => toast.error(err.message),
});
return { isDeleting, deleteRoom, isSuccess };
return { isDeleting, setIsDeleteRoom, isSuccess };
};
export { useDeleteRoom };
export { useSetIsDeleteRoom };
@@ -2,34 +2,39 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
// Services
import { deleteUser as deleteUserFn } from '@service/userServices';
import { setIsDeleteUser as setIsDeleteUserFn } from '@service/userServices';
// Constants
import * as messages from '@constant/messages';
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
/**
* Delete the user from database
* @returns The boolean of status deleting and deleteUser function
*/
const useDeleteUser = () => {
const useIsDeleteUser = () => {
const queryClient = useQueryClient();
const { dispatch } = useUserRoomAvailable();
const {
isPending: isDeleting,
mutate: deleteUser,
mutate: setIsDeleteUser,
isSuccess,
} = useMutation({
mutationFn: deleteUserFn,
onSuccess: () => {
mutationFn: setIsDeleteUserFn,
onSuccess: (user) => {
toast.success(messages.DELETE_SUCCESS);
queryClient.invalidateQueries({
queryKey: ['users'],
});
// Remove user from global state
dispatch?.({ type: 'removeUser', payload: [{ id: user.id }] });
},
onError: (err) => toast.error(err.message),
});
return { isDeleting, deleteUser, isSuccess };
return { isDeleting, setIsDeleteUser, isSuccess };
};
export { useDeleteUser };
export { useIsDeleteUser };
@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
// Styled
@@ -17,11 +17,7 @@ import { useUpdateBooking } from '@hook/bookings/useUpdateBooking.ts';
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable.ts';
// Helpers
import {
convertCurrencyToNumber,
formatCurrency,
getDayDiff,
} from '@helper/helper.ts';
import { formatCurrency, getDayDiff } from '@helper/helper.ts';
// Services
import { getRoomById, updateRoomStatus } from '@service/roomServices.ts';
@@ -35,24 +31,25 @@ interface IBookingFormProp {
booking?: TBookingResponse;
}
interface IBookingForm extends Omit<IBooking, 'amount'> {
amount: string;
}
const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
const { roomsAvailable, usersAvailable, dispatch } = useUserRoomAvailable();
const { isCreating, createBooking } = useCreateBooking();
const { isUpdating, updateBooking } = useUpdateBooking();
const isLoading = isCreating || isUpdating;
const { id: editId, ...editValues } = { ...booking };
const formMethods = useForm<IBookingForm>({
const [ amountValue, setAmountValue ] = useState(
editValues.amount
? formatCurrency(editValues.amount)
: '$0.00'
);
const formMethods = useForm<IBooking>({
defaultValues: editId
? {
startDate: editValues.startDate,
endDate: editValues.endDate,
roomId: editValues.rooms?.id,
userId: editValues.users?.id,
amount: formatCurrency(editValues.amount!),
amount: editValues.amount!,
}
: {},
});
@@ -67,12 +64,10 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
} = formMethods;
// Submit form
const onSubmit = async (newBooking: IBookingForm) => {
const onSubmit = async (newBooking: IBooking) => {
if (!editId) {
// Add request
createBooking(
{ ...newBooking, amount: convertCurrencyToNumber(newBooking.amount) },
{
createBooking(newBooking, {
onSuccess: async () => {
reset();
onCloseModal?.();
@@ -91,14 +86,11 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
await updateRoomStatus(newBooking.roomId, true);
await updateUserBookedStatus(newBooking.userId, true);
},
}
);
});
} else {
// Edit request
newBooking.id = editId!;
updateBooking(
{ ...newBooking, amount: convertCurrencyToNumber(newBooking.amount) },
{
updateBooking(newBooking, {
onSuccess: async () => {
reset();
onCloseModal?.();
@@ -123,8 +115,7 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
// Change status new room
await updateRoomStatus(newBooking.roomId, true);
},
}
);
});
}
};
@@ -161,12 +152,13 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
// Calculate the final price
const computePrice = useCallback(async () => {
setValue('amount', 'Loading...');
const startDateValue = getValues('startDate');
const endDateValue = getValues('endDate');
const roomValue = getValues('roomId');
if (startDateValue && endDateValue && roomValue) {
setAmountValue('Loading...');
const daysDiff = getDayDiff(
new Date(startDateValue),
new Date(endDateValue)
@@ -176,10 +168,20 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
const room = await getRoomById(roomValue.toString());
const amount = daysDiff * room.price;
setValue('amount', formatCurrency(amount), { shouldValidate: true });
setValue('amount', amount, { shouldValidate: true });
setAmountValue(formatCurrency(amount));
}
}, [getValues, setValue]);
const checkValidTwoDates = useCallback(() => {
const startDate = new Date(getValues().startDate);
const endDate = new Date(getValues().endDate);
if(startDate >= endDate) {
setValue('endDate', '');
}
}, [setValue, getValues]);
const startDateValidate = useMemo(
() => new Date().toISOString().split('T')[0],
[]
@@ -241,13 +243,15 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
required: REQUIRED_FIELD_ERROR,
onChange: () => {
trigger('startDate');
checkValidTwoDates();
computePrice();
},
})}
/>
</Form.Row>
<Form.Row label="End Date" error={errors?.startDate?.message}>
<Form.Row label="End Date" error={errors?.endDate?.message}>
<Input
type="date"
id="endDate"
@@ -259,13 +263,15 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
trigger('endDate');
computePrice();
},
validate: (endDateValue) =>
endDateValue > getValues().startDate || 'Invalid date',
})}
/>
</Form.Row>
<Form.Row label="Amount">
<Input
type="text"
type="hidden"
id="amount"
{...register('amount', {
required: REQUIRED_FIELD_ERROR,
@@ -273,6 +279,8 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
})}
readOnly
/>
<span>{amountValue}</span>
</Form.Row>
<Form.Action>
@@ -281,13 +289,13 @@ const BookingForm = ({ onCloseModal, booking }: IBookingFormProp) => {
name="submit"
disabled={!isDirty || !isValid || isLoading}
>
{
!editId
? 'Add'
: 'Save'
}
{!editId ? 'Add' : 'Save'}
</Form.Button>
<Form.Button type="button" variations="secondary" onClick={onCloseModal}>
<Form.Button
type="button"
variations="secondary"
onClick={onCloseModal}
>
Close
</Form.Button>
</Form.Action>
@@ -29,35 +29,33 @@ interface IBookingRow {
const BookingRow = ({ booking }: IBookingRow) => {
const { checkOutBooking } = useCheckOut();
const {
id,
users,
startDate,
endDate,
rooms,
amount,
status
} = booking;
const statusText = status
? 'Check in'
: 'Check out';
const { id, users, startDate, endDate, rooms, amount, status } = booking;
const statusText = status ? 'Check in' : 'Check out';
const formattedPrice = useMemo(() => formatCurrency(amount), [amount]);
const renderEditBtn = useCallback(
(onCloseModal: () => void) => (
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}>
<Menus.Button
onClick={onCloseModal}
icon={<RiEditBoxFill />}
disabled={!status}
>
Edit
</Menus.Button>
),
[]
[status]
);
const renderCheckOutBtn = useCallback(
(onCloseModal: () => void) => (
<Menus.Button onClick={onCloseModal} icon={<ImExit />}>
<Menus.Button
onClick={onCloseModal}
icon={<ImExit />}
disabled={!status}
>
Checkout
</Menus.Button>
),
[]
[status]
);
const handleClickCheckOutBtn = useCallback(() => {
+1 -1
View File
@@ -2,7 +2,7 @@
import Input from '@commonStyle/Input.ts';
// Types
import { IRoom } from '@type/rooms.ts';
import { IRoom } from '@type/room.ts';
// Constants
import {
+4 -4
View File
@@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react';
import { formatCurrency } from '@helper/helper';
// Types
import { IRoom } from '@type/rooms';
import { IRoom } from '@type/room';
// Components
import RoomForm from './RoomForm';
@@ -16,7 +16,7 @@ import { HiTrash } from 'react-icons/hi';
import ConfirmMessage from '@component/ConfirmMessage';
// Hooks
import { useDeleteRoom } from '@hook/rooms/useDeleteRoom';
import { useSetIsDeleteRoom } from '@hook/rooms/useSetIsDeleteRoom';
// Constants
import { FORM } from '@constant/commons';
@@ -27,7 +27,7 @@ interface IRoomRow {
const RoomRow = ({ room }: IRoomRow) => {
const { id, name, price, status } = room;
const { deleteRoom } = useDeleteRoom();
const { setIsDeleteRoom } = useSetIsDeleteRoom();
const statusText = status
? 'Unavailable'
: 'Available';
@@ -79,7 +79,7 @@ const RoomRow = ({ room }: IRoomRow) => {
<Modal.Window name={FORM.DELETE} title="Delete Room">
<ConfirmMessage
message={`Are you sure to delete ${name}?`}
onConfirm={() => deleteRoom(id)}
onConfirm={() => setIsDeleteRoom(id)}
/>
</Modal.Window>
</Menus.Menu>
@@ -10,7 +10,7 @@ import OrderBy from '@component/OrderBy';
import RoomRow from './RoomRow';
// Types
import { IRoom } from '@type/rooms';
import { IRoom } from '@type/room';
// Constants
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons';
@@ -2,7 +2,7 @@ import renderer from 'react-test-renderer';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// Types
import { IRoom } from '@type/rooms';
import { IRoom } from '@type/room';
// Components
import RoomRow from '../RoomRow';
+1 -1
View File
@@ -20,7 +20,7 @@ import {
import { REGEX } from '@constant/commons.ts';
// Types
import { IUser } from '@type/users.ts';
import { IUser } from '@type/user';
interface IUserFormProp {
onCloseModal?: () => void;
+4 -4
View File
@@ -6,12 +6,12 @@ import Menus from '@component/Menus';
import UserForm from './UserForm';
// Types
import { IUser } from '@type/users';
import { IUser } from '@type/user';
import { FORM } from '@constant/commons';
import { HiTrash } from 'react-icons/hi';
import { useCallback } from 'react';
import ConfirmMessage from '@component/ConfirmMessage';
import { useDeleteUser } from '@hook/users/useDeleteUser';
import { useIsDeleteUser } from '@hook/users/useSetIsDeleteUser';
interface IUserRow {
user: IUser;
@@ -19,7 +19,7 @@ interface IUserRow {
const UserRow = ({ user }: IUserRow) => {
const { id, name, phone, isBooked } = user;
const { deleteUser } = useDeleteUser();
const { setIsDeleteUser } = useIsDeleteUser();
const renderEditBtn = useCallback(
(onCloseModal: () => void) => (
@@ -72,7 +72,7 @@ const UserRow = ({ user }: IUserRow) => {
<Modal.Window name={FORM.DELETE} title="Delete User">
<ConfirmMessage
message={`Are you sure to delete ${name}?`}
onConfirm={() => deleteUser(id)}
onConfirm={() => setIsDeleteUser(id)}
/>
</Modal.Window>
</Menus.Menu>
@@ -10,7 +10,7 @@ import OrderBy from '@component/OrderBy';
import UserRow from './UserRow';
// Types
import { IUser } from '@type/users';
import { IUser } from '@type/user';
// Constants
import { ORDERBY_OPTIONS, USER_PAGE } from '@constant/commons';
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import renderer from 'react-test-renderer';
// Types
import { IUser } from '@type/users';
import { IUser } from '@type/user';
// Components
import UserRow from '../UserRow';
+16 -13
View File
@@ -1,5 +1,5 @@
// Types
import { IRoom } from '@type/rooms';
import { IRoom } from '@type/room';
// Services
import supabase from './supabaseService';
@@ -38,7 +38,8 @@ const getAllRooms = async ({
.from(ROOMS_TABLE)
.select('*', { count: 'exact' })
.order(sortBy, { ascending: orderBy === 'asc' })
.like('name', `%${roomSearch}%`);
.like('name', `%${roomSearch}%`)
.eq('isDelete', false);
if (page) {
query = query.range(from, to);
@@ -94,16 +95,23 @@ const createRoom = async (room: IRoom): Promise<IRoom> => {
};
/**
* Delete room in database
* @param idRoom The id of room need to delete
* Set room is delete status
* @param idRoom The id of room need to set
*/
const deleteRoom = async (idRoom: number) => {
const { error } = await supabase.from(ROOMS_TABLE).delete().eq('id', idRoom);
const setIsDeleteRoom = async (idRoom: number) => {
const { data, error } = await supabase
.from(ROOMS_TABLE)
.update({ isDelete: true })
.eq('id', idRoom)
.select()
.single();
if (error) {
console.error(error.message);
throw new Error(ERROR_DELETE_ROOM);
}
return data;
};
/**
@@ -126,16 +134,12 @@ const getRoomById = async (idRoom: string): Promise<IRoom> => {
return data;
};
/**
* Update status of room
* @param id The id of room need to be change status
* @param status The status of room
*/
const updateRoomStatus = async (
id: number,
status: boolean
): Promise<void> => {
const updateRoomStatus = async (id: number, status: boolean): Promise<void> => {
const { error } = await supabase
.from(ROOMS_TABLE)
.update({ status })
@@ -145,14 +149,13 @@ const updateRoomStatus = async (
console.error(error.message);
throw new Error(ERROR_UPDATE_ROOM);
}
};
export {
getAllRooms,
updateRoom,
createRoom,
deleteRoom,
setIsDeleteRoom,
getRoomById,
updateRoomStatus,
};
+14 -6
View File
@@ -1,5 +1,5 @@
// Types
import { IUser } from '@type/users';
import { IUser } from '@type/user';
// Services
import supabase from './supabaseService';
@@ -55,16 +55,23 @@ const updateUser = async (user: IUser): Promise<IUser> => {
};
/**
* Delete user in database
* Set user in delete status
* @param idUser The id of user need to delete
*/
const deleteUser = async (idUser: number) => {
const { error } = await supabase.from('users').delete().eq('id', idUser);
const setIsDeleteUser = async (idUser: number) => {
const { data, error } = await supabase
.from(USERS_TABLE)
.update({isDelete: true})
.eq('id', idUser)
.select()
.single();
if (error) {
console.error(error.message);
throw new Error(ERROR_DELETE_USER);
}
return data;
};
interface IGetAllUsers {
@@ -94,7 +101,8 @@ const getAllUsers = async ({
.from(USERS_TABLE)
.select('*', { count: 'exact' })
.order(sortBy, { ascending: orderBy === 'asc' })
.like('phone', `%${phoneSearch}%`);
.like('phone', `%${phoneSearch}%`)
.eq('isDelete', false);
if (page) {
query = query.range(from, to);
@@ -144,7 +152,7 @@ export {
updateUser,
createUser,
getAllUsers,
deleteUser,
setIsDeleteUser,
getUserNotBooked,
updateUserBookedStatus,
};
@@ -3,6 +3,7 @@ interface IRoom {
name: string;
price: number;
status: boolean;
isDelete: boolean;
}
export type { IRoom };
+87 -1
View File
@@ -25,7 +25,7 @@ export interface Database {
id?: number
roomId: number
startDate: string
status: boolean
status?: boolean
userId: number
}
Update: {
@@ -57,18 +57,21 @@ export interface Database {
rooms: {
Row: {
id: number
isDelete: boolean
name: string
price: number
status: boolean
}
Insert: {
id?: number
isDelete?: boolean
name: string
price: number
status?: boolean
}
Update: {
id?: number
isDelete?: boolean
name?: string
price?: number
status?: boolean
@@ -79,18 +82,21 @@ export interface Database {
Row: {
id: number
isBooked: boolean
isDelete: boolean
name: string
phone: string
}
Insert: {
id?: number
isBooked?: boolean
isDelete?: boolean
name: string
phone: string
}
Update: {
id?: number
isBooked?: boolean
isDelete?: boolean
name?: string
phone?: string
}
@@ -111,3 +117,83 @@ export interface Database {
}
}
}
export type Tables<
PublicTableNameOrOptions extends
| keyof (Database["public"]["Tables"] & Database["public"]["Views"])
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
Database[PublicTableNameOrOptions["schema"]]["Views"])
: never = never
> = PublicTableNameOrOptions extends { schema: keyof Database }
? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R
}
? R
: never
: PublicTableNameOrOptions extends keyof (Database["public"]["Tables"] &
Database["public"]["Views"])
? (Database["public"]["Tables"] &
Database["public"]["Views"])[PublicTableNameOrOptions] extends {
Row: infer R
}
? R
: never
: never
export type TablesInsert<
PublicTableNameOrOptions extends
| keyof Database["public"]["Tables"]
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
: never = never
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I
}
? I
: never
: PublicTableNameOrOptions extends keyof Database["public"]["Tables"]
? Database["public"]["Tables"][PublicTableNameOrOptions] extends {
Insert: infer I
}
? I
: never
: never
export type TablesUpdate<
PublicTableNameOrOptions extends
| keyof Database["public"]["Tables"]
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
: never = never
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U
}
? U
: never
: PublicTableNameOrOptions extends keyof Database["public"]["Tables"]
? Database["public"]["Tables"][PublicTableNameOrOptions] extends {
Update: infer U
}
? U
: never
: never
export type Enums<
PublicEnumNameOrOptions extends
| keyof Database["public"]["Enums"]
| { schema: keyof Database },
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
: never = never
> = PublicEnumNameOrOptions extends { schema: keyof Database }
? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
: PublicEnumNameOrOptions extends keyof Database["public"]["Enums"]
? Database["public"]["Enums"][PublicEnumNameOrOptions]
: never
@@ -3,6 +3,7 @@ interface IUser {
name: string;
phone: string;
isBooked: boolean;
isDelete: boolean;
}
export type { IUser };