fix booking page
This commit is contained in:
@@ -15,7 +15,7 @@ import api from '../utils/api';
|
||||
const Booking = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [rooms, setRooms] = useState([]);
|
||||
const [rooms, setRooms] = useState([]);
|
||||
const [loadingRooms, setLoadingRooms] = useState(true);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [message, setMessage] = useState(null);
|
||||
@@ -33,26 +33,128 @@ const Booking = () => {
|
||||
specialRequests: ''
|
||||
});
|
||||
|
||||
const extractArray = (res, keys = []) => {
|
||||
const d = res?.data;
|
||||
const dd = d?.data;
|
||||
|
||||
// direct arrays
|
||||
if (Array.isArray(d)) return d;
|
||||
if (Array.isArray(dd)) return dd;
|
||||
|
||||
// common shapes
|
||||
for (const k of keys) {
|
||||
if (Array.isArray(dd?.[k])) return dd[k];
|
||||
if (Array.isArray(d?.[k])) return d[k];
|
||||
}
|
||||
|
||||
// fallback scan first array found
|
||||
if (dd && typeof dd === 'object') {
|
||||
for (const v of Object.values(dd)) {
|
||||
if (Array.isArray(v)) return v;
|
||||
}
|
||||
}
|
||||
if (d && typeof d === 'object') {
|
||||
for (const v of Object.values(d)) {
|
||||
if (Array.isArray(v)) return v;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeRoomOptions = (items = []) => {
|
||||
return items
|
||||
.filter(Boolean)
|
||||
.map((r) => {
|
||||
const id = r?._id || r?.id || '';
|
||||
const name =
|
||||
r?.name ||
|
||||
r?.title ||
|
||||
r?.roomName ||
|
||||
r?.roomCategory?.name ||
|
||||
'Room';
|
||||
|
||||
const roomNumber = r?.roomNumber || r?.number || '';
|
||||
const price =
|
||||
r?.basePrice ??
|
||||
r?.price ??
|
||||
r?.pricing?.basePrice ??
|
||||
r?.roomCategory?.basePrice;
|
||||
|
||||
const statusRaw = String(r?.status || '').toLowerCase();
|
||||
const isInactive =
|
||||
statusRaw === 'inactive' ||
|
||||
statusRaw === 'disabled' ||
|
||||
statusRaw === 'draft';
|
||||
|
||||
return {
|
||||
_id: id,
|
||||
name,
|
||||
roomNumber,
|
||||
basePrice: price,
|
||||
status: r?.status,
|
||||
kind: 'room',
|
||||
label:
|
||||
roomNumber && price != null
|
||||
? `${name} (#${roomNumber}) - $${price}/night`
|
||||
: roomNumber
|
||||
? `${name} (#${roomNumber})`
|
||||
: price != null
|
||||
? `${name} - $${price}/night`
|
||||
: name,
|
||||
_isInactive: isInactive
|
||||
};
|
||||
})
|
||||
.filter((x) => x._id)
|
||||
.filter((x) => !x._isInactive);
|
||||
};
|
||||
|
||||
const normalizeCategoryOptions = (items = []) => {
|
||||
return items
|
||||
.filter(Boolean)
|
||||
.map((c) => {
|
||||
const id = c?._id || c?.id || '';
|
||||
const name = c?.name || c?.title || c?.categoryName || 'Room Type';
|
||||
const price = c?.basePrice ?? c?.price ?? c?.startingPrice;
|
||||
return {
|
||||
_id: id,
|
||||
name,
|
||||
basePrice: price,
|
||||
kind: 'category',
|
||||
label: price != null ? `${name} - from $${price}/night` : name
|
||||
};
|
||||
})
|
||||
.filter((x) => x._id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
const loadRooms = async () => {
|
||||
try {
|
||||
const res = await api.get('/api/rooms?limit=100');
|
||||
// 1) Try actual rooms first
|
||||
const roomsRes = await api.get('/api/rooms?limit=100');
|
||||
const rawRooms = extractArray(roomsRes, ['rooms', 'items', 'data']);
|
||||
const normalizedRooms = normalizeRoomOptions(rawRooms);
|
||||
|
||||
const rawRooms =
|
||||
res?.data?.data?.rooms ||
|
||||
res?.data?.data ||
|
||||
res?.data?.rooms ||
|
||||
[];
|
||||
if (normalizedRooms.length > 0) {
|
||||
if (mounted) setRooms(normalizedRooms);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetched = (Array.isArray(rawRooms) ? rawRooms : []).filter((r) => {
|
||||
const status = String(r?.status || '').toLowerCase();
|
||||
return !status || status === 'active';
|
||||
});
|
||||
// 2) Fallback to room categories (the types shown on public rooms page)
|
||||
const categoriesRes = await api.get('/api/room-categories');
|
||||
const rawCategories = extractArray(categoriesRes, ['categories', 'roomCategories', 'items', 'data']);
|
||||
const normalizedCategories = normalizeCategoryOptions(rawCategories);
|
||||
|
||||
if (mounted) setRooms(fetched);
|
||||
if (mounted) {
|
||||
setRooms(normalizedCategories);
|
||||
if (normalizedCategories.length === 0) {
|
||||
setMessage({ type: 'warning', text: 'No rooms available' });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load rooms/categories:', e);
|
||||
if (mounted) setMessage({ type: 'error', text: 'Failed to load rooms' });
|
||||
} finally {
|
||||
if (mounted) setLoadingRooms(false);
|
||||
@@ -65,11 +167,10 @@ const Booking = () => {
|
||||
}, []);
|
||||
|
||||
const handleChange = (key) => (e) => {
|
||||
setForm(prev => ({ ...prev, [key]: e.target.value }));
|
||||
setForm((prev) => ({ ...prev, [key]: e.target.value }));
|
||||
};
|
||||
|
||||
const validateBeforeSubmit = () => {
|
||||
// check dates
|
||||
if (form.checkInDate && form.checkOutDate) {
|
||||
const inD = new Date(form.checkInDate);
|
||||
const outD = new Date(form.checkOutDate);
|
||||
@@ -81,10 +182,12 @@ const Booking = () => {
|
||||
|
||||
const adults = Number(form.adults);
|
||||
const children = Number(form.children || 0);
|
||||
|
||||
if (!Number.isFinite(adults) || adults < 1) {
|
||||
setMessage({ type: 'error', text: 'Adults must be at least 1' });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Number.isFinite(children) || children < 0) {
|
||||
setMessage({ type: 'error', text: 'Children cannot be negative' });
|
||||
return false;
|
||||
@@ -102,6 +205,9 @@ const Booking = () => {
|
||||
setSubmitting(true);
|
||||
|
||||
try {
|
||||
const selectedOption = rooms.find((r) => r._id === form.roomId);
|
||||
|
||||
// Keep original payload structure, but add category fallback fields safely
|
||||
const payload = {
|
||||
guestInfo: {
|
||||
firstName: form.firstName.trim(),
|
||||
@@ -109,7 +215,10 @@ const Booking = () => {
|
||||
email: form.email.trim(),
|
||||
phone: form.phone.trim()
|
||||
},
|
||||
roomId: form.roomId,
|
||||
roomId: selectedOption?.kind === 'room' ? form.roomId : undefined,
|
||||
roomCategoryId: selectedOption?.kind === 'category' ? form.roomId : undefined,
|
||||
roomCategory: selectedOption?.name || undefined,
|
||||
requestedRoomType: selectedOption?.name || undefined,
|
||||
checkInDate: form.checkInDate,
|
||||
checkOutDate: form.checkOutDate,
|
||||
numberOfGuests: {
|
||||
@@ -119,13 +228,14 @@ const Booking = () => {
|
||||
specialRequests: form.specialRequests
|
||||
};
|
||||
|
||||
// remove undefined keys
|
||||
Object.keys(payload).forEach((k) => payload[k] === undefined && delete payload[k]);
|
||||
|
||||
const res = await api.post('/api/bookings/request', payload);
|
||||
|
||||
const booking = res?.data?.data?.booking || null;
|
||||
const bookingNumber = booking?.bookingNumber || res?.data?.data?.bookingNumber || null;
|
||||
|
||||
const selectedRoom = rooms.find(r => String(r._id || r.id) === String(form.roomId));
|
||||
|
||||
const requestForConfirmation = {
|
||||
fullName: `${form.firstName} ${form.lastName}`.trim(),
|
||||
phone: form.phone,
|
||||
@@ -134,15 +244,15 @@ const Booking = () => {
|
||||
checkOutDate: form.checkOutDate,
|
||||
adults: Number(form.adults),
|
||||
children: Number(form.children || 0),
|
||||
roomCategory: selectedRoom?.name || selectedRoom?.title || '',
|
||||
roomCategory: selectedOption?.name || '',
|
||||
message: form.specialRequests || '',
|
||||
bookingNumber: bookingNumber || '',
|
||||
autoOpenWhatsApp: true
|
||||
autoOpenWhatsApp: true
|
||||
};
|
||||
|
||||
navigate('/booking/confirmation', { state: { request: requestForConfirmation } });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Booking submit error:', error);
|
||||
setMessage({
|
||||
type: 'error',
|
||||
text: error.response?.data?.message || 'Failed to submit booking request'
|
||||
@@ -190,18 +300,16 @@ const Booking = () => {
|
||||
onChange={handleChange('roomId')}
|
||||
required
|
||||
>
|
||||
{rooms.length === 0 ? (
|
||||
<MenuItem disabled value="">
|
||||
No rooms available
|
||||
</MenuItem>
|
||||
) : (
|
||||
{rooms.length > 0 ? (
|
||||
rooms.map((r) => (
|
||||
<MenuItem key={r._id || r.id} value={r._id || r.id}>
|
||||
{(r.name || r.title || 'Room')}
|
||||
{r.roomNumber ? ` (#${r.roomNumber})` : ''}
|
||||
{(r.basePrice ?? r.price) != null ? ` - $${r.basePrice ?? r.price}/night` : ''}
|
||||
<MenuItem key={r._id} value={r._id}>
|
||||
{r.label || r.name}
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem value="" disabled>
|
||||
No rooms available
|
||||
</MenuItem>
|
||||
)}
|
||||
</TextField>
|
||||
|
||||
@@ -248,7 +356,7 @@ const Booking = () => {
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
<Button type="submit" variant="contained" size="large" disabled={submitting}>
|
||||
<Button type="submit" variant="contained" size="large" disabled={submitting || rooms.length === 0}>
|
||||
{submitting ? 'Submitting...' : 'Submit Booking Request'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user