import React, { useEffect, useState } from 'react'; import { Search, Eye, XCircle, CheckCircle, Loader2, Users, Plus } from 'lucide-react'; import { groupBookingService, GroupBooking } from '../../services/api'; import { toast } from 'react-toastify'; import Loading from '../../components/common/Loading'; import Pagination from '../../components/common/Pagination'; import { useFormatCurrency } from '../../hooks/useFormatCurrency'; import { formatDate } from '../../utils/format'; import CreateGroupBookingModal from '../../components/shared/CreateGroupBookingModal'; const GroupBookingManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const [groupBookings, setGroupBookings] = useState([]); const [loading, setLoading] = useState(true); const [selectedBooking, setSelectedBooking] = useState(null); const [showDetailModal, setShowDetailModal] = useState(false); const [confirmingBookingId, setConfirmingBookingId] = useState(null); const [cancellingBookingId, setCancellingBookingId] = useState(null); const [showCreateModal, setShowCreateModal] = useState(false); const [filters, setFilters] = useState({ search: '', status: '', }); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const itemsPerPage = 10; useEffect(() => { setCurrentPage(1); }, [filters]); useEffect(() => { fetchGroupBookings(); }, [filters, currentPage]); const fetchGroupBookings = async () => { try { setLoading(true); const response = await groupBookingService.getGroupBookings({ ...filters, page: currentPage, limit: itemsPerPage, }); setGroupBookings(response.data.group_bookings); if (response.data.pagination) { setTotalPages(response.data.pagination.totalPages); } } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to load group bookings'); } finally { setLoading(false); } }; const handleConfirmBooking = async (id: number) => { if (!window.confirm('Are you sure you want to confirm this group booking?')) return; try { setConfirmingBookingId(id); await groupBookingService.confirmGroupBooking(id); toast.success('Group booking confirmed successfully'); await fetchGroupBookings(); if (selectedBooking?.id === id) { const updated = await groupBookingService.getGroupBooking(id); setSelectedBooking(updated.data.group_booking); } } catch (error: any) { toast.error(error.response?.data?.detail || error.response?.data?.message || 'Unable to confirm booking'); } finally { setConfirmingBookingId(null); } }; const handleCancelBooking = async (id: number) => { if (!window.confirm('Are you sure you want to cancel this group booking?')) return; try { setCancellingBookingId(id); await groupBookingService.cancelGroupBooking(id, 'Cancelled by admin'); toast.success('Group booking cancelled successfully'); await fetchGroupBookings(); if (selectedBooking?.id === id) { setShowDetailModal(false); } } catch (error: any) { toast.error(error.response?.data?.detail || error.response?.data?.message || 'Unable to cancel booking'); } finally { setCancellingBookingId(null); } }; const handleViewDetails = async (booking: GroupBooking) => { try { const response = await groupBookingService.getGroupBooking(booking.id); setSelectedBooking(response.data.group_booking); setShowDetailModal(true); } catch (error: any) { toast.error('Unable to load booking details'); } }; const getStatusBadge = (status: string) => { const badges: Record = { draft: { bg: 'bg-gradient-to-r from-gray-50 to-slate-50', text: 'text-gray-700', label: 'Draft', border: 'border-gray-200' }, pending: { bg: 'bg-gradient-to-r from-amber-50 to-yellow-50', text: 'text-amber-800', label: 'Pending', border: 'border-amber-200' }, confirmed: { bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', text: 'text-blue-800', label: 'Confirmed', border: 'border-blue-200' }, partially_confirmed: { bg: 'bg-gradient-to-r from-purple-50 to-violet-50', text: 'text-purple-800', label: 'Partially Confirmed', border: 'border-purple-200' }, checked_in: { bg: 'bg-gradient-to-r from-emerald-50 to-green-50', text: 'text-emerald-800', label: 'Checked In', border: 'border-emerald-200' }, checked_out: { bg: 'bg-gradient-to-r from-slate-50 to-gray-50', text: 'text-slate-700', label: 'Checked Out', border: 'border-slate-200' }, cancelled: { bg: 'bg-gradient-to-r from-rose-50 to-red-50', text: 'text-rose-800', label: 'Cancelled', border: 'border-rose-200' }, }; const badge = badges[status] || badges.draft; return ( {badge.label} ); }; if (loading && groupBookings.length === 0) { return ; } return (

Group Booking Management

Manage group bookings, room blocks, and member assignments

{/* Filters */}
setFilters({ ...filters, search: e.target.value })} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Group Bookings Table */}
{groupBookings.length === 0 ? ( ) : ( groupBookings.map((booking) => ( )) )}
Booking Number Group Name Coordinator Dates Rooms / Guests Total Price Status Actions
No group bookings found
{booking.group_booking_number}
{booking.group_name || 'N/A'}
{booking.group_type && (
{booking.group_type}
)}
{booking.coordinator.name}
{booking.coordinator.email}
{formatDate(booking.check_in_date, 'short')}
to {formatDate(booking.check_out_date, 'short')}
{booking.total_rooms} rooms
{booking.total_guests} guests
{formatCurrency(booking.total_price)}
{booking.discount_amount > 0 && (
-{formatCurrency(booking.discount_amount)} discount
)}
{getStatusBadge(booking.status)}
{booking.status === 'draft' || booking.status === 'pending' ? ( ) : null} {booking.status !== 'cancelled' && booking.status !== 'checked_out' ? ( ) : null}
{totalPages > 1 && (
)}
{/* Detail Modal */} {showDetailModal && selectedBooking && (

{selectedBooking.group_booking_number}

{selectedBooking.group_name || 'No group name'}

{/* Booking Info */}

Coordinator

{selectedBooking.coordinator.name}

{selectedBooking.coordinator.email}

{selectedBooking.coordinator.phone && (

{selectedBooking.coordinator.phone}

)}

Status

{getStatusBadge(selectedBooking.status)}

Check-in

{formatDate(selectedBooking.check_in_date, 'short')}

Check-out

{formatDate(selectedBooking.check_out_date, 'short')}

{/* Room Blocks */} {selectedBooking.room_blocks && selectedBooking.room_blocks.length > 0 && (

Room Blocks

{selectedBooking.room_blocks.map((block) => (

{block.room_type?.name || `Room Type ${block.room_type_id}`}

{block.rooms_blocked} rooms blocked • {block.rooms_confirmed} confirmed • {block.rooms_available} available

{formatCurrency(block.rate_per_room)}/room

Total: {formatCurrency(block.total_block_price)}

))}
)} {/* Members */} {selectedBooking.members && selectedBooking.members.length > 0 && (

Members ({selectedBooking.members.length})

{selectedBooking.members.map((member) => (

{member.full_name}

{member.email &&

{member.email}

} {member.phone &&

{member.phone}

} {member.assigned_room_id && (

Room #{member.assigned_room_id}

)}
{member.individual_amount && (

Amount: {formatCurrency(member.individual_amount)}

Paid: {formatCurrency(member.individual_paid)}

)}
))}
)} {/* Pricing */}

Pricing Summary

Original Total: {formatCurrency(selectedBooking.original_total_price)}
{selectedBooking.discount_amount > 0 && (
Discount ({selectedBooking.group_discount_percentage}%): -{formatCurrency(selectedBooking.discount_amount)}
)}
Total Price: {formatCurrency(selectedBooking.total_price)}
Amount Paid: {formatCurrency(selectedBooking.amount_paid)}
Balance Due: 0 ? 'text-red-600' : 'text-green-600'}> {formatCurrency(selectedBooking.balance_due)}
{/* Payments */} {selectedBooking.payments && selectedBooking.payments.length > 0 && (

Payments

{selectedBooking.payments.map((payment) => (

{formatCurrency(payment.amount)} - {payment.payment_method}

{payment.payment_type} • {payment.payment_status}

{payment.payment_date && (

{new Date(payment.payment_date).toLocaleDateString()}

)}
))}
)}
)} {/* Create Group Booking Modal */} setShowCreateModal(false)} onSuccess={() => { setShowCreateModal(false); fetchGroupBookings(); }} />
); }; export default GroupBookingManagementPage;