Files
Hotel-Booking/Frontend/src/features/bookings/services/bookingService.ts
Iliyan Angelov 7acf05e186 big update
2025-12-12 01:48:04 +02:00

390 lines
10 KiB
TypeScript

import apiClient from '../../../shared/services/apiClient';
export interface BookingData {
room_id: number;
check_in_date: string;
check_out_date: string;
guest_count: number;
notes?: string;
payment_method: 'cash' | 'stripe' | 'paypal';
total_price: number;
guest_info: {
full_name: string;
email: string;
phone: string;
};
services?: Array<{
service_id: number;
quantity: number;
}>;
promotion_code?: string;
referral_code?: string;
}
export interface Booking {
id: number;
booking_number: string;
user_id: number;
room_id: number;
check_in_date: string;
check_out_date: string;
guest_count: number;
total_price: number;
original_price?: number;
discount_amount?: number;
promotion_code?: string;
status:
| 'pending'
| 'confirmed'
| 'cancelled'
| 'checked_in'
| 'checked_out';
payment_method: 'cash' | 'stripe' | 'paypal';
payment_status:
| 'unpaid'
| 'paid'
| 'refunded';
deposit_paid?: boolean;
requires_deposit?: boolean;
notes?: string;
guest_info?: {
full_name: string;
email: string;
phone: string;
};
room?: {
id: number;
room_number: string;
floor: number;
status: string;
room_type: {
id: number;
name: string;
base_price: number;
capacity: number;
images?: string[];
};
};
user?: {
id: number;
name: string;
full_name: string;
email: string;
phone?: string;
phone_number?: string;
};
payments?: Payment[];
payment_balance?: {
total_paid: number;
total_price: number;
remaining_balance: number;
is_fully_paid: boolean;
payment_percentage: number;
};
createdAt: string;
updatedAt: string;
}
export interface Payment {
id: number;
booking_id: number;
amount: number;
payment_method: string;
payment_type: 'full' | 'deposit' | 'remaining';
deposit_percentage?: number;
payment_status: 'pending' | 'completed' | 'failed' | 'refunded';
transaction_id?: string;
payment_date?: string;
notes?: string;
created_at: string;
updated_at: string;
}
export interface BookingResponse {
success: boolean;
data: {
booking: Booking;
};
message?: string;
}
export interface BookingsResponse {
success: boolean;
status?: string;
data: {
bookings: Booking[];
pagination?: {
page: number;
limit: number;
total: number;
totalPages: number;
};
};
message?: string;
}
export interface CheckBookingResponse {
success: boolean;
data: {
booking: Booking;
};
message?: string;
}
export const createBooking = async (
bookingData: BookingData
): Promise<BookingResponse> => {
const response = await apiClient.post<{ status?: string; success?: boolean; data?: { booking?: Booking }; message?: string }>(
'/bookings',
bookingData
);
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
if (!data.data?.booking) {
throw new Error('Booking not found');
}
return {
success: data.status === 'success' || data.success === true,
data: { booking: data.data.booking },
message: data.message,
};
};
export const getMyBookings = async ():
Promise<BookingsResponse> => {
const response = await apiClient.get<{ status?: string; success?: boolean; data?: { bookings?: Booking[]; pagination?: { page: number; limit: number; total: number; totalPages: number } }; message?: string }>(
'/bookings/me'
);
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
return {
success: data.status === 'success' || data.success === true,
data: {
bookings: data.data?.bookings || [],
pagination: data.data?.pagination,
},
message: data.message,
};
};
export const getBookingById = async (
id: number
): Promise<BookingResponse> => {
const response = await apiClient.get<{ status?: string; success?: boolean; data?: { booking?: Booking }; message?: string }>(
`/bookings/${id}`
);
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
if (!data.data?.booking) {
throw new Error('Booking not found');
}
return {
success: data.status === 'success' || data.success === true,
data: { booking: data.data.booking },
message: data.message,
};
};
export const cancelBooking = async (
id: number
): Promise<BookingResponse> => {
const response = await apiClient.patch<{ status?: string; success?: boolean; data?: { booking?: Booking }; message?: string }>(
`/bookings/${id}/cancel`
);
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
if (!data.data?.booking) {
throw new Error('Booking not found');
}
return {
success: data.status === 'success' || data.success === true,
data: { booking: data.data.booking },
message: data.message,
};
};
export const checkBookingByNumber = async (
bookingNumber: string
): Promise<CheckBookingResponse> => {
const response =
await apiClient.get<{ status?: string; success?: boolean; data?: { booking?: Booking }; message?: string }>(
`/bookings/check/${bookingNumber}`
);
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
if (!data.data?.booking) {
throw new Error('Booking not found');
}
return {
success: data.status === 'success' || data.success === true,
data: { booking: data.data.booking },
message: data.message,
};
};
export const getAllBookings = async (
params?: {
status?: string;
search?: string;
page?: number;
limit?: number;
startDate?: string;
endDate?: string;
}
): Promise<BookingsResponse> => {
const response = await apiClient.get<{ status?: string; success?: boolean; data?: { bookings?: Booking[]; pagination?: { page: number; limit: number; total: number; totalPages: number } }; message?: string }>('/bookings', { params });
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
return {
success: data.status === 'success' || data.success === true,
data: {
bookings: data.data?.bookings || [],
pagination: data.data?.pagination,
},
message: data.message,
};
};
export const updateBooking = async (
id: number,
data: Partial<Booking>
): Promise<BookingResponse> => {
const response = await apiClient.put<{ status?: string; success?: boolean; data?: { booking?: Booking }; message?: string }>(`/bookings/${id}`, data);
const responseData = response.data;
// Handle both 'status: success' and 'success: true' formats
if (!responseData.data?.booking) {
throw new Error('Booking update failed');
}
return {
success: responseData.status === 'success' || responseData.success === true,
data: { booking: responseData.data.booking },
message: responseData.message,
};
};
export const checkRoomAvailability = async (
roomId: number,
checkInDate: string,
checkOutDate: string
): Promise<{ available: boolean; message?: string }> => {
try {
const response = await apiClient.get(
'/rooms/available',
{
params: {
roomId,
from: checkInDate,
to: checkOutDate,
},
}
);
if (response.data?.data?.available !== undefined) {
return {
available: response.data.data.available,
message: response.data.data.message,
};
}
return {
available: true,
message: response.data?.message || 'Room is available',
};
} catch (error: unknown) {
// Type guard for API errors
const isApiError = (e: unknown): e is { response?: { status?: number; data?: { message?: string; detail?: string } } } => {
return typeof e === 'object' && e !== null;
};
if (isApiError(error) && (error.response?.status === 409 || error.response?.status === 404)) {
return {
available: false,
message:
error.response.data?.message ||
error.response.data?.detail ||
'Room already booked during this time',
};
}
throw error;
}
};
export const notifyPayment = async (
bookingId: number,
file?: File
): Promise<{ success: boolean; message?: string }> => {
const formData = new FormData();
formData.append('bookingId', bookingId.toString());
if (file) {
formData.append('receipt', file);
}
const response = await apiClient.post(
'/notify/payment',
formData
);
return response.data;
};
export const generateQRCode = (
bookingNumber: string,
amount: number,
bankCode?: string,
accountNumber?: string,
accountName?: string
): string => {
// If bank details are not provided, return empty string (QR code won't be generated)
if (!bankCode || !accountNumber || !accountName) {
return '';
}
const transferContent = bookingNumber;
// Generate QR code URL (using generic QR code service, not Vietnam-specific)
// Note: This is a placeholder - you may want to use a different QR code service
// that supports international bank transfers
const qrUrl =
`https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=` +
encodeURIComponent(`Bank: ${bankCode}, Account: ${accountNumber}, Name: ${accountName}, Amount: ${amount}, Reference: ${transferContent}`);
return qrUrl;
};
export const adminCreateBooking = async (
bookingData: BookingData & { user_id: number; status?: string }
): Promise<BookingResponse> => {
const response = await apiClient.post<{ status?: string; success?: boolean; data?: { booking?: Booking }; message?: string }>(
'/bookings/admin-create',
bookingData
);
const data = response.data;
// Handle both 'status: success' and 'success: true' formats
if (!data.data?.booking) {
throw new Error('Booking not found');
}
return {
success: data.status === 'success' || data.success === true,
data: { booking: data.data.booking },
message: data.message,
};
};
export default {
createBooking,
getMyBookings,
getBookingById,
cancelBooking,
checkBookingByNumber,
checkRoomAvailability,
notifyPayment,
generateQRCode,
getAllBookings,
updateBooking,
adminCreateBooking,
};