fix data flow issues
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
@@ -10,6 +10,9 @@ import StatusBadge from '@/components/ui/StatusBadge';
|
||||
import InfoBanner from '@/components/ui/InfoBanner';
|
||||
import PageHeader from '@/components/ui/PageHeader';
|
||||
import { siteConfig } from '@/lib/siteConfig';
|
||||
import { BillingViewModel } from '@/lib/view-models/BillingViewModel';
|
||||
import { SponsorService } from '@/lib/services/sponsors/SponsorService';
|
||||
import { ServiceFactory } from '@/lib/services/ServiceFactory';
|
||||
import {
|
||||
CreditCard,
|
||||
DollarSign,
|
||||
@@ -73,108 +76,17 @@ interface BillingStats {
|
||||
// Mock Data
|
||||
// ============================================================================
|
||||
|
||||
const MOCK_PAYMENT_METHODS: PaymentMethod[] = [
|
||||
{
|
||||
id: 'pm-1',
|
||||
type: 'card',
|
||||
last4: '4242',
|
||||
brand: 'Visa',
|
||||
isDefault: true,
|
||||
expiryMonth: 12,
|
||||
expiryYear: 2027,
|
||||
},
|
||||
{
|
||||
id: 'pm-2',
|
||||
type: 'card',
|
||||
last4: '5555',
|
||||
brand: 'Mastercard',
|
||||
isDefault: false,
|
||||
expiryMonth: 6,
|
||||
expiryYear: 2026,
|
||||
},
|
||||
{
|
||||
id: 'pm-3',
|
||||
type: 'sepa',
|
||||
last4: '8901',
|
||||
bankName: 'Deutsche Bank',
|
||||
isDefault: false,
|
||||
},
|
||||
];
|
||||
|
||||
const MOCK_INVOICES: Invoice[] = [
|
||||
{
|
||||
id: 'inv-1',
|
||||
invoiceNumber: 'GP-2025-001234',
|
||||
date: new Date('2025-11-01'),
|
||||
dueDate: new Date('2025-11-15'),
|
||||
amount: 1090.91,
|
||||
vatAmount: 207.27,
|
||||
totalAmount: 1298.18,
|
||||
status: 'paid',
|
||||
description: 'GT3 Pro Championship - Primary Sponsor (Q4 2025)',
|
||||
sponsorshipType: 'league',
|
||||
pdfUrl: '#',
|
||||
},
|
||||
{
|
||||
id: 'inv-2',
|
||||
invoiceNumber: 'GP-2025-001235',
|
||||
date: new Date('2025-10-01'),
|
||||
dueDate: new Date('2025-10-15'),
|
||||
amount: 363.64,
|
||||
vatAmount: 69.09,
|
||||
totalAmount: 432.73,
|
||||
status: 'paid',
|
||||
description: 'Team Velocity - Gear Sponsor (Q4 2025)',
|
||||
sponsorshipType: 'team',
|
||||
pdfUrl: '#',
|
||||
},
|
||||
{
|
||||
id: 'inv-3',
|
||||
invoiceNumber: 'GP-2025-001236',
|
||||
date: new Date('2025-12-01'),
|
||||
dueDate: new Date('2025-12-15'),
|
||||
amount: 318.18,
|
||||
vatAmount: 60.45,
|
||||
totalAmount: 378.63,
|
||||
status: 'pending',
|
||||
description: 'Alex Thompson - Driver Sponsorship (Dec 2025)',
|
||||
sponsorshipType: 'driver',
|
||||
pdfUrl: '#',
|
||||
},
|
||||
{
|
||||
id: 'inv-4',
|
||||
invoiceNumber: 'GP-2025-001237',
|
||||
date: new Date('2025-11-15'),
|
||||
dueDate: new Date('2025-11-29'),
|
||||
amount: 454.55,
|
||||
vatAmount: 86.36,
|
||||
totalAmount: 540.91,
|
||||
status: 'overdue',
|
||||
description: 'Touring Car Cup - Secondary Sponsor (Q1 2026)',
|
||||
sponsorshipType: 'league',
|
||||
pdfUrl: '#',
|
||||
},
|
||||
];
|
||||
|
||||
const MOCK_STATS: BillingStats = {
|
||||
totalSpent: 12450,
|
||||
pendingAmount: 919.54,
|
||||
nextPaymentDate: new Date('2025-12-15'),
|
||||
nextPaymentAmount: 378.63,
|
||||
activeSponsorships: 6,
|
||||
averageMonthlySpend: 2075,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Components
|
||||
// ============================================================================
|
||||
|
||||
function PaymentMethodCard({
|
||||
method,
|
||||
method,
|
||||
onSetDefault,
|
||||
onRemove
|
||||
}: {
|
||||
method: PaymentMethod;
|
||||
onRemove
|
||||
}: {
|
||||
method: any;
|
||||
onSetDefault: () => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
@@ -214,16 +126,16 @@ function PaymentMethodCard({
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-white">{getLabel()}</span>
|
||||
<span className="font-medium text-white">{method.displayLabel}</span>
|
||||
{method.isDefault && (
|
||||
<span className="px-2 py-0.5 rounded-full text-xs bg-primary-blue/20 text-primary-blue font-medium">
|
||||
Default
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{method.expiryMonth && method.expiryYear && (
|
||||
{method.expiryDisplay && (
|
||||
<span className="text-sm text-gray-500">
|
||||
Expires {String(method.expiryMonth).padStart(2, '0')}/{method.expiryYear}
|
||||
Expires {method.expiryDisplay}
|
||||
</span>
|
||||
)}
|
||||
{method.type === 'sepa' && (
|
||||
@@ -246,7 +158,7 @@ function PaymentMethodCard({
|
||||
);
|
||||
}
|
||||
|
||||
function InvoiceRow({ invoice, index }: { invoice: Invoice; index: number }) {
|
||||
function InvoiceRow({ invoice, index }: { invoice: any; index: number }) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const statusConfig = {
|
||||
@@ -313,7 +225,7 @@ function InvoiceRow({ invoice, index }: { invoice: Invoice; index: number }) {
|
||||
<span>{invoice.invoiceNumber}</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{invoice.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
{invoice.formattedDate}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -322,13 +234,13 @@ function InvoiceRow({ invoice, index }: { invoice: Invoice; index: number }) {
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right">
|
||||
<div className="font-semibold text-white">
|
||||
€{invoice.totalAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}
|
||||
{invoice.formattedTotalAmount}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
incl. €{invoice.vatAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })} VAT
|
||||
incl. {invoice.formattedVatAmount} VAT
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${status.bg} ${status.color} border ${status.border}`}>
|
||||
<StatusIcon className="w-3 h-3" />
|
||||
{status.label}
|
||||
@@ -349,9 +261,49 @@ function InvoiceRow({ invoice, index }: { invoice: Invoice; index: number }) {
|
||||
|
||||
export default function SponsorBillingPage() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const [paymentMethods, setPaymentMethods] = useState(MOCK_PAYMENT_METHODS);
|
||||
const [data, setData] = useState<BillingViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showAllInvoices, setShowAllInvoices] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadBilling = async () => {
|
||||
try {
|
||||
const sponsorService = ServiceFactory.getSponsorService();
|
||||
const billingData = await sponsorService.getBilling('demo-sponsor-1');
|
||||
setData(new BillingViewModel(billingData));
|
||||
} catch (err) {
|
||||
console.error('Error loading billing data:', err);
|
||||
setError('Failed to load billing data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadBilling();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<div className="w-8 h-8 border-2 border-primary-blue border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-400">Loading billing data...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<p className="text-gray-400">{error || 'No billing data available'}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSetDefault = (methodId: string) => {
|
||||
setPaymentMethods(methods =>
|
||||
methods.map(m => ({ ...m, isDefault: m.id === methodId }))
|
||||
@@ -364,7 +316,17 @@ export default function SponsorBillingPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const displayedInvoices = showAllInvoices ? MOCK_INVOICES : MOCK_INVOICES.slice(0, 4);
|
||||
const handleSetDefault = (methodId: string) => {
|
||||
// In a real app, this would call an API
|
||||
console.log('Setting default payment method:', methodId);
|
||||
};
|
||||
|
||||
const handleRemoveMethod = (methodId: string) => {
|
||||
if (confirm('Remove this payment method?')) {
|
||||
// In a real app, this would call an API
|
||||
console.log('Removing payment method:', methodId);
|
||||
}
|
||||
};
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
@@ -404,7 +366,7 @@ export default function SponsorBillingPage() {
|
||||
<StatCard
|
||||
icon={DollarSign}
|
||||
label="Total Spent"
|
||||
value={`€${MOCK_STATS.totalSpent.toLocaleString('de-DE')}`}
|
||||
value={data.stats.formattedTotalSpent}
|
||||
subValue="All time"
|
||||
color="text-performance-green"
|
||||
bgColor="bg-performance-green/10"
|
||||
@@ -412,23 +374,23 @@ export default function SponsorBillingPage() {
|
||||
<StatCard
|
||||
icon={AlertTriangle}
|
||||
label="Pending Payments"
|
||||
value={`€${MOCK_STATS.pendingAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`}
|
||||
subValue={`${MOCK_INVOICES.filter(i => i.status === 'pending' || i.status === 'overdue').length} invoices`}
|
||||
value={data.stats.formattedPendingAmount}
|
||||
subValue={`${data.invoices.filter(i => i.status === 'pending' || i.status === 'overdue').length} invoices`}
|
||||
color="text-warning-amber"
|
||||
bgColor="bg-warning-amber/10"
|
||||
/>
|
||||
<StatCard
|
||||
icon={Calendar}
|
||||
label="Next Payment"
|
||||
value={MOCK_STATS.nextPaymentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
subValue={`€${MOCK_STATS.nextPaymentAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`}
|
||||
value={data.stats.formattedNextPaymentDate}
|
||||
subValue={data.stats.formattedNextPaymentAmount}
|
||||
color="text-primary-blue"
|
||||
bgColor="bg-primary-blue/10"
|
||||
/>
|
||||
<StatCard
|
||||
icon={TrendingUp}
|
||||
label="Monthly Average"
|
||||
value={`€${MOCK_STATS.averageMonthlySpend.toLocaleString('de-DE')}`}
|
||||
value={data.stats.formattedAverageMonthlySpend}
|
||||
subValue="Last 6 months"
|
||||
color="text-gray-400"
|
||||
bgColor="bg-iron-gray"
|
||||
@@ -449,10 +411,10 @@ export default function SponsorBillingPage() {
|
||||
}
|
||||
/>
|
||||
<div className="p-5 space-y-3">
|
||||
{paymentMethods.map((method) => (
|
||||
<PaymentMethodCard
|
||||
key={method.id}
|
||||
method={method}
|
||||
{data.paymentMethods.map((method) => (
|
||||
<PaymentMethodCard
|
||||
key={method.id}
|
||||
method={method}
|
||||
onSetDefault={() => handleSetDefault(method.id)}
|
||||
onRemove={() => handleRemoveMethod(method.id)}
|
||||
/>
|
||||
@@ -482,18 +444,18 @@ export default function SponsorBillingPage() {
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
{displayedInvoices.map((invoice, index) => (
|
||||
{data.invoices.slice(0, showAllInvoices ? data.invoices.length : 4).map((invoice, index) => (
|
||||
<InvoiceRow key={invoice.id} invoice={invoice} index={index} />
|
||||
))}
|
||||
</div>
|
||||
{MOCK_INVOICES.length > 4 && (
|
||||
{data.invoices.length > 4 && (
|
||||
<div className="p-4 border-t border-charcoal-outline">
|
||||
<Button
|
||||
variant="secondary"
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={() => setShowAllInvoices(!showAllInvoices)}
|
||||
>
|
||||
{showAllInvoices ? 'Show Less' : `View All ${MOCK_INVOICES.length} Invoices`}
|
||||
{showAllInvoices ? 'Show Less' : `View All ${data.invoices.length} Invoices`}
|
||||
<ChevronRight className={`w-4 h-4 ml-2 transition-transform ${showAllInvoices ? 'rotate-90' : ''}`} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user