Update code, create booking services

This commit is contained in:
2023-11-29 19:33:37 +07:00
parent 110be87c90
commit 139db98283
13 changed files with 309 additions and 57 deletions
@@ -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(
<ConfirmMessage
disabled={isDisable}
message={message}
onConfirm={handleOnConfirm}
/>
@@ -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 (
<StyledConfirmDelete>
<p>{message}</p>
<div>
<Button variations="danger" disabled={disabled} onClick={onConfirm}>
Delete
<Button variations="danger" onClick={handleConfirmBtn}>
Yes
</Button>
<Button
variations="secondary"
disabled={disabled}
onClick={onCloseModal}
>
Cancel
<Button variations="secondary" onClick={onCloseModal}>
No
</Button>
</div>
</StyledConfirmDelete>
@@ -13,7 +13,7 @@ const StyledConfirmDelete = styled.div`
display: flex;
justify-content: space-around;
padding-inline: 50px;
padding-inline: 100px;
}
`;
@@ -34,8 +34,6 @@ const Error = styled.p`
margin-top: 5px;
padding-inline: 5px;
width: 220px;
`;
const FormBtn = styled(Button)`
@@ -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 (
<StyledNav>
<StyledNavLink to={DASHBOARD}>
<MdSpaceDashboard />
<span>Dashboard</span>
<StyledNavLink to={BOOKING}>
<BiSolidCalendarAlt />
<span>Booking</span>
</StyledNavLink>
<StyledNavLink to={USER}>
<HiUsers />
@@ -19,6 +19,7 @@ interface ISelect {
onChange?: ChangeEventHandler<HTMLSelectElement> | 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 })}
@@ -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 };
@@ -48,6 +48,7 @@ const FORM = {
DELETE: 'delete',
USER: 'user-form',
ROOM: 'room-form',
CHECKOUT: 'checkout',
};
const REGEX = {
+13 -1
View File
@@ -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 };
@@ -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<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 room to database
* @param room The room 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 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,
};
+62 -17
View File
@@ -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<IDataState[]> => {
const getRoomById = async (idRoom: string): Promise<IRoom> => {
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<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
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<IUser> => {
.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<IUser> => {
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<IDataState[]> => {
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<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,
};