This commit is contained in:
Iliyan Angelov
2025-11-20 02:18:52 +02:00
parent 34b4c969d4
commit 44e11520c5
55 changed files with 4741 additions and 876 deletions

View File

@@ -214,9 +214,37 @@ const BookingManagementPage: React.FC = () => {
</div>
</td>
<td className="px-8 py-5 whitespace-nowrap">
<div className="text-sm font-bold text-slate-900 bg-gradient-to-r from-amber-600 to-amber-700 bg-clip-text text-transparent">
{formatCurrency(booking.total_price)}
</div>
{(() => {
const completedPayments = booking.payments?.filter(
(p) => p.payment_status === 'completed'
) || [];
const amountPaid = completedPayments.reduce(
(sum, p) => sum + (p.amount || 0),
0
);
const remainingDue = booking.total_price - amountPaid;
const hasPayments = completedPayments.length > 0;
return (
<div>
<div className="text-sm font-bold text-slate-900 bg-gradient-to-r from-amber-600 to-amber-700 bg-clip-text text-transparent">
{formatCurrency(booking.total_price)}
</div>
{hasPayments && (
<div className="text-xs mt-1">
<div className="text-green-600 font-medium">
Paid: {formatCurrency(amountPaid)}
</div>
{remainingDue > 0 && (
<div className="text-amber-600 font-medium">
Due: {formatCurrency(remainingDue)}
</div>
)}
</div>
)}
</div>
);
})()}
</td>
<td className="px-8 py-5 whitespace-nowrap">
{getStatusBadge(booking.status)}
@@ -369,12 +397,219 @@ const BookingManagementPage: React.FC = () => {
<p className="text-lg font-semibold text-slate-900">{selectedBooking.guest_count} guest{selectedBooking.guest_count !== 1 ? 's' : ''}</p>
</div>
{/* Total Price - Highlighted */}
<div className="bg-gradient-to-br from-amber-50 via-yellow-50 to-amber-50 p-6 rounded-xl border-2 border-amber-200 shadow-lg">
<label className="text-xs font-semibold text-amber-700 uppercase tracking-wider mb-2 block">Total Price</label>
<p className="text-4xl font-bold bg-gradient-to-r from-amber-600 via-amber-700 to-amber-600 bg-clip-text text-transparent">
{formatCurrency(selectedBooking.total_price)}
</p>
{/* Payment Method & Status */}
<div className="bg-gradient-to-br from-indigo-50/50 to-purple-50/50 p-6 rounded-xl border border-indigo-100">
<label className="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-4 block flex items-center gap-2">
<div className="w-1 h-4 bg-gradient-to-b from-indigo-400 to-indigo-600 rounded-full"></div>
Payment Information
</label>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-xs text-slate-500 mb-1">Payment Method</p>
<p className="text-base font-semibold text-slate-900">
{selectedBooking.payment_method === 'cash'
? '💵 Pay at Hotel'
: selectedBooking.payment_method === 'stripe'
? '💳 Stripe (Card)'
: selectedBooking.payment_method === 'paypal'
? '💳 PayPal'
: selectedBooking.payment_method || 'N/A'}
</p>
</div>
<div>
<p className="text-xs text-slate-500 mb-1">Payment Status</p>
<p className={`text-base font-semibold ${
selectedBooking.payment_status === 'paid' || selectedBooking.payment_status === 'completed'
? 'text-green-600'
: selectedBooking.payment_status === 'pending'
? 'text-yellow-600'
: 'text-red-600'
}`}>
{selectedBooking.payment_status === 'paid' || selectedBooking.payment_status === 'completed'
? '✅ Paid'
: selectedBooking.payment_status === 'pending'
? '⏳ Pending'
: selectedBooking.payment_status === 'failed'
? '❌ Failed'
: selectedBooking.payment_status || 'Unpaid'}
</p>
</div>
</div>
</div>
{/* Service Usages */}
{selectedBooking.service_usages && selectedBooking.service_usages.length > 0 && (
<div className="bg-gradient-to-br from-purple-50/50 to-pink-50/50 p-6 rounded-xl border border-purple-100">
<label className="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-4 block flex items-center gap-2">
<div className="w-1 h-4 bg-gradient-to-b from-purple-400 to-purple-600 rounded-full"></div>
Additional Services
</label>
<div className="space-y-2">
{selectedBooking.service_usages.map((service: any, idx: number) => (
<div key={service.id || idx} className="flex justify-between items-center py-2 border-b border-purple-100 last:border-0">
<div>
<p className="text-sm font-medium text-slate-900">{service.service_name || service.name || 'Service'}</p>
<p className="text-xs text-slate-500">
{formatCurrency(service.unit_price || service.price || 0)} × {service.quantity || 1}
</p>
</div>
<p className="text-sm font-semibold text-slate-900">
{formatCurrency(service.total_price || (service.unit_price || service.price || 0) * (service.quantity || 1))}
</p>
</div>
))}
</div>
</div>
)}
{/* Payment Breakdown */}
{(() => {
const completedPayments = selectedBooking.payments?.filter(
(p) => p.payment_status === 'completed'
) || [];
const allPayments = selectedBooking.payments || [];
const amountPaid = completedPayments.reduce(
(sum, p) => sum + (p.amount || 0),
0
);
const remainingDue = selectedBooking.total_price - amountPaid;
const hasPayments = allPayments.length > 0;
return (
<>
{hasPayments && (
<div className="bg-gradient-to-br from-teal-50/50 to-cyan-50/50 p-6 rounded-xl border border-teal-100">
<label className="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-4 block flex items-center gap-2">
<div className="w-1 h-4 bg-gradient-to-b from-teal-400 to-teal-600 rounded-full"></div>
Payment History
</label>
<div className="space-y-3">
{allPayments.map((payment: any, idx: number) => (
<div key={payment.id || idx} className="p-3 bg-white rounded-lg border border-teal-100">
<div className="flex justify-between items-start mb-2">
<div>
<p className="text-sm font-semibold text-slate-900">
{formatCurrency(payment.amount || 0)}
</p>
<p className="text-xs text-slate-500 mt-1">
{payment.payment_type === 'deposit' ? 'Deposit (20%)' : payment.payment_type === 'remaining' ? 'Remaining Payment' : 'Full Payment'}
{' • '}
{payment.payment_method === 'stripe' ? 'Stripe' : payment.payment_method === 'paypal' ? 'PayPal' : payment.payment_method || 'Cash'}
</p>
</div>
<span className={`text-xs px-2 py-1 rounded-full font-medium ${
payment.payment_status === 'completed' || payment.payment_status === 'paid'
? 'bg-green-100 text-green-700'
: payment.payment_status === 'pending'
? 'bg-yellow-100 text-yellow-700'
: 'bg-red-100 text-red-700'
}`}>
{payment.payment_status === 'completed' || payment.payment_status === 'paid' ? 'Paid' : payment.payment_status || 'Pending'}
</span>
</div>
{payment.transaction_id && (
<p className="text-xs text-slate-400 font-mono">ID: {payment.transaction_id}</p>
)}
{payment.payment_date && (
<p className="text-xs text-slate-400 mt-1">
{new Date(payment.payment_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
</p>
)}
</div>
))}
</div>
</div>
)}
{/* Payment Summary - Always show, even if no payments */}
<div className="bg-gradient-to-br from-green-50 via-emerald-50 to-green-50 p-6 rounded-xl border-2 border-green-200 shadow-lg mb-4">
<label className="text-xs font-semibold text-green-700 uppercase tracking-wider mb-2 block">Amount Paid</label>
<p className="text-3xl font-bold bg-gradient-to-r from-green-600 via-emerald-700 to-green-600 bg-clip-text text-transparent">
{formatCurrency(amountPaid)}
</p>
{hasPayments && completedPayments.length > 0 && (
<p className="text-xs text-green-600 mt-2">
{completedPayments.length} payment{completedPayments.length !== 1 ? 's' : ''} completed
{amountPaid > 0 && selectedBooking.total_price > 0 && (
<span className="ml-2">
({((amountPaid / selectedBooking.total_price) * 100).toFixed(0)}% of total)
</span>
)}
</p>
)}
{amountPaid === 0 && !hasPayments && (
<p className="text-sm text-gray-500 mt-2">No payments made yet</p>
)}
</div>
{/* Remaining Due - Show prominently if there's remaining balance */}
{remainingDue > 0 && (
<div className="bg-gradient-to-br from-amber-50 via-yellow-50 to-amber-50 p-6 rounded-xl border-2 border-amber-200 shadow-lg mb-4">
<label className="text-xs font-semibold text-amber-700 uppercase tracking-wider mb-2 block">Remaining Due (To be paid)</label>
<p className="text-3xl font-bold text-amber-600">
{formatCurrency(remainingDue)}
</p>
{selectedBooking.total_price > 0 && (
<p className="text-xs text-amber-600 mt-2">
({((remainingDue / selectedBooking.total_price) * 100).toFixed(0)}% of total)
</p>
)}
</div>
)}
{/* Total Booking Price - Show as reference */}
<div className="bg-gradient-to-br from-slate-50 to-gray-50 p-6 rounded-xl border-2 border-slate-200 shadow-lg">
<label className="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-2 block">Total Booking Price</label>
<p className="text-2xl font-bold text-slate-700">
{formatCurrency(selectedBooking.total_price)}
</p>
<p className="text-xs text-slate-500 mt-2">
This is the total amount for the booking
</p>
</div>
</>
);
})()}
{/* Booking Metadata */}
<div className="bg-gradient-to-br from-slate-50 to-white p-6 rounded-xl border border-slate-200">
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-4 block flex items-center gap-2">
<div className="w-1 h-4 bg-gradient-to-b from-slate-400 to-slate-600 rounded-full"></div>
Booking Metadata
</label>
<div className="grid grid-cols-2 gap-4">
{selectedBooking.createdAt && (
<div>
<p className="text-xs text-slate-500 mb-1">Created At</p>
<p className="text-sm font-medium text-slate-900">
{new Date(selectedBooking.createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
</p>
</div>
)}
{selectedBooking.updatedAt && (
<div>
<p className="text-xs text-slate-500 mb-1">Last Updated</p>
<p className="text-sm font-medium text-slate-900">
{new Date(selectedBooking.updatedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
</p>
</div>
)}
{selectedBooking.requires_deposit !== undefined && (
<div>
<p className="text-xs text-slate-500 mb-1">Deposit Required</p>
<p className="text-sm font-medium text-slate-900">
{selectedBooking.requires_deposit ? 'Yes (20%)' : 'No'}
</p>
</div>
)}
{selectedBooking.deposit_paid !== undefined && (
<div>
<p className="text-xs text-slate-500 mb-1">Deposit Paid</p>
<p className={`text-sm font-medium ${selectedBooking.deposit_paid ? 'text-green-600' : 'text-amber-600'}`}>
{selectedBooking.deposit_paid ? '✅ Yes' : '❌ No'}
</p>
</div>
)}
</div>
</div>
{/* Notes */}