import React, { useEffect, useState } from 'react'; import { Plus, Search, Edit, Trash2, X, Upload, Image as ImageIcon } from 'lucide-react'; import serviceService, { Service } from '../../features/hotel_services/services/serviceService'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import IconPicker from '../../features/system/components/IconPicker'; import ConfirmationDialog from '../../shared/components/ConfirmationDialog'; const ServiceManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const [services, setServices] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [editingService, setEditingService] = useState(null); const [filters, setFilters] = useState({ search: '', status: '', }); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalItems, setTotalItems] = useState(0); const itemsPerPage = 5; const [formData, setFormData] = useState({ name: '', description: '', price: 0, unit: 'time', category: '', slug: '', image: '', content: '', sections: [] as any[], meta_title: '', meta_description: '', meta_keywords: '', status: 'active' as 'active' | 'inactive', }); const [imageFile, setImageFile] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [uploadingImage, setUploadingImage] = useState(false); const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; id: number | null }>({ show: false, id: null }); useEffect(() => { setCurrentPage(1); }, [filters]); useEffect(() => { fetchServices(); }, [filters, currentPage]); const fetchServices = async () => { try { setLoading(true); const response = await serviceService.getServices({ ...filters, page: currentPage, limit: itemsPerPage, }); setServices(response.data.services); if (response.data.pagination) { setTotalPages(response.data.pagination.totalPages); setTotalItems(response.data.pagination.total); } } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to load services list'); } finally { setLoading(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { // Auto-generate slug if not provided const dataToSubmit = { ...formData, slug: formData.slug || formData.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''), sections: formData.sections.length > 0 ? JSON.stringify(formData.sections) : null, }; if (editingService) { await serviceService.updateService(editingService.id, dataToSubmit); toast.success('Service updated successfully'); } else { await serviceService.createService(dataToSubmit); toast.success('Service added successfully'); } setShowModal(false); resetForm(); fetchServices(); } catch (error: any) { toast.error(error.response?.data?.message || 'An error occurred'); } }; const handleEdit = (service: Service) => { setEditingService(service); // Parse sections if it's a string let sections: any[] = []; if (service.sections) { if (typeof service.sections === 'string') { try { sections = JSON.parse(service.sections); } catch { sections = []; } } else if (Array.isArray(service.sections)) { sections = service.sections; } } setFormData({ name: service.name, description: service.description || '', price: service.price, unit: service.unit || 'time', category: service.category || '', slug: service.slug || '', image: service.image || '', content: service.content || '', sections: sections, meta_title: service.meta_title || '', meta_description: service.meta_description || '', meta_keywords: service.meta_keywords || '', status: service.status, }); // Set image preview if image exists if (service.image) { setImagePreview(service.image.startsWith('http') ? service.image : `${import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'}${service.image}`); } else { setImagePreview(null); } setShowModal(true); }; const handleDeleteClick = (id: number) => { setDeleteConfirm({ show: true, id }); }; const handleDelete = async () => { if (!deleteConfirm.id) return; try { await serviceService.deleteService(deleteConfirm.id); toast.success('Service deleted successfully'); setDeleteConfirm({ show: false, id: null }); fetchServices(); } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to delete service'); setDeleteConfirm({ show: false, id: null }); } }; const resetForm = () => { setEditingService(null); setFormData({ name: '', description: '', price: 0, unit: 'time', category: '', slug: '', image: '', content: '', sections: [], meta_title: '', meta_description: '', meta_keywords: '', status: 'active', }); setImagePreview(null); setImageFile(null); }; const handleImageUpload = async (file: File) => { try { setUploadingImage(true); const formData = new FormData(); formData.append('image', file); // Use the page content image upload endpoint or create a service-specific one const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8000'}/api/page-content/upload-image`, { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, }, body: formData, }); const data = await response.json(); if (data.status === 'success' && data.data?.image_url) { setFormData(prev => ({ ...prev, image: data.data.image_url })); setImagePreview(data.data.image_url); toast.success('Image uploaded successfully'); } else { throw new Error(data.message || 'Upload failed'); } } catch (error: any) { toast.error(error.message || 'Failed to upload image'); } finally { setUploadingImage(false); } }; if (loading) { return ; } return (
{}

Service Management

Manage hotel services

{}
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" />
{}
{services.map((service, index) => ( ))}
Service Name Description Price Unit Status Actions
{service.name}
{service.description}
{formatCurrency(service.price)}
{service.unit}
{service.status === 'active' ? 'Active' : 'Inactive'}
{} {showModal && (
{}

{editingService ? 'Update Service' : 'Add New Service'}

{editingService ? 'Modify service information' : 'Create a new service'}

{}
setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-3 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 font-medium shadow-sm" required />