Files
Hotel-Booking/client/src/pages/customer/SearchResultsPage.tsx
Iliyan Angelov 824eec6190 Hotel Booking
2025-11-16 14:19:13 +02:00

358 lines
10 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
useSearchParams,
useNavigate,
Link
} from 'react-router-dom';
import {
Search,
Calendar,
AlertCircle,
ArrowLeft,
Home,
Users,
} from 'lucide-react';
import {
RoomCard,
RoomCardSkeleton,
Pagination,
} from '../../components/rooms';
import { searchAvailableRooms } from
'../../services/api/roomService';
import type { Room } from '../../services/api/roomService';
import { toast } from 'react-toastify';
const SearchResultsPage: React.FC = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [rooms, setRooms] = useState<Room[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [pagination, setPagination] = useState({
total: 0,
page: 1,
limit: 12,
totalPages: 1,
});
// Get search params
const from = searchParams.get('from') || '';
const to = searchParams.get('to') || '';
const type = searchParams.get('type') || '';
const capacityParam = searchParams.get('capacity') || '';
const capacity = capacityParam ? Number(capacityParam) : undefined;
const pageParam = searchParams.get('page') || '';
const page = pageParam ? Number(pageParam) : 1;
useEffect(() => {
// Validate required params
if (!from || !to) {
toast.error(
'Missing search information. ' +
'Please select check-in and check-out dates.'
);
navigate('/');
return;
}
fetchAvailableRooms();
}, [from, to, type, capacity, page]);
const fetchAvailableRooms = async () => {
try {
setLoading(true);
setError(null);
const response = await searchAvailableRooms({
from,
to,
type: type || undefined,
capacity: capacity || undefined,
page,
limit: 12,
});
if (
response.success ||
response.status === 'success'
) {
setRooms(response.data.rooms || []);
if (response.data.pagination) {
setPagination(response.data.pagination);
} else {
// Fallback compute
const total = response.data.rooms
? response.data.rooms.length
: 0;
const limit = 12;
setPagination({
total,
page,
limit,
totalPages: Math.max(1, Math.ceil(total / limit)),
});
}
} else {
throw new Error('Unable to search rooms');
}
} catch (err: any) {
console.error('Error searching rooms:', err);
const message =
err.response?.data?.message ||
'Unable to search available rooms';
setError(message);
toast.error(message);
} finally {
setLoading(false);
}
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
});
};
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 py-8">
{/* Back Button */}
<Link
to="/"
className="inline-flex items-center gap-2 bg-indigo-600
text-white px-3 py-2 rounded-md hover:bg-indigo-700
disabled:bg-gray-400 mb-6 transition-colors"
>
<ArrowLeft className="w-5 h-5" />
<span>Back to home</span>
</Link>
{/* Search Info Header */}
<div
className="bg-white rounded-lg shadow-sm
p-6 mb-8"
>
<div className="flex items-start justify-between
flex-wrap gap-4"
>
<div>
<h1
className="text-3xl font-bold
text-gray-900 mb-4"
>
Search Results
</h1>
<div
className="flex flex-wrap items-center
gap-4 text-gray-700"
>
<div
className="flex items-center gap-2"
>
<Calendar className="w-5 h-5
text-indigo-600"
/>
<span>
<strong>Check-in:</strong>{' '}
{formatDate(from)}
</span>
</div>
<div
className="flex items-center gap-2"
>
<Calendar className="w-5 h-5
text-indigo-600"
/>
<span>
<strong>Check-out:</strong>{' '}
{formatDate(to)}
</span>
</div>
{type && (
<div
className="flex items-center gap-2"
>
<Home className="w-5 h-5
text-indigo-600"
/>
<span>
<strong>Room Type:</strong>{' '}
{type}
</span>
</div>
)}
{capacity && (
<div className="flex items-center gap-2">
<Users className="w-5 h-5 text-indigo-600" />
<span>
<strong>Guests:</strong>{' '}
{capacity}
</span>
</div>
)}
</div>
</div>
<button
onClick={() => navigate('/')}
className="px-4 py-2 border border-gray-300
bg-indigo-600 text-white rounded-lg
hover:bg-indigo-700 disabled:bg-gray-400
transition-colors flex items-center gap-2"
>
<Search className="w-4 h-4" />
New Search
</button>
</div>
</div>
{/* Loading State */}
{loading && (
<div>
<p
className="text-gray-600 mb-6
text-center animate-pulse"
>
Searching for available rooms...
</p>
<div
className="grid grid-cols-1 md:grid-cols-2
lg:grid-cols-3 gap-6"
>
{[...Array(6)].map((_, index) => (
<RoomCardSkeleton key={index} />
))}
</div>
</div>
)}
{/* Error State */}
{error && !loading && (
<div
className="bg-red-50 border border-red-200
rounded-lg p-8 text-center"
>
<AlertCircle
className="w-12 h-12 text-red-500
mx-auto mb-3"
/>
<p className="text-red-700 font-medium mb-4">
{error}
</p>
<button
onClick={fetchAvailableRooms}
className="px-6 py-2 bg-red-600
text-white rounded-lg
hover:bg-red-700 transition-colors"
>
Try Again
</button>
</div>
)}
{/* Results */}
{!loading && !error && (
<>
{rooms.length > 0 ? (
<>
<div
className="flex items-center
justify-between mb-6"
>
</div>
<div
className="grid grid-cols-1
md:grid-cols-2 lg:grid-cols-3
gap-6"
>
{rooms.map((room) => (
<RoomCard key={room.id} room={room} />
))}
</div>
<Pagination
currentPage={pagination.page}
totalPages={pagination.totalPages}
/>
</>
) : (
// Empty State
<div
className="bg-white rounded-lg
shadow-sm p-12 text-center"
>
<div
className="w-24 h-24 bg-gray-100
rounded-full flex items-center
justify-center mx-auto mb-6"
>
<Search
className="w-12 h-12 text-gray-400"
/>
</div>
<h3
className="text-2xl font-bold
text-gray-900 mb-3"
>
No matching rooms found
</h3>
<p
className="text-gray-600 mb-6
max-w-md mx-auto"
>
Sorry, there are no available rooms
for the selected dates.
Please try searching with different dates
or room types.
</p>
<div
className="flex flex-col sm:flex-row
gap-3 justify-center"
>
<button
onClick={() => navigate('/')}
className="px-6 py-3 bg-indigo-600
text-white rounded-lg
hover:bg-indigo-700
transition-colors font-semibold
inline-flex items-center
justify-center gap-2"
>
<Search className="w-5 h-5" />
Search Again
</button>
<Link
to="/rooms"
className="px-6 py-3 border
border-gray-300 text-gray-700
rounded-lg hover:bg-gray-50
transition-colors font-semibold
inline-flex items-center
justify-center gap-2"
>
View All Rooms
</Link>
</div>
</div>
)}
</>
)}
</div>
</div>
);
};
export default SearchResultsPage;