update to python fastpi
This commit is contained in:
217
Frontend/src/components/rooms/RoomAmenities.tsx
Normal file
217
Frontend/src/components/rooms/RoomAmenities.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Wifi,
|
||||
Tv,
|
||||
Wind,
|
||||
Coffee,
|
||||
Utensils,
|
||||
Car,
|
||||
Dumbbell,
|
||||
Waves,
|
||||
UtensilsCrossed,
|
||||
Shield,
|
||||
Cigarette,
|
||||
Bath,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface RoomAmenitiesProps {
|
||||
amenities: string[];
|
||||
}
|
||||
|
||||
const RoomAmenities: React.FC<RoomAmenitiesProps> = ({
|
||||
amenities
|
||||
}) => {
|
||||
const normalizeAmenities = (input: any): string[] => {
|
||||
if (Array.isArray(input)) return input;
|
||||
if (!input) return [];
|
||||
if (typeof input === 'string') {
|
||||
// Try JSON.parse first (stringified JSON)
|
||||
try {
|
||||
const parsed = JSON.parse(input);
|
||||
if (Array.isArray(parsed)) return parsed;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Fallback: comma separated list
|
||||
return input
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// If it's an object with values as amenities
|
||||
if (typeof input === 'object') {
|
||||
try {
|
||||
// Convert object values to array if possible
|
||||
const vals = Object.values(input);
|
||||
if (Array.isArray(vals) && vals.length > 0) {
|
||||
// flatten nested arrays
|
||||
return vals.flat().map((v: any) => String(v).trim()).filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const safeAmenities = normalizeAmenities(amenities);
|
||||
|
||||
// Icon mapping for common amenities
|
||||
const amenityIcons: Record<string, React.ReactNode> = {
|
||||
wifi: <Wifi className="w-5 h-5" />,
|
||||
'wi-fi': <Wifi className="w-5 h-5" />,
|
||||
tv: <Tv className="w-5 h-5" />,
|
||||
television: <Tv className="w-5 h-5" />,
|
||||
'air-conditioning': <Wind className="w-5 h-5" />,
|
||||
'air conditioning': <Wind className="w-5 h-5" />,
|
||||
ac: <Wind className="w-5 h-5" />,
|
||||
'mini bar': <Coffee className="w-5 h-5" />,
|
||||
minibar: <Coffee className="w-5 h-5" />,
|
||||
restaurant: <Utensils className="w-5 h-5" />,
|
||||
parking: <Car className="w-5 h-5" />,
|
||||
gym: <Dumbbell className="w-5 h-5" />,
|
||||
fitness: <Dumbbell className="w-5 h-5" />,
|
||||
pool: <Waves className="w-5 h-5" />,
|
||||
'swimming pool': <Waves className="w-5 h-5" />,
|
||||
'room service': <UtensilsCrossed className="w-5 h-5" />,
|
||||
safe: <Shield className="w-5 h-5" />,
|
||||
'no smoking': <Cigarette className="w-5 h-5" />,
|
||||
bathtub: <Bath className="w-5 h-5" />,
|
||||
shower: <Bath className="w-5 h-5" />,
|
||||
breakfast: <Coffee className="w-5 h-5" />,
|
||||
'breakfast included': <Coffee className="w-5 h-5" />,
|
||||
kettle: <Coffee className="w-5 h-5" />,
|
||||
'hair dryer': <Shield className="w-5 h-5" />,
|
||||
hairdryer: <Shield className="w-5 h-5" />,
|
||||
iron: <Shield className="w-5 h-5" />,
|
||||
fridge: <Utensils className="w-5 h-5" />,
|
||||
microwave: <Utensils className="w-5 h-5" />,
|
||||
'private bathroom': <Bath className="w-5 h-5" />,
|
||||
balcony: <Wind className="w-5 h-5" />,
|
||||
'24-hour front desk': <Shield className="w-5 h-5" />,
|
||||
'front desk': <Shield className="w-5 h-5" />,
|
||||
spa: <Waves className="w-5 h-5" />,
|
||||
sauna: <Waves className="w-5 h-5" />,
|
||||
jacuzzi: <Waves className="w-5 h-5" />,
|
||||
'airport shuttle': <Car className="w-5 h-5" />,
|
||||
shuttle: <Car className="w-5 h-5" />,
|
||||
laundry: <Shield className="w-5 h-5" />,
|
||||
pets: <Car className="w-5 h-5" />,
|
||||
};
|
||||
|
||||
const amenityLabels: Record<string, string> = {
|
||||
wifi: 'Wi‑Fi',
|
||||
tv: 'TV',
|
||||
ac: 'Air Conditioning',
|
||||
'air-conditioning': 'Air Conditioning',
|
||||
minibar: 'Mini Bar',
|
||||
'mini bar': 'Mini Bar',
|
||||
restaurant: 'Restaurant',
|
||||
parking: 'Parking',
|
||||
gym: 'Gym',
|
||||
pool: 'Swimming Pool',
|
||||
'room service': 'Room Service',
|
||||
safe: 'Safe',
|
||||
'no smoking': 'No Smoking',
|
||||
bathtub: 'Bathtub',
|
||||
shower: 'Shower',
|
||||
breakfast: 'Breakfast Included',
|
||||
kettle: 'Electric Kettle',
|
||||
hairdryer: 'Hair Dryer',
|
||||
iron: 'Iron',
|
||||
fridge: 'Refrigerator',
|
||||
microwave: 'Microwave',
|
||||
'private bathroom': 'Private Bathroom',
|
||||
balcony: 'Balcony',
|
||||
spa: 'Spa',
|
||||
sauna: 'Sauna',
|
||||
jacuzzi: 'Jacuzzi',
|
||||
laundry: 'Laundry Service',
|
||||
'24-hour front desk': '24/7 Front Desk',
|
||||
'airport shuttle': 'Airport Shuttle',
|
||||
pets: 'Pets Allowed',
|
||||
};
|
||||
|
||||
const amenityDescriptions: Record<string, string> = {
|
||||
wifi: 'Free wireless internet connection',
|
||||
tv: 'TV with cable or satellite',
|
||||
ac: 'Air conditioning system in room',
|
||||
minibar: 'Drinks and snacks in mini bar',
|
||||
pool: 'Outdoor or indoor swimming pool',
|
||||
gym: 'Fitness center/gym',
|
||||
'room service': 'Order food to room',
|
||||
breakfast: 'Breakfast served at restaurant',
|
||||
balcony: 'Private balcony with view',
|
||||
'24-hour front desk': '24-hour front desk service',
|
||||
spa: 'Spa and relaxation services',
|
||||
};
|
||||
|
||||
const getIcon = (amenity: string) => {
|
||||
const key = amenity.toLowerCase().trim();
|
||||
return amenityIcons[key] || (
|
||||
<span className="w-5 h-5 flex items-center
|
||||
justify-center text-blue-600 font-bold"
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const getLabel = (amenity: string) => {
|
||||
const key = amenity.toLowerCase().trim();
|
||||
if (amenityLabels[key]) return amenityLabels[key];
|
||||
// Fallback: capitalize words and replace dashes/underscores
|
||||
return amenity
|
||||
.toLowerCase()
|
||||
.replace(/[_-]/g, ' ')
|
||||
.split(' ')
|
||||
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const getDescription = (amenity: string) => {
|
||||
const key = amenity.toLowerCase().trim();
|
||||
return amenityDescriptions[key] || '';
|
||||
};
|
||||
|
||||
if (safeAmenities.length === 0) {
|
||||
return (
|
||||
<div className="text-gray-500 text-center py-4">
|
||||
Amenity information is being updated
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2
|
||||
lg:grid-cols-3 gap-4"
|
||||
>
|
||||
{safeAmenities.slice(0, 10).map((amenity, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-3 p-3
|
||||
bg-gray-50 rounded-lg hover:bg-gray-100
|
||||
transition-colors"
|
||||
title={getDescription(amenity)}
|
||||
>
|
||||
<div className="text-blue-600">{getIcon(amenity)}</div>
|
||||
<div>
|
||||
<div className="text-gray-800 font-medium">
|
||||
{getLabel(amenity)}
|
||||
</div>
|
||||
{getDescription(amenity) && (
|
||||
<div className="text-xs text-gray-500">
|
||||
{getDescription(amenity)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomAmenities;
|
||||
Reference in New Issue
Block a user