diff --git a/client/src/pages/Booking.js b/client/src/pages/Booking.js index be896ba..ec05fe9 100644 --- a/client/src/pages/Booking.js +++ b/client/src/pages/Booking.js @@ -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 ? ( - - No rooms available - - ) : ( + {rooms.length > 0 ? ( rooms.map((r) => ( - - {(r.name || r.title || 'Room')} - {r.roomNumber ? ` (#${r.roomNumber})` : ''} - {(r.basePrice ?? r.price) != null ? ` - $${r.basePrice ?? r.price}/night` : ''} + + {r.label || r.name} )) + ) : ( + + No rooms available + )} @@ -248,7 +356,7 @@ const Booking = () => { rows={3} /> -