diff --git a/hotel-management/src/components/ConfirmMessage/ConfirmMessage.test.tsx b/hotel-management/src/components/ConfirmMessage/ConfirmMessage.test.tsx index 233ce05..afcaf70 100644 --- a/hotel-management/src/components/ConfirmMessage/ConfirmMessage.test.tsx +++ b/hotel-management/src/components/ConfirmMessage/ConfirmMessage.test.tsx @@ -3,14 +3,12 @@ import renderer from 'react-test-renderer'; // Components import ConfirmMessage from '.'; -describe('ConfirmMessage', () => { - const isDisable = true; +describe('ConfirmMessage testing snapshot', () => { const message = 'Hello World!'; const handleOnConfirm = jest.fn(); const wrapper = renderer.create( diff --git a/hotel-management/src/components/ConfirmMessage/index.tsx b/hotel-management/src/components/ConfirmMessage/index.tsx index ce9b911..8ea6d85 100644 --- a/hotel-management/src/components/ConfirmMessage/index.tsx +++ b/hotel-management/src/components/ConfirmMessage/index.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react'; +import { memo, useCallback } from 'react'; // Styled import { StyledConfirmDelete } from './styled'; @@ -8,27 +8,27 @@ import Button from '@commonStyle/Button'; interface IConfirmMessage { message: string; - disabled: boolean; onConfirm: () => void; onCloseModal?: () => void; } const ConfirmMessage = memo( - ({ message, disabled, onConfirm, onCloseModal }: IConfirmMessage) => { + ({ message, onConfirm, onCloseModal }: IConfirmMessage) => { + const handleConfirmBtn = useCallback(() => { + onConfirm(); + onCloseModal!(); + }, [onCloseModal, onConfirm]); + return (

{message}

