import React, { useState, useEffect } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { Calendar, MapPin, Users, CreditCard, Eye, XCircle, AlertCircle, CheckCircle, Clock, DoorOpen, DoorClosed, Loader2, Search, Filter, } from 'lucide-react'; import { toast } from 'react-toastify'; import { getMyBookings, cancelBooking, type Booking, } from '../../services/api/bookingService'; import useAuthStore from '../../store/useAuthStore'; import Loading from '../../components/common/Loading'; import EmptyState from '../../components/common/EmptyState'; import { useFormatCurrency } from '../../hooks/useFormatCurrency'; const MyBookingsPage: React.FC = () => { const navigate = useNavigate(); const { isAuthenticated } = useAuthStore(); const { formatCurrency } = useFormatCurrency(); const [bookings, setBookings] = useState([]); const [filteredBookings, setFilteredBookings] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [cancellingId, setCancellingId] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); // Redirect if not authenticated useEffect(() => { if (!isAuthenticated) { toast.error('Please login to view your bookings'); navigate('/login', { state: { from: '/bookings' } }); } }, [isAuthenticated, navigate]); // Fetch bookings useEffect(() => { if (isAuthenticated) { fetchBookings(); } }, [isAuthenticated]); // Filter bookings useEffect(() => { let filtered = [...bookings]; // Filter by status if (statusFilter !== 'all') { filtered = filtered.filter( (b) => b.status === statusFilter ); } // Filter by search query if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filtered = filtered.filter( (b) => b.booking_number.toLowerCase().includes(query) || b.room?.room_type?.name .toLowerCase() .includes(query) || b.room?.room_number .toString() .includes(query) ); } setFilteredBookings(filtered); }, [bookings, statusFilter, searchQuery]); const fetchBookings = async () => { try { setLoading(true); setError(null); const response = await getMyBookings(); if ( response.success && response.data?.bookings ) { setBookings(response.data.bookings); } else { throw new Error( 'Unable to load bookings list' ); } } catch (err: any) { console.error('Error fetching bookings:', err); const message = err.response?.data?.message || 'Unable to load bookings list'; setError(message); toast.error(message); } finally { setLoading(false); } }; const handleCancelBooking = async ( bookingId: number, bookingNumber: string ) => { const confirmed = window.confirm( `Are you sure you want to cancel booking ${bookingNumber}?\n\n` + `⚠️ Note:\n` + `- You will be charged 20% of the order value\n` + `- The remaining 80% will be refunded\n` + `- Room status will be updated to "available"` ); if (!confirmed) return; try { setCancellingId(bookingId); const response = await cancelBooking(bookingId); if (response.success) { toast.success( `✅ Successfully cancelled booking ${bookingNumber}!` ); // Update local state setBookings((prev) => prev.map((b) => b.id === bookingId ? { ...b, status: 'cancelled' } : b ) ); } else { throw new Error( response.message || 'Unable to cancel booking' ); } } catch (err: any) { console.error('Error cancelling booking:', err); const message = err.response?.data?.message || 'Unable to cancel booking. Please try again.'; toast.error(message); } finally { setCancellingId(null); } }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', }); }; const formatPrice = (price: number) => formatCurrency(price); const getStatusConfig = (status: string) => { switch (status) { case 'pending': return { icon: Clock, color: 'bg-yellow-100 text-yellow-800', text: 'Pending confirmation', }; case 'confirmed': return { icon: CheckCircle, color: 'bg-green-100 text-green-800', text: 'Confirmed', }; case 'cancelled': return { icon: XCircle, color: 'bg-red-100 text-red-800', text: 'Cancelled', }; case 'checked_in': return { icon: DoorOpen, color: 'bg-blue-100 text-blue-800', text: 'Checked in', }; case 'checked_out': return { icon: DoorClosed, color: 'bg-gray-100 text-gray-800', text: 'Checked out', }; default: return { icon: AlertCircle, color: 'bg-gray-100 text-gray-800', text: status, }; } }; const canCancelBooking = (booking: Booking) => { // Only allow cancellation of pending bookings return booking.status === 'pending'; }; if (loading) { return ; } return (
{/* Header */}

My Bookings

Manage and track your bookings

{/* Filters */}
{/* Search */}
setSearchQuery(e.target.value) } className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" />
{/* Status Filter */}
{/* Results count */}
Showing {filteredBookings.length} / {bookings.length} bookings
{/* Error State */} {error && (

{error}

)} {/* Bookings List */} {filteredBookings.length === 0 ? ( {!searchQuery && statusFilter === 'all' ? ( View room list ) : ( )} ) : (
{filteredBookings.map((booking) => { const statusConfig = getStatusConfig( booking.status ); const StatusIcon = statusConfig.icon; const room = booking.room; const roomType = room?.room_type; return (
{/* Room Image */} {((room?.images && room.images.length > 0) ? room.images[0] : roomType?.images?.[0]) && (
0) ? room.images[0] : (roomType?.images?.[0] || '')} alt={roomType.name} className="w-full h-48 lg:h-full object-cover rounded-lg" />
)} {/* Booking Info */}
{/* Header */}

{roomType?.name || 'N/A'}

Room {room?.room_number} - Floor {room?.floor}

{/* Status Badge */}
{statusConfig.text}
{/* Details Grid */}
{/* Booking Number */}

Booking number

{booking.booking_number}

{/* Check-in */}

Check-in date

{formatDate( booking.check_in_date )}

{/* Check-out */}

Check-out date

{formatDate( booking.check_out_date )}

{/* Guest Count */}

Guests

{booking.guest_count} guest(s)

{/* Payment Method */}

Payment

{booking.payment_method === 'cash' ? 'On-site' : 'Bank transfer'}

{/* Total Price */}

Total price

{formatPrice(booking.total_price)}

{/* Actions */}
{/* View Details */} View details {/* Cancel Booking */} {canCancelBooking(booking) && ( )}
); })}
)}
); }; export default MyBookingsPage;