Merge pull request #42 from Nez27/feat/update-code-create-booking-service

Update code, create booking service
This commit is contained in:
Loi Phan
2023-11-30 09:01:26 +07:00
committed by GitHub
13 changed files with 335 additions and 57 deletions
@@ -3,14 +3,12 @@ import renderer from 'react-test-renderer';
// Components // Components
import ConfirmMessage from '.'; import ConfirmMessage from '.';
describe('ConfirmMessage', () => { describe('ConfirmMessage testing snapshot', () => {
const isDisable = true;
const message = 'Hello World!'; const message = 'Hello World!';
const handleOnConfirm = jest.fn(); const handleOnConfirm = jest.fn();
const wrapper = renderer.create( const wrapper = renderer.create(
<ConfirmMessage <ConfirmMessage
disabled={isDisable}
message={message} message={message}
onConfirm={handleOnConfirm} onConfirm={handleOnConfirm}
/> />
@@ -1,4 +1,4 @@
import { memo } from 'react'; import { memo, useCallback } from 'react';
// Styled // Styled
import { StyledConfirmDelete } from './styled'; import { StyledConfirmDelete } from './styled';
@@ -8,27 +8,27 @@ import Button from '@commonStyle/Button';
interface IConfirmMessage { interface IConfirmMessage {
message: string; message: string;
disabled: boolean;
onConfirm: () => void; onConfirm: () => void;
onCloseModal?: () => void; onCloseModal?: () => void;
} }
const ConfirmMessage = memo( const ConfirmMessage = memo(
({ message, disabled, onConfirm, onCloseModal }: IConfirmMessage) => { ({ message, onConfirm, onCloseModal }: IConfirmMessage) => {
const handleConfirmBtn = useCallback(() => {
onConfirm();
onCloseModal!();
}, [onCloseModal, onConfirm]);
return ( return (
<StyledConfirmDelete> <StyledConfirmDelete>
<p>{message}</p> <p>{message}</p>
<div> <div>
<Button variations="danger" disabled={disabled} onClick={onConfirm}> <Button variations="danger" onClick={handleConfirmBtn}>
Delete Yes
</Button> </Button>
<Button <Button variations="secondary" onClick={onCloseModal}>
variations="secondary" No
disabled={disabled}
onClick={onCloseModal}
>
Cancel
</Button> </Button>
</div> </div>
</StyledConfirmDelete> </StyledConfirmDelete>
@@ -13,7 +13,7 @@ const StyledConfirmDelete = styled.div`
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
padding-inline: 50px; padding-inline: 100px;
} }
`; `;
@@ -34,8 +34,6 @@ const Error = styled.p`
margin-top: 5px; margin-top: 5px;
padding-inline: 5px; padding-inline: 5px;
width: 220px;
`; `;
const FormBtn = styled(Button)` const FormBtn = styled(Button)`
@@ -1,5 +1,5 @@
// Components // Components
import { MdSpaceDashboard } from 'react-icons/md'; import { BiSolidCalendarAlt } from 'react-icons/bi';
import { HiUsers } from 'react-icons/hi2'; import { HiUsers } from 'react-icons/hi2';
import { BsHouseDoorFill } from 'react-icons/bs'; import { BsHouseDoorFill } from 'react-icons/bs';
@@ -7,14 +7,14 @@ import { BsHouseDoorFill } from 'react-icons/bs';
import { StyledNav, StyledNavLink } from './styled'; import { StyledNav, StyledNavLink } from './styled';
// Constants // Constants
import { DASHBOARD, ROOM, USER } from '@constant/path'; import { BOOKING, ROOM, USER } from '@constant/path';
const Nav = () => { const Nav = () => {
return ( return (
<StyledNav> <StyledNav>
<StyledNavLink to={DASHBOARD}> <StyledNavLink to={BOOKING}>
<MdSpaceDashboard /> <BiSolidCalendarAlt />
<span>Dashboard</span> <span>Booking</span>
</StyledNavLink> </StyledNavLink>
<StyledNavLink to={USER}> <StyledNavLink to={USER}>
<HiUsers /> <HiUsers />
@@ -19,6 +19,7 @@ interface ISelect {
onChange?: ChangeEventHandler<HTMLSelectElement> | undefined; onChange?: ChangeEventHandler<HTMLSelectElement> | undefined;
id?: string; id?: string;
ariaLabel: string; ariaLabel: string;
disable?: boolean;
} }
const Select = ({ const Select = ({
@@ -28,6 +29,7 @@ const Select = ({
value, value,
onChange, onChange,
ariaLabel, ariaLabel,
disable = false,
}: ISelect) => { }: ISelect) => {
const { register } = useFormContext() ?? {}; const { register } = useFormContext() ?? {};
@@ -38,6 +40,7 @@ const Select = ({
aria-label={ariaLabel} aria-label={ariaLabel}
id={id} id={id}
value={value} value={value}
disabled={disable}
{...(register {...(register
? { ...register(id!, optionsConfigForm) } ? { ...register(id!, optionsConfigForm) }
: { onChange: onChange })} : { onChange: onChange })}
@@ -6,6 +6,8 @@ const StyledSelect = styled.select`
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
font-weight: 500; font-weight: 500;
width: 200px;
`; `;
export { StyledSelect }; export { StyledSelect };
@@ -48,6 +48,7 @@ const FORM = {
DELETE: 'delete', DELETE: 'delete',
USER: 'user-form', USER: 'user-form',
ROOM: 'room-form', ROOM: 'room-form',
CHECKOUT: 'checkout',
}; };
const REGEX = { const REGEX = {
@@ -141,7 +141,7 @@ const reducer = (state: IUserRoomState, action: IAction) => {
roomsAvailable: tempArr, roomsAvailable: tempArr,
}; };
} }
default: default:
throw new Error('Action unknown'); throw new Error('Action unknown');
} }
+25 -1
View File
@@ -10,4 +10,28 @@ const formatCurrency = (value: number): string => {
}).format(value); }).format(value);
}; };
export { formatCurrency };
/**
* Calculate the day durations
* @param startDate The start of date
* @param endDate The end of date
* @returns The number of days between 2 date input
*/
const getDayDiff = (startDate: Date, endDate: Date): number => {
const msInDay = 24 * 60 * 60 * 1000;
return Math.round(
Math.abs(Number(endDate) - Number(startDate)) / msInDay
);
}
/**
* Convert from currency string to the number
* @param currency The currency string
* @returns The number
*/
const convertCurrencyToNumber = (currency: string) => {
return Number(currency.replace(/[^0-9.-]+/g,""))
}
export { formatCurrency, getDayDiff, convertCurrencyToNumber };
@@ -0,0 +1,155 @@
// Types
import { IBooking, TBookingResponse } from '@type/booking';
// Services
import supabase from './supabaseService';
// Constants
import { DEFAULT_PAGE_SIZE } from '@constant/config';
import {
BOOKINGS_TABLE,
ERROR_CHECKOUT,
ERROR_CREATE_BOOKING,
ERROR_DELETE_BOOKING,
ERROR_FETCHING_BOOKING,
ERROR_UPDATE_BOOKING,
} from '@constant/messages';
import { updateRoomStatus } from './roomServices';
import { updateUserBookedStatus } from './userServices';
interface ICheckOutBooking {
idBooking: number;
roomId: number;
userId: number;
}
interface IGetAllBookings {
userNameSearch: string;
page: number;
}
/**
* Get all bookings from database
* @returns Return all bookings in database
*/
const getAllBookings = async ({
userNameSearch,
page,
}: IGetAllBookings): Promise<{
data: TBookingResponse[];
count: number | null;
}> => {
const from = (page - 1) * DEFAULT_PAGE_SIZE;
const to = from + DEFAULT_PAGE_SIZE - 1;
const { data, error, count } = await supabase
.from(BOOKINGS_TABLE)
.select(
'id, startDate, endDate, status, amount, rooms(id, name), users!inner(id, name)',
{
count: 'exact',
}
)
.ilike('users.name', `%${userNameSearch}%`)
.range(from, to);
if (error) {
console.error(error.message);
throw new Error(ERROR_FETCHING_BOOKING);
}
return { data, count };
};
/**
* Update booking into database
* @param booking Booking object need to be updated
*/
const updateBooking = async (booking: IBooking): Promise<IBooking> => {
const { data, error } = await supabase
.from(BOOKINGS_TABLE)
.update(booking)
.eq('id', booking.id)
.select()
.single();
if (error) {
console.error(error.message);
throw new Error(ERROR_UPDATE_BOOKING);
}
return data;
};
/**
* Add booking to database
* @param booking The booking object need to be add
*/
const createBooking = async (booking: IBooking): Promise<IBooking> => {
const { data, error: createBookingError } = await supabase
.from(BOOKINGS_TABLE)
.insert(booking)
.select()
.single();
if (createBookingError) {
console.error(createBookingError.message);
throw new Error(ERROR_CREATE_BOOKING);
}
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
* @param param0 The ICheckOutBooking object
* @returns The data of booking after insert to database
*/
const checkOutBooking = async ({
idBooking,
roomId,
userId,
}: ICheckOutBooking) => {
// Update status booking
const { data, error } = await supabase
.from(BOOKINGS_TABLE)
.update({ status: false })
.eq('id', idBooking)
.select()
.single();
if (error) {
console.error(error.message);
throw new Error(ERROR_CHECKOUT);
}
// Update status room, user
await updateRoomStatus(roomId, false);
await updateUserBookedStatus(userId, false);
return data;
};
export {
createBooking,
getAllBookings,
updateBooking,
deleteBooking,
checkOutBooking,
};
+71 -17
View File
@@ -7,17 +7,18 @@ import supabase from './supabaseService';
// Constants // Constants
import { DEFAULT_PAGE_SIZE } from '@constant/config'; import { DEFAULT_PAGE_SIZE } from '@constant/config';
import {
const ROOMS_TABLE = 'rooms'; ERROR_CREATE_ROOM,
const ERROR_FETCHING = "Can't fetch room data!"; ERROR_DELETE_ROOM,
const ERROR_UPDATE_ROOM = "Can't update room!"; ERROR_FETCHING_ROOM,
const ERROR_CREATE_ROOM = "Can't create room!"; ERROR_UPDATE_ROOM,
const ERROR_DELETE_ROOM = "Can't delete room!"; ROOMS_TABLE,
} from '@constant/messages';
interface IGetAllRooms { interface IGetAllRooms {
sortBy: string; sortBy: string;
orderBy: string; orderBy: string;
roomName: string; roomSearch: string;
page: number; page: number;
} }
@@ -28,22 +29,27 @@ interface IGetAllRooms {
const getAllRooms = async ({ const getAllRooms = async ({
sortBy, sortBy,
orderBy, orderBy,
roomName, roomSearch,
page, page,
}: IGetAllRooms): Promise<{ data: IRoom[]; count: number | null }> => { }: IGetAllRooms): Promise<{ data: IRoom[]; count: number | null }> => {
const from = (page - 1) * DEFAULT_PAGE_SIZE; const from = (page - 1) * DEFAULT_PAGE_SIZE;
const to = from + DEFAULT_PAGE_SIZE - 1; const to = from + DEFAULT_PAGE_SIZE - 1;
const { data, error, count } = await supabase let query = supabase
.from(ROOMS_TABLE) .from(ROOMS_TABLE)
.select('*', { count: 'exact' }) .select('*', { count: 'exact' })
.range(from, to)
.order(sortBy, { ascending: orderBy === 'asc' }) .order(sortBy, { ascending: orderBy === 'asc' })
.ilike('name', `%${roomName}%`); .like('name', `%${roomSearch}%`);
if(page) {
query = query.range(from, to);
}
const { data, error, count } = await query;
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_FETCHING); throw new Error(ERROR_FETCHING_ROOM);
} }
return { data, count }; return { data, count };
@@ -101,18 +107,66 @@ const deleteRoom = async (idRoom: number) => {
} }
}; };
const getRoomsAvailable = async (): Promise<IDataState[]> => { /**
* Get room by id in database
* @param idRoom The id room need to be get
* @returns The data of room
*/
const getRoomById = async (idRoom: string): Promise<IRoom> => {
const { data, error } = await supabase const { data, error } = await supabase
.from(ROOMS_TABLE) .from(ROOMS_TABLE)
.select('id, name') .select('*')
.eq('status', false); .eq('id', idRoom)
.single();
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_FETCHING); throw new Error(ERROR_FETCHING_ROOM);
} }
return data; return data;
}; };
export { getAllRooms, updateRoom, createRoom, deleteRoom, getRoomsAvailable }; /**
*
* @returns
*/
const getRoomsAvailable = async (): Promise<IDataState[]> => {
const { data, error } = await supabase
.from(ROOMS_TABLE)
.select('id, name, status');
if (error) {
console.error(error.message);
throw new Error(ERROR_FETCHING_ROOM);
}
return data;
};
const updateRoomStatus = async (
id: number,
status: boolean
): Promise<number> => {
const { error, status: statusConnection } = await supabase
.from(ROOMS_TABLE)
.update({ status })
.eq('id', id);
if (error) {
console.error(error.message);
throw new Error(ERROR_UPDATE_ROOM);
}
return statusConnection;
};
export {
getAllRooms,
updateRoom,
createRoom,
deleteRoom,
getRoomsAvailable,
getRoomById,
updateRoomStatus,
};
+59 -16
View File
@@ -1,17 +1,17 @@
// Types // Types
import { IUser } from '@type/users'; import { IUser } from '@type/users';
import { IDataState } from '@type/common';
// 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 {
const USERS_TABLE = 'users'; ERROR_CREATE_USER,
const ERROR_FETCHING = "Users can't be loaded!"; ERROR_DELETE_USER,
const ERROR_CREATE_USER = "Can't create user!"; ERROR_FETCHING_USER,
const ERROR_UPDATE_USER = "Can't update user!"; ERROR_UPDATE_USER,
USERS_TABLE,
} from '@constant/messages';
/** /**
* Create user to the database * Create user to the database
@@ -24,6 +24,8 @@ const createUser = async (user: IUser): Promise<IUser> => {
.select() .select()
.single(); .single();
console.log('Create');
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_CREATE_USER); throw new Error(ERROR_CREATE_USER);
@@ -52,6 +54,19 @@ const updateUser = async (user: IUser): Promise<IUser> => {
return data; return data;
}; };
/**
* Delete user in database
* @param idUser The id of user need to delete
*/
const deleteUser = async (idUser: number) => {
const { error } = await supabase.from('users').delete().eq('id', idUser);
if (error) {
console.error(error.message);
throw new Error(ERROR_DELETE_USER);
}
};
interface IGetAllUsers { interface IGetAllUsers {
sortBy: string; sortBy: string;
orderBy: string; orderBy: string;
@@ -75,16 +90,21 @@ const getAllUsers = async ({
const from = (page - 1) * DEFAULT_PAGE_SIZE; const from = (page - 1) * DEFAULT_PAGE_SIZE;
const to = from + DEFAULT_PAGE_SIZE - 1; const to = from + DEFAULT_PAGE_SIZE - 1;
const { data, error, count } = await supabase let query = supabase
.from(USERS_TABLE) .from(USERS_TABLE)
.select('*', { count: 'exact' }) .select('*', { count: 'exact' })
.range(from, to)
.order(sortBy, { ascending: orderBy === 'asc' }) .order(sortBy, { ascending: orderBy === 'asc' })
.ilike('phone', `%${phoneSearch}%`); .like('phone', `%${phoneSearch}%`);
if (page) {
query = query.range(from, to);
}
const { data, error, count } = await query;
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_FETCHING); throw new Error(ERROR_FETCHING_USER);
} }
return { data, count }; return { data, count };
@@ -93,15 +113,38 @@ const getAllUsers = async ({
const getUserNotBooked = async (): Promise<IDataState[]> => { const getUserNotBooked = async (): Promise<IDataState[]> => {
const { data, error } = await supabase const { data, error } = await supabase
.from(USERS_TABLE) .from(USERS_TABLE)
.select('id, name') .select('id, name, isBooked');
.eq('isBooked', false);
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_FETCHING); throw new Error(ERROR_FETCHING_USER);
} }
return data; return data;
}; };
export { updateUser, createUser, getAllUsers, getUserNotBooked }; const updateUserBookedStatus = async (
id: number,
isBooked: boolean
): Promise<number> => {
const { error, status } = await supabase
.from(USERS_TABLE)
.update({ isBooked })
.eq('id', id);
if (error) {
console.error(error.message);
throw new Error(ERROR_UPDATE_USER);
}
return status;
};
export {
updateUser,
createUser,
getAllUsers,
deleteUser,
getUserNotBooked,
updateUserBookedStatus,
};