This commit is contained in:
Iliyan Angelov
2025-11-21 01:20:51 +02:00
parent a38ab4fa82
commit 6f85b8cf17
242 changed files with 7154 additions and 14492 deletions

View File

@@ -12,7 +12,6 @@ import {
Sparkles,
ChevronRight,
X,
Download
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
@@ -21,7 +20,8 @@ import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useCurrency } from '../../contexts/CurrencyContext';
import { useNavigate } from 'react-router-dom';
import { invoiceService, Invoice } from '../../services/api';
import { paymentService, Payment } from '../../services/api';
import { paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import { promotionService, Promotion } from '../../services/api';
import { formatDate } from '../../utils/format';
@@ -33,7 +33,7 @@ const BusinessDashboardPage: React.FC = () => {
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState<BusinessTab>('overview');
// Invoices State
const [invoices, setInvoices] = useState<Invoice[]>([]);
const [invoicesLoading, setInvoicesLoading] = useState(true);
const [invoiceFilters, setInvoiceFilters] = useState({
@@ -45,7 +45,7 @@ const BusinessDashboardPage: React.FC = () => {
const [invoicesTotalItems, setInvoicesTotalItems] = useState(0);
const invoicesPerPage = 10;
// Payments State
const [payments, setPayments] = useState<Payment[]>([]);
const [paymentsLoading, setPaymentsLoading] = useState(true);
const [paymentFilters, setPaymentFilters] = useState({
@@ -59,7 +59,7 @@ const BusinessDashboardPage: React.FC = () => {
const [paymentsTotalItems, setPaymentsTotalItems] = useState(0);
const paymentsPerPage = 5;
// Promotions State
const [promotions, setPromotions] = useState<Promotion[]>([]);
const [promotionsLoading, setPromotionsLoading] = useState(true);
const [showPromotionModal, setShowPromotionModal] = useState(false);
@@ -116,7 +116,7 @@ const BusinessDashboardPage: React.FC = () => {
}
}, [promotionFilters]);
// Invoices Functions
const fetchInvoices = async () => {
try {
setInvoicesLoading(true);
@@ -168,12 +168,12 @@ const BusinessDashboardPage: React.FC = () => {
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-slate-50 to-gray-50', text: 'text-slate-700', label: 'Cancelled', border: 'border-slate-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;
};
// Payments Functions
const fetchPayments = async () => {
try {
setPaymentsLoading(true);
@@ -210,7 +210,42 @@ const BusinessDashboardPage: React.FC = () => {
);
};
// Promotions Functions
const getPaymentStatusBadge = (status: string) => {
const statusConfig: Record<string, { bg: string; text: string; label: string; border: string }> = {
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 (
<span className={`px-3 py-1.5 rounded-full text-xs font-semibold border shadow-sm ${config.bg} ${config.text} ${config.border}`}>
{config.label}
</span>
);
};
const fetchPromotions = async () => {
try {
setPromotionsLoading(true);
@@ -320,7 +355,7 @@ const BusinessDashboardPage: React.FC = () => {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-10 animate-fade-in">
{/* Luxury Header */}
{}
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-emerald-400/5 via-transparent to-purple-600/5 rounded-3xl blur-3xl"></div>
<div className="relative bg-white/80 backdrop-blur-xl rounded-3xl shadow-2xl border border-emerald-200/30 p-8 md:p-10">
@@ -347,7 +382,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Premium Tab Navigation */}
{}
<div className="mt-10 pt-8 border-t border-gradient-to-r from-transparent via-emerald-200/30 to-transparent">
<div className="flex flex-wrap gap-3">
{tabs.map((tab) => {
@@ -383,7 +418,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Overview Tab */}
{}
{activeTab === 'overview' && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
<div
@@ -487,10 +522,10 @@ const BusinessDashboardPage: React.FC = () => {
</div>
)}
{/* Invoices Tab */}
{}
{activeTab === 'invoices' && (
<div className="space-y-8">
{/* Section Header */}
{}
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div className="space-y-3">
@@ -517,7 +552,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Filters */}
{}
<div className="relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 rounded-xl bg-gradient-to-br from-amber-500/10 to-amber-600/10 border border-amber-200/40">
@@ -546,7 +581,7 @@ const BusinessDashboardPage: React.FC = () => {
<option value="sent">Sent</option>
<option value="paid">Paid</option>
<option value="overdue">Overdue</option>
<option value="cancelled">Cancelled</option>
<option value="cancelled">Canceled</option>
</select>
<div className="flex items-center gap-3 px-4 py-3.5 bg-gradient-to-r from-gray-50 to-white border-2 border-gray-200 rounded-xl">
<Filter className="w-5 h-5 text-emerald-600" />
@@ -557,7 +592,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Invoices Table */}
{}
{invoicesLoading && invoices.length === 0 ? (
<Loading fullScreen text="Loading invoices..." />
) : (
@@ -664,10 +699,10 @@ const BusinessDashboardPage: React.FC = () => {
</div>
)}
{/* Payments Tab */}
{}
{activeTab === 'payments' && (
<div className="space-y-8">
{/* Section Header */}
{}
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<div className="space-y-3">
<div className="flex items-center gap-3">
@@ -682,7 +717,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Filters */}
{}
<div className="relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 rounded-xl bg-gradient-to-br from-amber-500/10 to-amber-600/10 border border-amber-200/40">
@@ -728,7 +763,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Payments Table */}
{}
{paymentsLoading && payments.length === 0 ? (
<Loading fullScreen text="Loading payments..." />
) : (
@@ -743,6 +778,7 @@ const BusinessDashboardPage: React.FC = () => {
<th className="px-8 py-5 text-left text-xs font-bold text-gray-700 uppercase tracking-wider border-b border-gray-200">Customer</th>
<th className="px-8 py-5 text-left text-xs font-bold text-gray-700 uppercase tracking-wider border-b border-gray-200">Method</th>
<th className="px-8 py-5 text-left text-xs font-bold text-gray-700 uppercase tracking-wider border-b border-gray-200">Type</th>
<th className="px-8 py-5 text-left text-xs font-bold text-gray-700 uppercase tracking-wider border-b border-gray-200">Status</th>
<th className="px-8 py-5 text-left text-xs font-bold text-gray-700 uppercase tracking-wider border-b border-gray-200">Amount</th>
<th className="px-8 py-5 text-left text-xs font-bold text-gray-700 uppercase tracking-wider border-b border-gray-200">Payment Date</th>
</tr>
@@ -758,7 +794,7 @@ const BusinessDashboardPage: React.FC = () => {
</td>
<td className="px-8 py-5 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
{payment.booking?.user?.name || payment.booking?.user?.full_name || 'N/A'}
{payment.booking?.user?.name || 'N/A'}
</div>
</td>
<td className="px-8 py-5 whitespace-nowrap">
@@ -779,6 +815,9 @@ const BusinessDashboardPage: React.FC = () => {
</span>
)}
</td>
<td className="px-8 py-5 whitespace-nowrap">
{getPaymentStatusBadge(payment.payment_status)}
</td>
<td className="px-8 py-5 whitespace-nowrap">
<div className="text-sm font-bold bg-gradient-to-r from-emerald-600 to-emerald-700 bg-clip-text text-transparent">
{formatCurrency(payment.amount)}
@@ -786,7 +825,7 @@ const BusinessDashboardPage: React.FC = () => {
</td>
<td className="px-8 py-5 whitespace-nowrap">
<div className="text-sm text-gray-600">
{new Date(payment.payment_date || payment.createdAt).toLocaleDateString('en-US')}
{new Date(payment.payment_date || payment.createdAt || '').toLocaleDateString('en-US')}
</div>
</td>
</tr>
@@ -803,18 +842,22 @@ const BusinessDashboardPage: React.FC = () => {
/>
</div>
{/* Summary Card */}
{}
<div className="bg-gradient-to-r from-emerald-500 via-emerald-600 to-purple-600 rounded-2xl shadow-2xl p-8 text-white">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold mb-2 text-emerald-100">Total Revenue</h3>
<p className="text-4xl font-bold">
{formatCurrency(payments.reduce((sum, p) => sum + p.amount, 0))}
{formatCurrency(payments
.filter(p => p.payment_status === 'completed')
.reduce((sum, p) => sum + p.amount, 0))}
</p>
<p className="text-sm mt-3 text-emerald-100/90">
Total {payments.filter(p => p.payment_status === 'completed').length} paid transaction{payments.filter(p => p.payment_status === 'completed').length !== 1 ? 's' : ''}
</p>
<p className="text-sm mt-3 text-emerald-100/90">Total {payments.length} transaction{payments.length !== 1 ? 's' : ''}</p>
</div>
<div className="bg-white/20 backdrop-blur-sm p-6 rounded-2xl">
<div className="text-5xl font-bold text-white/80">{payments.length}</div>
<div className="text-5xl font-bold text-white/80">{payments.filter(p => p.payment_status === 'completed').length}</div>
</div>
</div>
</div>
@@ -823,10 +866,10 @@ const BusinessDashboardPage: React.FC = () => {
</div>
)}
{/* Promotions Tab */}
{}
{activeTab === 'promotions' && (
<div className="space-y-8">
{/* Section Header */}
{}
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div className="space-y-3">
@@ -856,7 +899,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Filters */}
{}
<div className="relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 rounded-xl bg-gradient-to-br from-amber-500/10 to-amber-600/10 border border-amber-200/40">
@@ -896,7 +939,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Promotions Table */}
{}
{promotionsLoading && promotions.length === 0 ? (
<Loading fullScreen text="Loading promotions..." />
) : (
@@ -986,11 +1029,11 @@ const BusinessDashboardPage: React.FC = () => {
</div>
)}
{/* Promotion Modal */}
{}
{showPromotionModal && (
<div className="fixed inset-0 bg-black/70 backdrop-blur-md flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden border border-gray-200">
{/* Modal Header */}
{}
<div className="bg-gradient-to-r from-slate-900 via-slate-800 to-slate-900 px-8 py-6 border-b border-slate-700">
<div className="flex justify-between items-center">
<div>
@@ -1010,7 +1053,7 @@ const BusinessDashboardPage: React.FC = () => {
</div>
</div>
{/* Modal Content */}
{}
<div className="p-8 overflow-y-auto max-h-[calc(90vh-120px)] custom-scrollbar">
<form onSubmit={handlePromotionSubmit} className="space-y-6">
<div className="grid grid-cols-2 gap-6">