fix data flow issues

This commit is contained in:
2025-12-19 23:18:53 +01:00
parent ec177a75ce
commit 5c74837d73
45 changed files with 2726 additions and 746 deletions

View File

@@ -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>