updates
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
LogIn,
|
||||
LogOut,
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
|
||||
import roomService, { Room } from '../../features/rooms/services/roomService';
|
||||
import serviceService, { Service } from '../../features/hotel_services/services/serviceService';
|
||||
import userService from '../../features/auth/services/userService';
|
||||
import { toast } from 'react-toastify';
|
||||
import Loading from '../../shared/components/Loading';
|
||||
import CurrencyIcon from '../../shared/components/CurrencyIcon';
|
||||
@@ -37,7 +38,7 @@ import { parseDateLocal } from '../../shared/utils/format';
|
||||
import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal';
|
||||
import { logger } from '../../shared/utils/logger';
|
||||
|
||||
type ReceptionTab = 'overview' | 'check-in' | 'check-out' | 'bookings' | 'rooms' | 'services';
|
||||
type ReceptionTab = 'overview' | 'check-in' | 'check-out' | 'walk-in' | 'bookings' | 'rooms' | 'services';
|
||||
|
||||
interface GuestInfo {
|
||||
name: string;
|
||||
@@ -77,6 +78,27 @@ const ReceptionDashboardPage: React.FC = () => {
|
||||
const [discount, setDiscount] = useState(0);
|
||||
const [showInvoice, setShowInvoice] = useState(false);
|
||||
|
||||
// Walk-in booking state
|
||||
const [walkInLoading, setWalkInLoading] = useState(false);
|
||||
const [walkInSearchingRooms, setWalkInSearchingRooms] = useState(false);
|
||||
const [walkInAvailableRooms, setWalkInAvailableRooms] = useState<Room[]>([]);
|
||||
const [walkInForm, setWalkInForm] = useState({
|
||||
guestName: '',
|
||||
guestEmail: '',
|
||||
guestPhone: '',
|
||||
guestIdNumber: '',
|
||||
selectedRoomId: '',
|
||||
checkInDate: new Date().toISOString().split('T')[0],
|
||||
checkOutDate: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||||
numGuests: 1,
|
||||
numChildren: 0,
|
||||
specialRequests: '',
|
||||
paymentMethod: 'cash' as 'cash' | 'stripe',
|
||||
paymentStatus: 'unpaid' as 'unpaid' | 'deposit' | 'full',
|
||||
});
|
||||
const [walkInTotalPrice, setWalkInTotalPrice] = useState(0);
|
||||
const [walkInSelectedRoom, setWalkInSelectedRoom] = useState<Room | null>(null);
|
||||
|
||||
|
||||
const [bookings, setBookings] = useState<Booking[]>([]);
|
||||
const [bookingsLoading, setBookingsLoading] = useState(true);
|
||||
@@ -342,6 +364,139 @@ const ReceptionDashboardPage: React.FC = () => {
|
||||
setShowInvoice(false);
|
||||
};
|
||||
|
||||
// Walk-in booking handlers
|
||||
const handleWalkInSearchRooms = async () => {
|
||||
if (!walkInForm.checkInDate || !walkInForm.checkOutDate) {
|
||||
toast.error('Please select check-in and check-out dates');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setWalkInSearchingRooms(true);
|
||||
const response = await roomService.searchAvailableRooms({
|
||||
from: walkInForm.checkInDate,
|
||||
to: walkInForm.checkOutDate,
|
||||
limit: 50,
|
||||
});
|
||||
setWalkInAvailableRooms(response.data.rooms || []);
|
||||
if (response.data.rooms && response.data.rooms.length === 0) {
|
||||
toast.warning('No available rooms for selected dates');
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error('Failed to search available rooms');
|
||||
logger.error('Error searching rooms', error);
|
||||
} finally {
|
||||
setWalkInSearchingRooms(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (walkInForm.checkInDate && walkInForm.checkOutDate && walkInForm.selectedRoomId) {
|
||||
const selectedRoom = walkInAvailableRooms.find(r => r.id.toString() === walkInForm.selectedRoomId);
|
||||
if (selectedRoom) {
|
||||
setWalkInSelectedRoom(selectedRoom);
|
||||
const checkIn = new Date(walkInForm.checkInDate);
|
||||
const checkOut = new Date(walkInForm.checkOutDate);
|
||||
const nights = Math.ceil((checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60 * 24));
|
||||
const roomPrice = selectedRoom.room_type?.base_price || selectedRoom.price || 0;
|
||||
setWalkInTotalPrice(roomPrice * nights);
|
||||
}
|
||||
}
|
||||
}, [walkInForm.checkInDate, walkInForm.checkOutDate, walkInForm.selectedRoomId, walkInAvailableRooms]);
|
||||
|
||||
const handleWalkInBooking = async () => {
|
||||
if (!walkInForm.guestName || !walkInForm.guestPhone || !walkInForm.selectedRoomId) {
|
||||
toast.error('Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!walkInSelectedRoom) {
|
||||
toast.error('Please select a room');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setWalkInLoading(true);
|
||||
|
||||
// First, create or find user
|
||||
let userId: number;
|
||||
try {
|
||||
const userResponse = await userService.getUsers({
|
||||
search: walkInForm.guestEmail || walkInForm.guestPhone,
|
||||
role: 'customer',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (userResponse.data?.users && userResponse.data.users.length > 0) {
|
||||
userId = userResponse.data.users[0].id;
|
||||
} else {
|
||||
// Create new user for walk-in
|
||||
const createUserResponse = await userService.createUser({
|
||||
full_name: walkInForm.guestName,
|
||||
email: walkInForm.guestEmail || `${walkInForm.guestPhone}@walkin.local`,
|
||||
phone_number: walkInForm.guestPhone,
|
||||
password: 'temp123', // Temporary password
|
||||
role: 'customer',
|
||||
});
|
||||
userId = createUserResponse.data.user.id;
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error('Failed to create/find guest profile');
|
||||
logger.error('Error creating user', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create booking
|
||||
const bookingData = {
|
||||
user_id: userId,
|
||||
room_id: parseInt(walkInForm.selectedRoomId),
|
||||
check_in_date: walkInForm.checkInDate,
|
||||
check_out_date: walkInForm.checkOutDate,
|
||||
guest_count: walkInForm.numGuests,
|
||||
total_price: walkInTotalPrice,
|
||||
status: 'confirmed',
|
||||
payment_method: walkInForm.paymentMethod,
|
||||
guest_info: {
|
||||
full_name: walkInForm.guestName,
|
||||
email: walkInForm.guestEmail || `${walkInForm.guestPhone}@walkin.local`,
|
||||
phone: walkInForm.guestPhone,
|
||||
},
|
||||
notes: walkInForm.specialRequests || undefined,
|
||||
};
|
||||
|
||||
await bookingService.adminCreateBooking(bookingData);
|
||||
|
||||
toast.success('Walk-in booking created successfully!');
|
||||
|
||||
// Reset form
|
||||
setWalkInForm({
|
||||
guestName: '',
|
||||
guestEmail: '',
|
||||
guestPhone: '',
|
||||
guestIdNumber: '',
|
||||
selectedRoomId: '',
|
||||
checkInDate: new Date().toISOString().split('T')[0],
|
||||
checkOutDate: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||||
numGuests: 1,
|
||||
numChildren: 0,
|
||||
specialRequests: '',
|
||||
paymentMethod: 'cash',
|
||||
paymentStatus: 'unpaid',
|
||||
});
|
||||
setWalkInAvailableRooms([]);
|
||||
setWalkInSelectedRoom(null);
|
||||
setWalkInTotalPrice(0);
|
||||
|
||||
// Optionally switch to check-in tab
|
||||
setActiveTab('check-in');
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data?.message || 'Failed to create walk-in booking');
|
||||
logger.error('Error creating walk-in booking', error);
|
||||
} finally {
|
||||
setWalkInLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchBookings = useCallback(async () => {
|
||||
try {
|
||||
@@ -986,6 +1141,7 @@ const ReceptionDashboardPage: React.FC = () => {
|
||||
{ id: 'overview' as ReceptionTab, label: 'Overview', icon: LogIn },
|
||||
{ id: 'check-in' as ReceptionTab, label: 'Check-in', icon: LogIn },
|
||||
{ id: 'check-out' as ReceptionTab, label: 'Check-out', icon: LogOut },
|
||||
{ id: 'walk-in' as ReceptionTab, label: 'Walk-in Booking', icon: Plus },
|
||||
{ id: 'bookings' as ReceptionTab, label: 'Bookings', icon: Calendar },
|
||||
{ id: 'rooms' as ReceptionTab, label: 'Rooms', icon: Hotel },
|
||||
{ id: 'services' as ReceptionTab, label: 'Services', icon: Wrench },
|
||||
@@ -1947,6 +2103,271 @@ const ReceptionDashboardPage: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{}
|
||||
{activeTab === 'walk-in' && (
|
||||
<div className="space-y-8">
|
||||
{walkInLoading && (
|
||||
<Loading fullScreen text="Creating walk-in booking..." />
|
||||
)}
|
||||
|
||||
{}
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 rounded-xl bg-gradient-to-br from-purple-500/10 to-pink-500/10 border border-purple-200/40">
|
||||
<Plus className="w-6 h-6 text-purple-600" />
|
||||
</div>
|
||||
<h2 className="text-3xl font-extrabold text-gray-900">Walk-in Booking</h2>
|
||||
</div>
|
||||
<p className="text-gray-600 text-base max-w-2xl leading-relaxed">
|
||||
Quick booking creation for walk-in guests
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{}
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||
<span className="text-purple-600 font-bold">1</span>
|
||||
</div>
|
||||
Guest Information
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Full Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={walkInForm.guestName}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, guestName: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
placeholder="Guest full name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Phone Number *</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={walkInForm.guestPhone}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, guestPhone: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
placeholder="+1234567890"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={walkInForm.guestEmail}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, guestEmail: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
placeholder="guest@example.com"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">ID Number</label>
|
||||
<input
|
||||
type="text"
|
||||
value={walkInForm.guestIdNumber}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, guestIdNumber: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
placeholder="ID/Passport number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||
<span className="text-purple-600 font-bold">2</span>
|
||||
</div>
|
||||
Booking Details
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Check-in Date *</label>
|
||||
<input
|
||||
type="date"
|
||||
value={walkInForm.checkInDate}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, checkInDate: e.target.value })}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Check-out Date *</label>
|
||||
<input
|
||||
type="date"
|
||||
value={walkInForm.checkOutDate}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, checkOutDate: e.target.value })}
|
||||
min={walkInForm.checkInDate || new Date().toISOString().split('T')[0]}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Number of Guests *</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={walkInForm.numGuests}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, numGuests: parseInt(e.target.value) || 1 })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Children</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={walkInForm.numChildren}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, numChildren: parseInt(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Special Requests</label>
|
||||
<textarea
|
||||
value={walkInForm.specialRequests}
|
||||
onChange={(e) => setWalkInForm({ ...walkInForm, specialRequests: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
placeholder="Any special requests or notes..."
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleWalkInSearchRooms}
|
||||
disabled={walkInSearchingRooms || !walkInForm.checkInDate || !walkInForm.checkOutDate}
|
||||
className="w-full px-6 py-3.5 bg-gradient-to-r from-purple-500 to-pink-600 text-white font-semibold rounded-xl shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
|
||||
>
|
||||
{walkInSearchingRooms ? 'Searching...' : 'Search Available Rooms'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{}
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||
<span className="text-purple-600 font-bold">3</span>
|
||||
</div>
|
||||
Select Room
|
||||
</h3>
|
||||
{walkInAvailableRooms.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Hotel className="w-16 h-16 mx-auto text-gray-300 mb-4" />
|
||||
<p className="text-gray-500">Search for available rooms to see options</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{walkInAvailableRooms.map((room) => (
|
||||
<div
|
||||
key={room.id}
|
||||
onClick={() => setWalkInForm({ ...walkInForm, selectedRoomId: room.id.toString() })}
|
||||
className={`p-4 rounded-xl border-2 cursor-pointer transition-all duration-200 ${
|
||||
walkInForm.selectedRoomId === room.id.toString()
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-purple-50/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-bold text-gray-900">Room {room.room_number}</div>
|
||||
<div className="text-sm text-gray-600">{room.room_type?.name}</div>
|
||||
<div className="text-sm font-semibold text-purple-600 mt-1">
|
||||
{formatCurrency(room.room_type?.base_price || room.price || 0)}/night
|
||||
</div>
|
||||
</div>
|
||||
{walkInForm.selectedRoomId === room.id.toString() && (
|
||||
<CheckCircle className="w-6 h-6 text-purple-600" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{walkInSelectedRoom && (
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||
<span className="text-purple-600 font-bold">4</span>
|
||||
</div>
|
||||
Payment & Summary
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-50 rounded-xl p-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Room:</span>
|
||||
<span className="font-semibold">Room {walkInSelectedRoom.room_number}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Nights:</span>
|
||||
<span className="font-semibold">
|
||||
{Math.ceil(
|
||||
(new Date(walkInForm.checkOutDate).getTime() -
|
||||
new Date(walkInForm.checkInDate).getTime()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-lg font-bold pt-2 border-t border-gray-200">
|
||||
<span>Total:</span>
|
||||
<span className="text-purple-600">{formatCurrency(walkInTotalPrice)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Payment Method</label>
|
||||
<select
|
||||
value={walkInForm.paymentMethod}
|
||||
onChange={(e) =>
|
||||
setWalkInForm({ ...walkInForm, paymentMethod: e.target.value as 'cash' | 'stripe' })
|
||||
}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
>
|
||||
<option value="cash">Cash</option>
|
||||
<option value="stripe">Card/Stripe</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Payment Status</label>
|
||||
<select
|
||||
value={walkInForm.paymentStatus}
|
||||
onChange={(e) =>
|
||||
setWalkInForm({ ...walkInForm, paymentStatus: e.target.value as 'unpaid' | 'deposit' | 'full' })
|
||||
}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700"
|
||||
>
|
||||
<option value="unpaid">Unpaid</option>
|
||||
<option value="deposit">Deposit</option>
|
||||
<option value="full">Full Payment</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleWalkInBooking}
|
||||
disabled={walkInLoading}
|
||||
className="w-full px-6 py-4 bg-gradient-to-r from-purple-500 to-pink-600 text-white font-bold rounded-xl shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
|
||||
>
|
||||
{walkInLoading ? 'Creating Booking...' : 'Create Walk-in Booking'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{}
|
||||
{activeTab === 'bookings' && (
|
||||
<div className="space-y-8">
|
||||
|
||||
Reference in New Issue
Block a user