import React, { useEffect, useState } from 'react'; import { FileText, CreditCard, Tag, Search, Plus, Edit, Trash2, Eye, Filter, Sparkles, ChevronRight, X, } from 'lucide-react'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { useCurrency } from '../../features/payments/contexts/CurrencyContext'; import { useNavigate } from 'react-router-dom'; import invoiceService, { Invoice } from '../../features/payments/services/invoiceService'; import paymentService from '../../features/payments/services/paymentService'; import type { Payment } from '../../features/payments/services/paymentService'; import promotionService, { Promotion } from '../../features/loyalty/services/promotionService'; import { getRoomTypes } from '../../features/rooms/services/roomService'; import { formatDate } from '../../shared/utils/format'; interface RoomType { id: number; name: string; } type BusinessTab = 'overview' | 'invoices' | 'payments' | 'promotions'; const BusinessDashboardPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const { currency } = useCurrency(); const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('overview'); const [invoices, setInvoices] = useState([]); const [invoicesLoading, setInvoicesLoading] = useState(true); const [invoiceFilters, setInvoiceFilters] = useState({ search: '', status: '', }); const [invoicesCurrentPage, setInvoicesCurrentPage] = useState(1); const [invoicesTotalPages, setInvoicesTotalPages] = useState(1); const [invoicesTotalItems, setInvoicesTotalItems] = useState(0); const invoicesPerPage = 10; const [payments, setPayments] = useState([]); const [paymentsLoading, setPaymentsLoading] = useState(true); const [paymentFilters, setPaymentFilters] = useState({ search: '', method: '', from: '', to: '', }); const [paymentsCurrentPage, setPaymentsCurrentPage] = useState(1); const [paymentsTotalPages, setPaymentsTotalPages] = useState(1); const [paymentsTotalItems, setPaymentsTotalItems] = useState(0); const paymentsPerPage = 5; const [promotions, setPromotions] = useState([]); const [promotionsLoading, setPromotionsLoading] = useState(true); const [showPromotionModal, setShowPromotionModal] = useState(false); const [editingPromotion, setEditingPromotion] = useState(null); const [promotionFilters, setPromotionFilters] = useState({ search: '', status: '', type: '', }); const [promotionsCurrentPage, setPromotionsCurrentPage] = useState(1); const [promotionsTotalPages, setPromotionsTotalPages] = useState(1); const [promotionsTotalItems, setPromotionsTotalItems] = useState(0); const promotionsPerPage = 5; const [promotionFormData, setPromotionFormData] = useState({ code: '', name: '', description: '', discount_type: 'percentage' as 'percentage' | 'fixed', discount_value: 0, min_booking_amount: 0, max_discount_amount: 0, min_stay_days: 0, max_stay_days: 0, advance_booking_days: 0, max_advance_booking_days: 0, allowed_check_in_days: [] as number[], allowed_check_out_days: [] as number[], allowed_room_type_ids: [] as number[], excluded_room_type_ids: [] as number[], min_guests: 0, max_guests: 0, first_time_customer_only: false, repeat_customer_only: false, blackout_dates: [] as string[], start_date: '', end_date: '', usage_limit: 0, status: 'active' as 'active' | 'inactive' | 'expired', }); const [roomTypes, setRoomTypes] = useState([]); useEffect(() => { if (activeTab === 'invoices') { fetchInvoices(); } else if (activeTab === 'payments') { fetchPayments(); } else if (activeTab === 'promotions') { fetchPromotions(); } }, [activeTab, invoiceFilters, invoicesCurrentPage, paymentFilters, paymentsCurrentPage, promotionFilters, promotionsCurrentPage]); useEffect(() => { fetchRoomTypes(); }, []); const fetchRoomTypes = async () => { try { const response = await getRoomTypes(); if (response.success && response.data.room_types) { setRoomTypes(response.data.room_types); } } catch (error: any) { console.error('Failed to fetch room types:', error); } }; useEffect(() => { if (activeTab === 'invoices') { setInvoicesCurrentPage(1); } }, [invoiceFilters]); useEffect(() => { if (activeTab === 'payments') { setPaymentsCurrentPage(1); } }, [paymentFilters]); useEffect(() => { if (activeTab === 'promotions') { setPromotionsCurrentPage(1); } }, [promotionFilters]); const fetchInvoices = async () => { try { setInvoicesLoading(true); const response = await invoiceService.getInvoices({ status: invoiceFilters.status || undefined, page: invoicesCurrentPage, limit: invoicesPerPage, }); if (response.status === 'success' && response.data) { let invoiceList = response.data.invoices || []; // Client-side filtering for search (only on current page results) // Note: This is a limitation - search only works on current page // For full search functionality, backend needs to support search parameter if (invoiceFilters.search) { invoiceList = invoiceList.filter((inv) => inv.invoice_number.toLowerCase().includes(invoiceFilters.search.toLowerCase()) || inv.customer_name.toLowerCase().includes(invoiceFilters.search.toLowerCase()) || inv.customer_email.toLowerCase().includes(invoiceFilters.search.toLowerCase()) ); } setInvoices(invoiceList); // Only update pagination if not searching (to avoid incorrect counts) if (!invoiceFilters.search) { setInvoicesTotalPages(response.data.total_pages || 1); setInvoicesTotalItems(response.data.total || 0); } else { // When searching, keep original pagination but show filtered count setInvoicesTotalPages(response.data.total_pages || 1); setInvoicesTotalItems(response.data.total || 0); } } } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to load invoices'); } finally { setInvoicesLoading(false); } }; const handleDeleteInvoice = async (id: number) => { if (!window.confirm('Are you sure you want to delete this invoice?')) { return; } try { await invoiceService.deleteInvoice(id); toast.success('Invoice deleted successfully'); fetchInvoices(); } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to delete invoice'); } }; const getInvoiceStatusBadge = (status: string) => { const badges: Record = { draft: { bg: 'bg-gradient-to-r from-slate-50 to-gray-50', text: 'text-slate-700', label: 'Draft', border: 'border-slate-200' }, sent: { bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', text: 'text-blue-800', label: 'Sent', border: 'border-blue-200' }, paid: { bg: 'bg-gradient-to-r from-emerald-50 to-green-50', text: 'text-emerald-800', label: 'Paid', border: 'border-emerald-200' }, overdue: { bg: 'bg-gradient-to-r from-rose-50 to-red-50', text: 'text-rose-800', label: 'Overdue', border: 'border-rose-200' }, cancelled: { bg: 'bg-gradient-to-r from-rose-50 to-red-50', text: 'text-rose-800', label: '❌ Canceled', border: 'border-rose-200' }, }; return badges[status] || badges.draft; }; const fetchPayments = async () => { try { setPaymentsLoading(true); const response = await paymentService.getPayments({ ...paymentFilters, page: paymentsCurrentPage, limit: paymentsPerPage, }); setPayments(response.data.payments); if (response.data.pagination) { setPaymentsTotalPages(response.data.pagination.totalPages); setPaymentsTotalItems(response.data.pagination.total); } } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to load payments list'); } finally { setPaymentsLoading(false); } }; const getPaymentMethodBadge = (method: string) => { const badges: Record = { cash: { bg: 'bg-gradient-to-r from-emerald-50 to-green-50', text: 'text-emerald-800', label: 'Cash', border: 'border-emerald-200' }, bank_transfer: { bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', text: 'text-blue-800', label: 'Bank transfer', border: 'border-blue-200' }, stripe: { bg: 'bg-gradient-to-r from-indigo-50 to-purple-50', text: 'text-indigo-800', label: 'Stripe', border: 'border-indigo-200' }, paypal: { bg: 'bg-gradient-to-r from-blue-50 to-cyan-50', text: 'text-blue-800', label: 'PayPal', border: 'border-blue-200' }, credit_card: { bg: 'bg-gradient-to-r from-purple-50 to-pink-50', text: 'text-purple-800', label: 'Credit card', border: 'border-purple-200' }, }; const badge = badges[method] || badges.cash; return ( {badge.label} ); }; const getPaymentStatusBadge = (status: string) => { const statusConfig: Record = { completed: { bg: 'bg-gradient-to-r from-emerald-50 to-green-50', text: 'text-emerald-800', label: '✅ Paid', border: 'border-emerald-200' }, pending: { bg: 'bg-gradient-to-r from-amber-50 to-yellow-50', text: 'text-amber-800', label: '⏳ Pending', border: 'border-amber-200' }, failed: { bg: 'bg-gradient-to-r from-rose-50 to-red-50', text: 'text-rose-800', label: '❌ Failed', border: 'border-rose-200' }, refunded: { bg: 'bg-gradient-to-r from-slate-50 to-gray-50', text: 'text-slate-700', label: '💰 Refunded', border: 'border-slate-200' }, }; const config = statusConfig[status] || statusConfig.pending; return ( {config.label} ); }; const fetchPromotions = async () => { try { setPromotionsLoading(true); const response = await promotionService.getPromotions({ ...promotionFilters, page: promotionsCurrentPage, limit: promotionsPerPage, }); setPromotions(response.data.promotions); if (response.data.pagination) { setPromotionsTotalPages(response.data.pagination.totalPages); setPromotionsTotalItems(response.data.pagination.total); } } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to load promotions list'); } finally { setPromotionsLoading(false); } }; const handlePromotionSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { // Prepare data, converting empty arrays to undefined and 0 values to undefined for optional fields const submitData: any = { ...promotionFormData, min_stay_days: promotionFormData.min_stay_days || undefined, max_stay_days: promotionFormData.max_stay_days || undefined, advance_booking_days: promotionFormData.advance_booking_days || undefined, max_advance_booking_days: promotionFormData.max_advance_booking_days || undefined, min_guests: promotionFormData.min_guests || undefined, max_guests: promotionFormData.max_guests || undefined, allowed_check_in_days: promotionFormData.allowed_check_in_days?.length ? promotionFormData.allowed_check_in_days : undefined, allowed_check_out_days: promotionFormData.allowed_check_out_days?.length ? promotionFormData.allowed_check_out_days : undefined, allowed_room_type_ids: promotionFormData.allowed_room_type_ids?.length ? promotionFormData.allowed_room_type_ids : undefined, excluded_room_type_ids: promotionFormData.excluded_room_type_ids?.length ? promotionFormData.excluded_room_type_ids : undefined, blackout_dates: promotionFormData.blackout_dates?.length ? promotionFormData.blackout_dates : undefined, min_booking_amount: promotionFormData.min_booking_amount || undefined, max_discount_amount: promotionFormData.max_discount_amount || undefined, usage_limit: promotionFormData.usage_limit || undefined, }; if (editingPromotion) { await promotionService.updatePromotion(editingPromotion.id, submitData); toast.success('Promotion updated successfully'); } else { await promotionService.createPromotion(submitData); toast.success('Promotion added successfully'); } setShowPromotionModal(false); resetPromotionForm(); fetchPromotions(); } catch (error: any) { toast.error(error.response?.data?.message || 'An error occurred'); } }; const handleEditPromotion = (promotion: Promotion) => { setEditingPromotion(promotion); setPromotionFormData({ code: promotion.code, name: promotion.name, description: promotion.description || '', discount_type: promotion.discount_type, discount_value: promotion.discount_value, min_booking_amount: promotion.min_booking_amount || 0, max_discount_amount: promotion.max_discount_amount || 0, min_stay_days: promotion.min_stay_days || 0, max_stay_days: promotion.max_stay_days || 0, advance_booking_days: promotion.advance_booking_days || 0, max_advance_booking_days: promotion.max_advance_booking_days || 0, allowed_check_in_days: promotion.allowed_check_in_days || [], allowed_check_out_days: promotion.allowed_check_out_days || [], allowed_room_type_ids: promotion.allowed_room_type_ids || [], excluded_room_type_ids: promotion.excluded_room_type_ids || [], min_guests: promotion.min_guests || 0, max_guests: promotion.max_guests || 0, first_time_customer_only: promotion.first_time_customer_only || false, repeat_customer_only: promotion.repeat_customer_only || false, blackout_dates: promotion.blackout_dates || [], start_date: promotion.start_date?.split('T')[0] || '', end_date: promotion.end_date?.split('T')[0] || '', usage_limit: promotion.usage_limit || 0, status: promotion.status, }); setShowPromotionModal(true); }; const handleDeletePromotion = async (id: number) => { if (!window.confirm('Are you sure you want to delete this promotion?')) return; try { await promotionService.deletePromotion(id); toast.success('Promotion deleted successfully'); fetchPromotions(); } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to delete promotion'); } }; const resetPromotionForm = () => { setEditingPromotion(null); setPromotionFormData({ code: '', name: '', description: '', discount_type: 'percentage', discount_value: 0, min_booking_amount: 0, max_discount_amount: 0, min_stay_days: 0, max_stay_days: 0, advance_booking_days: 0, max_advance_booking_days: 0, allowed_check_in_days: [], allowed_check_out_days: [], allowed_room_type_ids: [], excluded_room_type_ids: [], min_guests: 0, max_guests: 0, first_time_customer_only: false, repeat_customer_only: false, blackout_dates: [], start_date: '', end_date: '', usage_limit: 0, status: 'active', }); }; const getPromotionStatusBadge = (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' }, 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} ); }; const tabs = [ { id: 'overview' as BusinessTab, label: 'Overview', icon: FileText }, { id: 'invoices' as BusinessTab, label: 'Invoices', icon: FileText }, { id: 'payments' as BusinessTab, label: 'Payments', icon: CreditCard }, { id: 'promotions' as BusinessTab, label: 'Promotions', icon: Tag }, ]; return (
{}

Business Dashboard

Manage invoices, payments, and promotional campaigns

{}
{tabs.map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
{} {activeTab === 'overview' && (
setActiveTab('invoices')} className="group relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-blue-100/50 p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-105 hover:border-blue-300/60 overflow-hidden" >

Invoices

Manage and track all invoices and billing

View Invoices
setActiveTab('payments')} className="group relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-emerald-100/50 p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-105 hover:border-emerald-300/60 overflow-hidden" >

Payments

Track payment transactions and revenue

View Payments
setActiveTab('promotions')} className="group relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-purple-100/50 p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-105 hover:border-purple-300/60 overflow-hidden" >

Promotions

Manage discount codes and campaigns

View Promotions
)} {} {activeTab === 'invoices' && (
{}

Invoice Management

Manage and track all invoices and billing information

{}

Filters

setInvoiceFilters({ ...invoiceFilters, search: e.target.value })} className="w-full pl-12 pr-4 py-3.5 bg-white border-2 border-gray-200 rounded-xl focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100 transition-all duration-200 text-gray-700 placeholder-gray-400 font-medium shadow-sm hover:shadow-md" />
{invoicesTotalItems} invoice{invoicesTotalItems !== 1 ? 's' : ''}
{} {invoicesLoading && invoices.length === 0 ? ( ) : (
{invoices.length > 0 ? ( invoices.map((invoice) => { const statusBadge = getInvoiceStatusBadge(invoice.status); return ( ); }) ) : ( )}
Invoice # Customer Booking Amount Status Due Date Actions
{invoice.invoice_number}
{invoice.customer_name}
{invoice.customer_email}
#{invoice.booking_id}
{formatCurrency(invoice.total_amount)}
{invoice.balance_due > 0 && (
Due: {formatCurrency(invoice.balance_due)}
)}
{statusBadge.label} {formatDate(invoice.due_date, 'short')}
{invoicesTotalPages > 1 && (
)}
)}
)} {} {activeTab === 'payments' && (
{}

Payment Management

Track payment transactions and revenue streams

{}

Filters

setPaymentFilters({ ...paymentFilters, search: e.target.value })} className="w-full pl-12 pr-4 py-3.5 bg-white border-2 border-gray-200 rounded-xl focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100 transition-all duration-200 text-gray-700 placeholder-gray-400 font-medium shadow-sm hover:shadow-md" />
setPaymentFilters({ ...paymentFilters, from: e.target.value })} className="px-4 py-3.5 bg-white border-2 border-gray-200 rounded-xl focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100 transition-all duration-200 text-gray-700 font-medium shadow-sm hover:shadow-md" placeholder="From date" /> setPaymentFilters({ ...paymentFilters, to: e.target.value })} className="px-4 py-3.5 bg-white border-2 border-gray-200 rounded-xl focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100 transition-all duration-200 text-gray-700 font-medium shadow-sm hover:shadow-md" placeholder="To date" />
{} {paymentsLoading && payments.length === 0 ? ( ) : ( <>
{payments.map((payment) => ( ))}
Transaction ID Booking Number Customer Method Type Status Amount Payment Date
{payment.transaction_id || `PAY-${payment.id}`}
{payment.booking?.booking_number}
{payment.booking?.user?.name || 'N/A'}
{getPaymentMethodBadge(payment.payment_method)} {payment.payment_type === 'deposit' ? ( Deposit (20%) ) : payment.payment_type === 'remaining' ? ( Remaining ) : ( Full Payment )} {getPaymentStatusBadge(payment.payment_status)}
{formatCurrency(payment.amount)}
{new Date(payment.payment_date || payment.createdAt || '').toLocaleDateString('en-US')}
{}

Total Revenue

{formatCurrency(payments .filter(p => p.payment_status === 'completed') .reduce((sum, p) => sum + p.amount, 0))}

Total {payments.filter(p => p.payment_status === 'completed').length} paid transaction{payments.filter(p => p.payment_status === 'completed').length !== 1 ? 's' : ''}

{payments.filter(p => p.payment_status === 'completed').length}
)}
)} {} {activeTab === 'promotions' && (
{}

Promotion Management

Manage discount codes and promotional campaigns

{}

Filters

setPromotionFilters({ ...promotionFilters, search: e.target.value })} className="w-full pl-12 pr-4 py-3.5 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700 placeholder-gray-400 font-medium shadow-sm hover:shadow-md" />
{} {promotionsLoading && promotions.length === 0 ? ( ) : (
{promotions.map((promotion) => ( ))}
Code Program Name Value Period Used Status Actions
{promotion.code}
{promotion.name}
{promotion.description}
{promotion.discount_type === 'percentage' ? `${promotion.discount_value}%` : formatCurrency(promotion.discount_value)}
{promotion.start_date ? new Date(promotion.start_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : 'N/A'} {promotion.end_date ? new Date(promotion.end_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : 'N/A'}
{promotion.used_count || 0} / {promotion.usage_limit || '∞'}
{getPromotionStatusBadge(promotion.status)}
)}
)}
{} {showPromotionModal && (

{editingPromotion ? 'Update Promotion' : 'Add New Promotion'}

{editingPromotion ? 'Modify promotion details' : 'Create a new promotion program'}

setPromotionFormData({ ...promotionFormData, code: e.target.value.toUpperCase() })} className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700 font-medium shadow-sm font-mono" placeholder="e.g: SUMMER2024" required />
setPromotionFormData({ ...promotionFormData, name: e.target.value })} className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 text-gray-700 font-medium shadow-sm" placeholder="e.g: Summer Sale" required />