import React, { useEffect, useState } from 'react'; import { Plus, Search, Edit, Trash2, X, Package as PackageIcon } from 'lucide-react'; import { packageService, Package, PackageStatus, PackageItem, PackageItemType, CreatePackageData } from '../../services/api'; import { roomService, Room } from '../../services/api'; import { serviceService, Service } from '../../services/api'; import { toast } from 'react-toastify'; import Loading from '../../components/common/Loading'; import Pagination from '../../components/common/Pagination'; import { useFormatCurrency } from '../../hooks/useFormatCurrency'; const PackageManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const [packages, setPackages] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [editingPackage, setEditingPackage] = useState(null); const [roomTypes, setRoomTypes] = useState>([]); const [, setServices] = useState([]); const [filters, setFilters] = useState({ search: '', status: '', room_type_id: '', }); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalItems, setTotalItems] = useState(0); const itemsPerPage = 10; const [formData, setFormData] = useState({ name: '', code: '', description: '', status: 'active', base_price: undefined, price_modifier: 1.0, discount_percentage: undefined, room_type_id: undefined, min_nights: undefined, max_nights: undefined, valid_from: '', valid_to: '', image_url: '', highlights: [], terms_conditions: '', extra_data: undefined, items: [], }); const [newItem, setNewItem] = useState>({ item_type: 'service', item_name: '', item_description: '', quantity: 1, unit: 'per_stay', price: undefined, included: true, display_order: 0, }); useEffect(() => { setCurrentPage(1); }, [filters]); useEffect(() => { fetchPackages(); }, [filters, currentPage]); useEffect(() => { fetchRoomTypes(); fetchServices(); }, []); const fetchRoomTypes = async () => { try { const response = await roomService.getRooms({ limit: 100, page: 1 }); const allUniqueRoomTypes = new Map(); 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 (err) { console.error('Failed to fetch room types:', err); } }; const fetchServices = async () => { try { const response = await serviceService.getServices(); if (response.data && response.data.services) { setServices(response.data.services); } } catch (err) { console.error('Failed to fetch services:', err); } }; const fetchPackages = async () => { try { setLoading(true); const params: any = { page: currentPage, limit: itemsPerPage, }; if (filters.search) params.search = filters.search; if (filters.status) params.status = filters.status; if (filters.room_type_id) params.room_type_id = parseInt(filters.room_type_id); const response = await packageService.getPackages(params); setPackages(response.data.packages); if (response.data.pagination) { setTotalPages(response.data.pagination.totalPages); setTotalItems(response.data.pagination.total); } } catch (error: any) { toast.error(error.response?.data?.detail || 'Unable to load packages'); } finally { setLoading(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const submitData = { ...formData, room_type_id: formData.room_type_id ? parseInt(formData.room_type_id.toString()) : undefined, base_price: formData.base_price || undefined, discount_percentage: formData.discount_percentage || undefined, valid_from: formData.valid_from || undefined, valid_to: formData.valid_to || undefined, }; if (editingPackage) { await packageService.updatePackage(editingPackage.id, submitData); toast.success('Package updated successfully'); } else { await packageService.createPackage(submitData); toast.success('Package created successfully'); } setShowModal(false); resetForm(); fetchPackages(); } catch (error: any) { toast.error(error.response?.data?.detail || 'An error occurred'); } }; const handleEdit = (pkg: Package) => { setEditingPackage(pkg); setFormData({ name: pkg.name, code: pkg.code, description: pkg.description || '', status: pkg.status, base_price: pkg.base_price, price_modifier: pkg.price_modifier, discount_percentage: pkg.discount_percentage, room_type_id: pkg.room_type_id, min_nights: pkg.min_nights, max_nights: pkg.max_nights, valid_from: pkg.valid_from?.split('T')[0] || '', valid_to: pkg.valid_to?.split('T')[0] || '', image_url: pkg.image_url || '', highlights: pkg.highlights || [], terms_conditions: pkg.terms_conditions || '', items: pkg.items || [], }); setShowModal(true); }; const handleDelete = async (id: number) => { if (!window.confirm('Are you sure you want to delete this package?')) return; try { await packageService.deletePackage(id); toast.success('Package deleted successfully'); fetchPackages(); } catch (error: any) { toast.error(error.response?.data?.detail || 'Unable to delete package'); } }; const resetForm = () => { setEditingPackage(null); setFormData({ name: '', code: '', description: '', status: 'active', base_price: undefined, price_modifier: 1.0, discount_percentage: undefined, room_type_id: undefined, min_nights: undefined, max_nights: undefined, valid_from: '', valid_to: '', image_url: '', highlights: [], terms_conditions: '', extra_data: undefined, items: [], }); setNewItem({ item_type: 'service', item_name: '', item_description: '', quantity: 1, unit: 'per_stay', price: undefined, included: true, display_order: 0, }); }; const addItem = () => { if (!newItem.item_name) { toast.error('Please enter item name'); return; } setFormData({ ...formData, items: [ ...(formData.items || []), { ...newItem, display_order: formData.items?.length || 0, } as PackageItem, ], }); setNewItem({ item_type: 'service', item_name: '', item_description: '', quantity: 1, unit: 'per_stay', price: undefined, included: true, display_order: 0, }); }; const removeItem = (index: number) => { const newItems = [...(formData.items || [])]; newItems.splice(index, 1); setFormData({ ...formData, items: newItems }); }; const addHighlight = () => { const highlight = prompt('Enter highlight:'); if (highlight) { setFormData({ ...formData, highlights: [...(formData.highlights || []), highlight], }); } }; const removeHighlight = (index: number) => { const newHighlights = [...(formData.highlights || [])]; newHighlights.splice(index, 1); setFormData({ ...formData, highlights: newHighlights }); }; const getStatusBadge = (status: string) => { const badges: Record = { active: { bg: 'bg-gradient-to-r from-emerald-50 to-green-50', text: 'text-emerald-800', label: 'Active', border: 'border-emerald-200' }, inactive: { bg: 'bg-gradient-to-r from-slate-50 to-gray-50', text: 'text-slate-700', label: 'Inactive', border: 'border-slate-200' }, scheduled: { bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', text: 'text-blue-800', label: 'Scheduled', border: 'border-blue-200' }, expired: { bg: 'bg-gradient-to-r from-rose-50 to-red-50', text: 'text-rose-800', label: 'Expired', border: 'border-rose-200' }, }; const badge = badges[status] || badges.active; return ( {badge.label} ); }; if (loading && packages.length === 0) { return ; } return (

Package Management

Manage package deals and bundles

{/* Filters */}
setFilters({ ...filters, search: e.target.value })} className="w-full pl-12 pr-4 py-3.5 bg-white border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100 transition-all duration-200 text-slate-700 placeholder-slate-400 font-medium shadow-sm hover:shadow-md" />
{/* Packages Table */}
{packages.map((pkg) => ( ))}
Code Name Items Pricing Room Type Status Actions
{pkg.code}
{pkg.name}
{pkg.description}
{pkg.items?.length || 0} item(s)
{pkg.base_price ? ( {formatCurrency(pkg.base_price)} ) : pkg.discount_percentage ? ( {pkg.discount_percentage}% off ) : ( Custom pricing )}
{pkg.room_type_name || 'All Types'}
{getStatusBadge(pkg.status)}
{packages.length === 0 && (

No packages found

)}
{totalPages > 1 && (
)}
{/* Create/Edit Modal */} {showModal && (

{editingPackage ? 'Edit Package' : 'Create Package'}

setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100 transition-all" />
setFormData({ ...formData, code: e.target.value.toUpperCase() })} className="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100 transition-all disabled:bg-slate-100" />