updates
This commit is contained in:
@@ -17,10 +17,7 @@ const EditRoomPage: React.FC = () => {
|
||||
const [editingRoom, setEditingRoom] = useState<Room | null>(null);
|
||||
const [uploadingImages, setUploadingImages] = useState(false);
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const [customAmenityInput, setCustomAmenityInput] = useState('');
|
||||
const [editingAmenity, setEditingAmenity] = useState<{ name: string; newName: string } | null>(null);
|
||||
const [availableAmenities, setAvailableAmenities] = useState<string[]>([]);
|
||||
const [roomTypes, setRoomTypes] = useState<Array<{ id: number; name: string }>>([]);
|
||||
const [roomTypes, setRoomTypes] = useState<Array<{ id: number; name: string; amenities?: string[] | string }>>([]);
|
||||
const [deletingImageUrl, setDeletingImageUrl] = useState<string | null>(null);
|
||||
const [failedImageUrls, setFailedImageUrls] = useState<Set<string>>(new Set());
|
||||
const [loadingImageUrls, setLoadingImageUrls] = useState<Set<string>>(new Set());
|
||||
@@ -40,13 +37,27 @@ const EditRoomPage: React.FC = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchRoomData();
|
||||
}
|
||||
fetchAvailableAmenities();
|
||||
fetchRoomTypes();
|
||||
const loadData = async () => {
|
||||
await fetchRoomTypes();
|
||||
if (id) {
|
||||
await fetchRoomData();
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
// Update amenities when room type changes
|
||||
useEffect(() => {
|
||||
if (formData.room_type_id && roomTypes.length > 0) {
|
||||
const selectedRoomType = roomTypes.find(rt => rt.id === formData.room_type_id);
|
||||
const inheritedAmenities = selectedRoomType ? getAmenitiesArray(selectedRoomType.amenities) : [];
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
amenities: inheritedAmenities
|
||||
}));
|
||||
}
|
||||
}, [formData.room_type_id, roomTypes]);
|
||||
|
||||
// Reset image loading states when room data changes
|
||||
useEffect(() => {
|
||||
if (editingRoom) {
|
||||
@@ -85,56 +96,53 @@ const EditRoomPage: React.FC = () => {
|
||||
}
|
||||
}, [editingRoom?.id]);
|
||||
|
||||
// Helper function to ensure amenities is always an array
|
||||
const getAmenitiesArray = (amenities: any): string[] => {
|
||||
if (!amenities) return [];
|
||||
if (Array.isArray(amenities)) return amenities;
|
||||
if (typeof amenities === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(amenities);
|
||||
if (Array.isArray(parsed)) return parsed;
|
||||
} catch {
|
||||
// Not JSON, treat as comma-separated
|
||||
return amenities.split(',').map((a: string) => a.trim()).filter(Boolean);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const fetchRoomTypes = async () => {
|
||||
try {
|
||||
const response = await roomService.getRooms({ limit: 100, page: 1 });
|
||||
const allUniqueRoomTypes = new Map<number, { id: number; name: string }>();
|
||||
response.data.rooms.forEach((room: Room) => {
|
||||
if (room.room_type && !allUniqueRoomTypes.has(room.room_type.id)) {
|
||||
allUniqueRoomTypes.set(room.room_type.id, {
|
||||
id: room.room_type.id,
|
||||
name: room.room_type.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.pagination && response.data.pagination.totalPages > 1) {
|
||||
const totalPages = response.data.pagination.totalPages;
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
try {
|
||||
const pageResponse = await roomService.getRooms({ limit: 100, page });
|
||||
pageResponse.data.rooms.forEach((room: Room) => {
|
||||
if (room.room_type && !allUniqueRoomTypes.has(room.room_type.id)) {
|
||||
allUniqueRoomTypes.set(room.room_type.id, {
|
||||
id: room.room_type.id,
|
||||
name: room.room_type.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`Failed to fetch page ${page}`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allUniqueRoomTypes.size > 0) {
|
||||
setRoomTypes(Array.from(allUniqueRoomTypes.values()));
|
||||
const response = await roomService.getRoomTypes();
|
||||
if (response.data?.room_types) {
|
||||
setRoomTypes(response.data.room_types);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to fetch room types', err);
|
||||
// Fallback to old method if getRoomTypes fails
|
||||
try {
|
||||
const response = await roomService.getRooms({ limit: 100, page: 1 });
|
||||
const allUniqueRoomTypes = new Map<number, { id: number; name: string }>();
|
||||
response.data.rooms.forEach((room: Room) => {
|
||||
if (room.room_type && !allUniqueRoomTypes.has(room.room_type.id)) {
|
||||
allUniqueRoomTypes.set(room.room_type.id, {
|
||||
id: room.room_type.id,
|
||||
name: room.room_type.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
if (allUniqueRoomTypes.size > 0) {
|
||||
setRoomTypes(Array.from(allUniqueRoomTypes.values()));
|
||||
}
|
||||
} catch (fallbackErr) {
|
||||
logger.error('Failed to fetch room types (fallback)', fallbackErr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAvailableAmenities = async () => {
|
||||
try {
|
||||
const response = await roomService.getAmenities();
|
||||
if (response.data?.amenities) {
|
||||
setAvailableAmenities(response.data.amenities);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch amenities', error);
|
||||
}
|
||||
};
|
||||
// Amenities are now managed only at the room type level
|
||||
// No need to fetch available amenities separately
|
||||
|
||||
const fetchRoomData = async () => {
|
||||
if (!id) return null;
|
||||
@@ -162,6 +170,10 @@ const EditRoomPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Inherit amenities from room type when setting form data
|
||||
const roomType = roomTypes.find(rt => rt.id === room.room_type_id);
|
||||
const inheritedAmenities = roomType ? getAmenitiesArray(roomType.amenities) : amenitiesArray;
|
||||
|
||||
setFormData({
|
||||
room_number: room.room_number,
|
||||
floor: room.floor,
|
||||
@@ -173,7 +185,7 @@ const EditRoomPage: React.FC = () => {
|
||||
capacity: room.capacity?.toString() || '',
|
||||
room_size: room.room_size || '',
|
||||
view: room.view || '',
|
||||
amenities: amenitiesArray,
|
||||
amenities: inheritedAmenities, // Always use room type amenities
|
||||
});
|
||||
|
||||
return room;
|
||||
@@ -199,7 +211,8 @@ const EditRoomPage: React.FC = () => {
|
||||
capacity: formData.capacity ? parseInt(formData.capacity) : undefined,
|
||||
room_size: formData.room_size || undefined,
|
||||
view: formData.view || undefined,
|
||||
amenities: Array.isArray(formData.amenities) ? formData.amenities : [],
|
||||
// Don't send amenities - backend will inherit from room type
|
||||
amenities: [],
|
||||
};
|
||||
|
||||
await contextUpdateRoom(editingRoom.id, updateData);
|
||||
@@ -211,92 +224,8 @@ const EditRoomPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleAmenity = (amenity: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
amenities: prev.amenities.includes(amenity)
|
||||
? prev.amenities.filter(a => a !== amenity)
|
||||
: [...prev.amenities, amenity]
|
||||
}));
|
||||
};
|
||||
|
||||
const handleAddCustomAmenity = () => {
|
||||
const trimmed = customAmenityInput.trim();
|
||||
if (trimmed && !formData.amenities.includes(trimmed)) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
amenities: [...prev.amenities, trimmed]
|
||||
}));
|
||||
setCustomAmenityInput('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveAmenity = (amenity: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
amenities: prev.amenities.filter(a => a !== amenity)
|
||||
}));
|
||||
};
|
||||
|
||||
const handleEditAmenity = (amenity: string) => {
|
||||
setEditingAmenity({ name: amenity, newName: amenity });
|
||||
};
|
||||
|
||||
const handleSaveAmenityEdit = async () => {
|
||||
if (!editingAmenity || editingAmenity.name === editingAmenity.newName.trim()) {
|
||||
setEditingAmenity(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const newName = editingAmenity.newName.trim();
|
||||
if (!newName) {
|
||||
toast.error('Amenity name cannot be empty');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await roomService.updateAmenity(editingAmenity.name, newName);
|
||||
toast.success(`Amenity "${editingAmenity.name}" updated to "${newName}"`);
|
||||
|
||||
setAvailableAmenities(prev => {
|
||||
const updated = prev.map(a => a === editingAmenity.name ? newName : a);
|
||||
return updated.sort();
|
||||
});
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
amenities: prev.amenities.map(a => a === editingAmenity.name ? newName : a)
|
||||
}));
|
||||
|
||||
setEditingAmenity(null);
|
||||
await fetchAvailableAmenities();
|
||||
await refreshRooms();
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data?.message || error.response?.data?.detail || 'Failed to update amenity');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAmenity = async (amenity: string) => {
|
||||
if (!window.confirm(`Are you sure you want to delete "${amenity}"? This will remove it from all rooms and room types.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await roomService.deleteAmenity(amenity);
|
||||
toast.success(`Amenity "${amenity}" deleted successfully`);
|
||||
|
||||
setAvailableAmenities(prev => prev.filter(a => a !== amenity));
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
amenities: prev.amenities.filter(a => a !== amenity)
|
||||
}));
|
||||
|
||||
await fetchAvailableAmenities();
|
||||
await refreshRooms();
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data?.message || error.response?.data?.detail || 'Failed to delete amenity');
|
||||
}
|
||||
};
|
||||
// Amenities are now managed only at the room type level
|
||||
// Rooms automatically inherit amenities from their room type
|
||||
|
||||
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
@@ -763,7 +692,17 @@ const EditRoomPage: React.FC = () => {
|
||||
</label>
|
||||
<select
|
||||
value={formData.room_type_id}
|
||||
onChange={(e) => setFormData({ ...formData, room_type_id: parseInt(e.target.value) })}
|
||||
onChange={(e) => {
|
||||
const selectedRoomTypeId = parseInt(e.target.value);
|
||||
// Find the selected room type and inherit its amenities
|
||||
const selectedRoomType = roomTypes.find(rt => rt.id === selectedRoomTypeId);
|
||||
const inheritedAmenities = selectedRoomType ? getAmenitiesArray(selectedRoomType.amenities) : [];
|
||||
setFormData({
|
||||
...formData,
|
||||
room_type_id: selectedRoomTypeId,
|
||||
amenities: inheritedAmenities // Inherit amenities from room type
|
||||
});
|
||||
}}
|
||||
className="w-full px-5 py-3.5 bg-gradient-to-br from-white to-slate-50/50 border-2 border-slate-200 rounded-2xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100/50 transition-all duration-300 text-slate-800 font-medium shadow-sm hover:shadow-md focus:shadow-lg cursor-pointer appearance-none bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNiA5TDEyIDE1TDE4IDkiIHN0cm9rZT0iIzkyOUE1IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==')] bg-[length:20px] bg-[right_1rem_center] bg-no-repeat pr-12"
|
||||
required
|
||||
>
|
||||
@@ -913,184 +852,50 @@ const EditRoomPage: React.FC = () => {
|
||||
<h2 className="text-xl sm:text-2xl font-bold bg-gradient-to-r from-slate-900 to-slate-700 bg-clip-text text-transparent">
|
||||
Amenities & Features
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500 mt-1">Customize room amenities and special features</p>
|
||||
<p className="text-sm text-slate-500 mt-1">Amenities are inherited from the room type</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom Amenity Input */}
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
value={customAmenityInput}
|
||||
onChange={(e) => setCustomAmenityInput(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleAddCustomAmenity();
|
||||
}
|
||||
}}
|
||||
placeholder="Add custom amenity..."
|
||||
className="w-full px-5 py-3.5 bg-gradient-to-br from-white to-slate-50/50 border-2 border-slate-200 rounded-2xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100/50 transition-all duration-300 text-slate-800 font-medium shadow-sm hover:shadow-md focus:shadow-lg placeholder:text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddCustomAmenity}
|
||||
disabled={!customAmenityInput.trim()}
|
||||
className="px-6 py-3.5 bg-gradient-to-r from-amber-500 to-amber-600 text-white rounded-2xl hover:from-amber-600 hover:to-amber-700 transition-all duration-300 font-semibold shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 transform hover:scale-105 disabled:hover:scale-100"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
<span className="hidden sm:inline">Add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Selected Amenities */}
|
||||
{formData.amenities.length > 0 && (
|
||||
<div className="p-5 bg-gradient-to-br from-amber-50/50 to-amber-100/30 rounded-2xl border-2 border-amber-200/50 shadow-lg">
|
||||
<p className="text-sm font-semibold text-slate-700 mb-4 flex items-center gap-2">
|
||||
<Check className="w-4 h-4 text-amber-600" />
|
||||
Selected Amenities ({formData.amenities.length})
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
{formData.amenities.map((amenity) => {
|
||||
const isCustom = !availableAmenities.includes(amenity);
|
||||
return (
|
||||
<div
|
||||
key={amenity}
|
||||
className="group flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-amber-100 to-amber-50 rounded-xl border border-amber-300 shadow-md hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
<span className="text-sm font-semibold text-amber-800">{amenity}</span>
|
||||
{isCustom && (
|
||||
<span className="text-xs font-medium text-amber-700 bg-amber-200/50 px-2 py-0.5 rounded-lg">Custom</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveAmenity(amenity)}
|
||||
className="ml-1 p-1 rounded-lg text-slate-400 hover:text-red-600 hover:bg-red-50 transition-all duration-200"
|
||||
title="Remove amenity"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Available Amenities */}
|
||||
<div className="border-2 border-slate-200 rounded-2xl p-4 sm:p-6 max-h-96 overflow-y-auto bg-gradient-to-br from-slate-50/50 to-white shadow-inner custom-scrollbar">
|
||||
{availableAmenities.length === 0 ? (
|
||||
<p className="text-sm text-slate-500 text-center py-8">Loading amenities...</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm font-semibold text-slate-700 mb-4 flex items-center gap-2">
|
||||
<Sparkles className="w-4 h-4 text-amber-500" />
|
||||
Available Amenities
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 gap-3">
|
||||
{availableAmenities.map((amenity) => {
|
||||
const isSelected = formData.amenities.includes(amenity);
|
||||
const isEditing = editingAmenity?.name === amenity;
|
||||
return (
|
||||
<div
|
||||
<div className="border-2 border-slate-200 rounded-2xl p-5 sm:p-6 bg-gradient-to-br from-slate-50/50 to-white shadow-inner">
|
||||
{(() => {
|
||||
const selectedRoomType = roomTypes.find(rt => rt.id === formData.room_type_id);
|
||||
const inheritedAmenities = selectedRoomType ? getAmenitiesArray(selectedRoomType.amenities) : [];
|
||||
|
||||
if (inheritedAmenities.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-6">
|
||||
<p className="text-sm text-slate-500 mb-2">No amenities defined for this room type.</p>
|
||||
<p className="text-xs text-slate-400 italic">
|
||||
Go to the <strong>Room Types</strong> tab in Advanced Room Management to add amenities to this room type.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<p className="text-xs text-slate-500 italic flex items-center gap-2">
|
||||
<Check className="w-3 h-3 text-amber-500" />
|
||||
Inherited from room type: <strong className="text-amber-600">{selectedRoomType?.name}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
{inheritedAmenities.map((amenity) => (
|
||||
<span
|
||||
key={amenity}
|
||||
className={`flex items-center gap-3 p-4 rounded-2xl transition-all duration-300 ${
|
||||
isSelected
|
||||
? 'bg-gradient-to-r from-amber-50 to-amber-100/50 border-2 border-amber-400 shadow-lg'
|
||||
: 'bg-white border-2 border-slate-200 hover:border-amber-300 hover:shadow-md'
|
||||
}`}
|
||||
className="px-4 py-2 bg-gradient-to-r from-amber-100 to-amber-50 border border-amber-300 rounded-xl text-sm font-semibold text-amber-800 shadow-sm"
|
||||
>
|
||||
<label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleAmenity(amenity)}
|
||||
className="hidden"
|
||||
/>
|
||||
<div className={`w-6 h-6 rounded-xl border-2 flex items-center justify-center transition-all duration-300 flex-shrink-0 shadow-md ${
|
||||
isSelected
|
||||
? 'bg-gradient-to-br from-amber-400 to-amber-600 border-amber-500 shadow-amber-200'
|
||||
: 'border-slate-300 bg-white'
|
||||
}`}>
|
||||
{isSelected && <Check className="w-4 h-4 text-white font-bold" />}
|
||||
</div>
|
||||
{isEditing ? (
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<input
|
||||
type="text"
|
||||
value={editingAmenity.newName}
|
||||
onChange={(e) => setEditingAmenity({ ...editingAmenity, newName: e.target.value })}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSaveAmenityEdit();
|
||||
} else if (e.key === 'Escape') {
|
||||
setEditingAmenity(null);
|
||||
}
|
||||
}}
|
||||
className="flex-1 px-4 py-2 bg-white border-2 border-amber-400 rounded-xl text-slate-800 text-sm font-medium focus:ring-2 focus:ring-amber-300"
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveAmenityEdit}
|
||||
className="p-2 rounded-xl bg-green-50 text-green-600 hover:bg-green-100 transition-colors shadow-sm"
|
||||
title="Save"
|
||||
>
|
||||
<Check className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditingAmenity(null)}
|
||||
className="p-2 rounded-xl bg-slate-100 text-slate-600 hover:bg-slate-200 transition-colors shadow-sm"
|
||||
title="Cancel"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<span className={`text-sm flex-1 font-medium transition-colors ${
|
||||
isSelected ? 'text-amber-800' : 'text-slate-700'
|
||||
}`}>
|
||||
{amenity}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
{!isEditing && (
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleEditAmenity(amenity);
|
||||
}}
|
||||
className="p-2.5 rounded-xl bg-blue-50 border border-blue-200 text-blue-600 hover:bg-blue-100 hover:border-blue-300 transition-all duration-200 shadow-sm hover:shadow-md"
|
||||
title="Edit amenity"
|
||||
>
|
||||
<EditIcon className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleDeleteAmenity(amenity);
|
||||
}}
|
||||
className="p-2.5 rounded-xl bg-red-50 border border-red-200 text-red-600 hover:bg-red-100 hover:border-red-300 transition-all duration-200 shadow-sm hover:shadow-md"
|
||||
title="Delete amenity"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{amenity}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-4 italic">
|
||||
To modify amenities, edit the room type in the <strong>Room Types</strong> tab.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user