fix booking page
This commit is contained in:
@@ -33,26 +33,128 @@ const Booking = () => {
|
|||||||
specialRequests: ''
|
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(() => {
|
useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
|
|
||||||
const loadRooms = async () => {
|
const loadRooms = async () => {
|
||||||
try {
|
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 =
|
if (normalizedRooms.length > 0) {
|
||||||
res?.data?.data?.rooms ||
|
if (mounted) setRooms(normalizedRooms);
|
||||||
res?.data?.data ||
|
return;
|
||||||
res?.data?.rooms ||
|
}
|
||||||
[];
|
|
||||||
|
|
||||||
const fetched = (Array.isArray(rawRooms) ? rawRooms : []).filter((r) => {
|
// 2) Fallback to room categories (the types shown on public rooms page)
|
||||||
const status = String(r?.status || '').toLowerCase();
|
const categoriesRes = await api.get('/api/room-categories');
|
||||||
return !status || status === 'active';
|
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) {
|
} catch (e) {
|
||||||
|
console.error('Failed to load rooms/categories:', e);
|
||||||
if (mounted) setMessage({ type: 'error', text: 'Failed to load rooms' });
|
if (mounted) setMessage({ type: 'error', text: 'Failed to load rooms' });
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setLoadingRooms(false);
|
if (mounted) setLoadingRooms(false);
|
||||||
@@ -65,11 +167,10 @@ const Booking = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (key) => (e) => {
|
const handleChange = (key) => (e) => {
|
||||||
setForm(prev => ({ ...prev, [key]: e.target.value }));
|
setForm((prev) => ({ ...prev, [key]: e.target.value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateBeforeSubmit = () => {
|
const validateBeforeSubmit = () => {
|
||||||
// check dates
|
|
||||||
if (form.checkInDate && form.checkOutDate) {
|
if (form.checkInDate && form.checkOutDate) {
|
||||||
const inD = new Date(form.checkInDate);
|
const inD = new Date(form.checkInDate);
|
||||||
const outD = new Date(form.checkOutDate);
|
const outD = new Date(form.checkOutDate);
|
||||||
@@ -81,10 +182,12 @@ const Booking = () => {
|
|||||||
|
|
||||||
const adults = Number(form.adults);
|
const adults = Number(form.adults);
|
||||||
const children = Number(form.children || 0);
|
const children = Number(form.children || 0);
|
||||||
|
|
||||||
if (!Number.isFinite(adults) || adults < 1) {
|
if (!Number.isFinite(adults) || adults < 1) {
|
||||||
setMessage({ type: 'error', text: 'Adults must be at least 1' });
|
setMessage({ type: 'error', text: 'Adults must be at least 1' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Number.isFinite(children) || children < 0) {
|
if (!Number.isFinite(children) || children < 0) {
|
||||||
setMessage({ type: 'error', text: 'Children cannot be negative' });
|
setMessage({ type: 'error', text: 'Children cannot be negative' });
|
||||||
return false;
|
return false;
|
||||||
@@ -102,6 +205,9 @@ const Booking = () => {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const selectedOption = rooms.find((r) => r._id === form.roomId);
|
||||||
|
|
||||||
|
// Keep original payload structure, but add category fallback fields safely
|
||||||
const payload = {
|
const payload = {
|
||||||
guestInfo: {
|
guestInfo: {
|
||||||
firstName: form.firstName.trim(),
|
firstName: form.firstName.trim(),
|
||||||
@@ -109,7 +215,10 @@ const Booking = () => {
|
|||||||
email: form.email.trim(),
|
email: form.email.trim(),
|
||||||
phone: form.phone.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,
|
checkInDate: form.checkInDate,
|
||||||
checkOutDate: form.checkOutDate,
|
checkOutDate: form.checkOutDate,
|
||||||
numberOfGuests: {
|
numberOfGuests: {
|
||||||
@@ -119,13 +228,14 @@ const Booking = () => {
|
|||||||
specialRequests: form.specialRequests
|
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 res = await api.post('/api/bookings/request', payload);
|
||||||
|
|
||||||
const booking = res?.data?.data?.booking || null;
|
const booking = res?.data?.data?.booking || null;
|
||||||
const bookingNumber = booking?.bookingNumber || res?.data?.data?.bookingNumber || 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 = {
|
const requestForConfirmation = {
|
||||||
fullName: `${form.firstName} ${form.lastName}`.trim(),
|
fullName: `${form.firstName} ${form.lastName}`.trim(),
|
||||||
phone: form.phone,
|
phone: form.phone,
|
||||||
@@ -134,15 +244,15 @@ const Booking = () => {
|
|||||||
checkOutDate: form.checkOutDate,
|
checkOutDate: form.checkOutDate,
|
||||||
adults: Number(form.adults),
|
adults: Number(form.adults),
|
||||||
children: Number(form.children || 0),
|
children: Number(form.children || 0),
|
||||||
roomCategory: selectedRoom?.name || selectedRoom?.title || '',
|
roomCategory: selectedOption?.name || '',
|
||||||
message: form.specialRequests || '',
|
message: form.specialRequests || '',
|
||||||
bookingNumber: bookingNumber || '',
|
bookingNumber: bookingNumber || '',
|
||||||
autoOpenWhatsApp: true
|
autoOpenWhatsApp: true
|
||||||
};
|
};
|
||||||
|
|
||||||
navigate('/booking/confirmation', { state: { request: requestForConfirmation } });
|
navigate('/booking/confirmation', { state: { request: requestForConfirmation } });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Booking submit error:', error);
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: error.response?.data?.message || 'Failed to submit booking request'
|
text: error.response?.data?.message || 'Failed to submit booking request'
|
||||||
@@ -190,18 +300,16 @@ const Booking = () => {
|
|||||||
onChange={handleChange('roomId')}
|
onChange={handleChange('roomId')}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
{rooms.length === 0 ? (
|
{rooms.length > 0 ? (
|
||||||
<MenuItem disabled value="">
|
|
||||||
No rooms available
|
|
||||||
</MenuItem>
|
|
||||||
) : (
|
|
||||||
rooms.map((r) => (
|
rooms.map((r) => (
|
||||||
<MenuItem key={r._id || r.id} value={r._id || r.id}>
|
<MenuItem key={r._id} value={r._id}>
|
||||||
{(r.name || r.title || 'Room')}
|
{r.label || r.name}
|
||||||
{r.roomNumber ? ` (#${r.roomNumber})` : ''}
|
|
||||||
{(r.basePrice ?? r.price) != null ? ` - $${r.basePrice ?? r.price}/night` : ''}
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))
|
))
|
||||||
|
) : (
|
||||||
|
<MenuItem value="" disabled>
|
||||||
|
No rooms available
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</TextField>
|
</TextField>
|
||||||
|
|
||||||
@@ -248,7 +356,7 @@ const Booking = () => {
|
|||||||
rows={3}
|
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'}
|
{submitting ? 'Submitting...' : 'Submit Booking Request'}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user