390 lines
10 KiB
TypeScript
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,
|
|
};
|