- -
diff --git a/hotel-management/src/components/ConfirmMessage/styled.ts b/hotel-management/src/components/ConfirmMessage/styled.ts index 47590e1..5a772a5 100644 --- a/hotel-management/src/components/ConfirmMessage/styled.ts +++ b/hotel-management/src/components/ConfirmMessage/styled.ts @@ -13,7 +13,7 @@ const StyledConfirmDelete = styled.div` display: flex; justify-content: space-around; - padding-inline: 50px; + padding-inline: 100px; } `; diff --git a/hotel-management/src/components/Form/styled.ts b/hotel-management/src/components/Form/styled.ts index 08d47ed..01ed87f 100644 --- a/hotel-management/src/components/Form/styled.ts +++ b/hotel-management/src/components/Form/styled.ts @@ -34,8 +34,6 @@ const Error = styled.p` margin-top: 5px; padding-inline: 5px; - - width: 220px; `; const FormBtn = styled(Button)` diff --git a/hotel-management/src/components/Nav/index.tsx b/hotel-management/src/components/Nav/index.tsx index 7e0e452..b5bb011 100644 --- a/hotel-management/src/components/Nav/index.tsx +++ b/hotel-management/src/components/Nav/index.tsx @@ -1,5 +1,5 @@ // Components -import { MdSpaceDashboard } from 'react-icons/md'; +import { BiSolidCalendarAlt } from 'react-icons/bi'; import { HiUsers } from 'react-icons/hi2'; import { BsHouseDoorFill } from 'react-icons/bs'; @@ -7,14 +7,14 @@ import { BsHouseDoorFill } from 'react-icons/bs'; import { StyledNav, StyledNavLink } from './styled'; // Constants -import { DASHBOARD, ROOM, USER } from '@constant/path'; +import { BOOKING, ROOM, USER } from '@constant/path'; const Nav = () => { return ( - - - Dashboard + + + Booking diff --git a/hotel-management/src/components/Select/index.tsx b/hotel-management/src/components/Select/index.tsx index 720c0f0..622d80e 100644 --- a/hotel-management/src/components/Select/index.tsx +++ b/hotel-management/src/components/Select/index.tsx @@ -19,6 +19,7 @@ interface ISelect { onChange?: ChangeEventHandler | undefined; id?: string; ariaLabel: string; + disable?: boolean; } const Select = ({ @@ -28,6 +29,7 @@ const Select = ({ value, onChange, ariaLabel, + disable = false, }: ISelect) => { const { register } = useFormContext() ?? {}; @@ -38,6 +40,7 @@ const Select = ({ aria-label={ariaLabel} id={id} value={value} + disabled={disable} {...(register ? { ...register(id!, optionsConfigForm) } : { onChange: onChange })} diff --git a/hotel-management/src/components/Select/styled.ts b/hotel-management/src/components/Select/styled.ts index 93b089c..92ee349 100644 --- a/hotel-management/src/components/Select/styled.ts +++ b/hotel-management/src/components/Select/styled.ts @@ -6,6 +6,8 @@ const StyledSelect = styled.select` border: 1px solid var(--border-color); border-radius: var(--radius-sm); font-weight: 500; + + width: 200px; `; export { StyledSelect }; diff --git a/hotel-management/src/constants/commons.ts b/hotel-management/src/constants/commons.ts index 9e62af2..fc82f1f 100644 --- a/hotel-management/src/constants/commons.ts +++ b/hotel-management/src/constants/commons.ts @@ -48,6 +48,7 @@ const FORM = { DELETE: 'delete', USER: 'user-form', ROOM: 'room-form', + CHECKOUT: 'checkout', }; const REGEX = { diff --git a/hotel-management/src/contexts/UserRoomAvailableContext.ts b/hotel-management/src/contexts/UserRoomAvailableContext.ts index 2c0d36f..d7e3bed 100644 --- a/hotel-management/src/contexts/UserRoomAvailableContext.ts +++ b/hotel-management/src/contexts/UserRoomAvailableContext.ts @@ -82,7 +82,7 @@ const reducer = (state: IUserRoomState, action: IAction) => { usersAvailable: tempArr, }; } - + case 'updateStatusUser': { const tempArr = state.usersAvailable; const indexItemUpdate = tempArr.findIndex( diff --git a/hotel-management/src/helpers/helper.ts b/hotel-management/src/helpers/helper.ts index 51e74f9..5f88c70 100644 --- a/hotel-management/src/helpers/helper.ts +++ b/hotel-management/src/helpers/helper.ts @@ -10,4 +10,16 @@ const formatCurrency = (value: number): string => { }).format(value); }; -export { formatCurrency }; +const getDayDiff = (startDate: Date, endDate: Date): number => { + const msInDay = 24 * 60 * 60 * 1000; + + return Math.round( + Math.abs(Number(endDate) - Number(startDate)) / msInDay + ); +} + +const convertCurrencyToNumber = (currency: string) => { + return Number(currency.replace(/[^0-9.-]+/g,"")) +} + +export { formatCurrency, getDayDiff, convertCurrencyToNumber }; diff --git a/hotel-management/src/services/bookingServices.ts b/hotel-management/src/services/bookingServices.ts new file mode 100644 index 0000000..68b719f --- /dev/null +++ b/hotel-management/src/services/bookingServices.ts @@ -0,0 +1,150 @@ +// 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 => { + 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 room to database + * @param room The room object need to be add + */ +const createBooking = async (booking: IBooking): Promise => { + 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 room in database + * @param idRoom The id of room 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); + } +}; + +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, +}; diff --git a/hotel-management/src/services/roomServices.ts b/hotel-management/src/services/roomServices.ts index b0fd13e..ac08893 100644 --- a/hotel-management/src/services/roomServices.ts +++ b/hotel-management/src/services/roomServices.ts @@ -7,17 +7,18 @@ import supabase from './supabaseService'; // Constants import { DEFAULT_PAGE_SIZE } from '@constant/config'; - -const ROOMS_TABLE = 'rooms'; -const ERROR_FETCHING = "Can't fetch room data!"; -const ERROR_UPDATE_ROOM = "Can't update room!"; -const ERROR_CREATE_ROOM = "Can't create room!"; -const ERROR_DELETE_ROOM = "Can't delete room!"; +import { + ERROR_CREATE_ROOM, + ERROR_DELETE_ROOM, + ERROR_FETCHING_ROOM, + ERROR_UPDATE_ROOM, + ROOMS_TABLE, +} from '@constant/messages'; interface IGetAllRooms { sortBy: string; orderBy: string; - roomName: string; + roomSearch: string; page: number; } @@ -28,22 +29,27 @@ interface IGetAllRooms { const getAllRooms = async ({ sortBy, orderBy, - roomName, + roomSearch, page, }: IGetAllRooms): Promise<{ data: IRoom[]; count: number | null }> => { const from = (page - 1) * DEFAULT_PAGE_SIZE; const to = from + DEFAULT_PAGE_SIZE - 1; - const { data, error, count } = await supabase + let query = supabase .from(ROOMS_TABLE) .select('*', { count: 'exact' }) - .range(from, to) .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) { console.error(error.message); - throw new Error(ERROR_FETCHING); + throw new Error(ERROR_FETCHING_ROOM); } return { data, count }; @@ -101,18 +107,57 @@ const deleteRoom = async (idRoom: number) => { } }; -const getRoomsAvailable = async (): Promise => { +const getRoomById = async (idRoom: string): Promise => { const { data, error } = await supabase .from(ROOMS_TABLE) - .select('id, name') - .eq('status', false); + .select('*') + .eq('id', idRoom) + .single(); if (error) { console.error(error.message); - throw new Error(ERROR_FETCHING); + throw new Error(ERROR_FETCHING_ROOM); } return data; }; -export { getAllRooms, updateRoom, createRoom, deleteRoom, getRoomsAvailable }; +const getRoomsAvailable = async (): Promise => { + 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 => { + 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, +}; diff --git a/hotel-management/src/services/userServices.ts b/hotel-management/src/services/userServices.ts index 0a77f62..e203f27 100644 --- a/hotel-management/src/services/userServices.ts +++ b/hotel-management/src/services/userServices.ts @@ -1,17 +1,17 @@ // Types import { IUser } from '@type/users'; -import { IDataState } from '@type/common'; // Services import supabase from './supabaseService'; - -// Constants +import { IDataState } from '@type/common'; import { DEFAULT_PAGE_SIZE } from '@constant/config'; - -const USERS_TABLE = 'users'; -const ERROR_FETCHING = "Users can't be loaded!"; -const ERROR_CREATE_USER = "Can't create user!"; -const ERROR_UPDATE_USER = "Can't update user!"; +import { + ERROR_CREATE_USER, + ERROR_DELETE_USER, + ERROR_FETCHING_USER, + ERROR_UPDATE_USER, + USERS_TABLE, +} from '@constant/messages'; /** * Create user to the database @@ -24,6 +24,8 @@ const createUser = async (user: IUser): Promise => { .select() .single(); + console.log('Create'); + if (error) { console.error(error.message); throw new Error(ERROR_CREATE_USER); @@ -52,6 +54,19 @@ const updateUser = async (user: IUser): Promise => { 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 { sortBy: string; orderBy: string; @@ -75,16 +90,21 @@ const getAllUsers = async ({ const from = (page - 1) * DEFAULT_PAGE_SIZE; const to = from + DEFAULT_PAGE_SIZE - 1; - const { data, error, count } = await supabase + let query = supabase .from(USERS_TABLE) .select('*', { count: 'exact' }) - .range(from, to) .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) { console.error(error.message); - throw new Error(ERROR_FETCHING); + throw new Error(ERROR_FETCHING_USER); } return { data, count }; @@ -93,15 +113,38 @@ const getAllUsers = async ({ const getUserNotBooked = async (): Promise => { const { data, error } = await supabase .from(USERS_TABLE) - .select('id, name') - .eq('isBooked', false); + .select('id, name, isBooked'); if (error) { console.error(error.message); - throw new Error(ERROR_FETCHING); + throw new Error(ERROR_FETCHING_USER); } return data; }; -export { updateUser, createUser, getAllUsers, getUserNotBooked }; +const updateUserBookedStatus = async ( + id: number, + isBooked: boolean +): Promise => { + 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, +};