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

@@ -8,6 +8,9 @@ import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import StatusBadge from '@/components/ui/StatusBadge';
import InfoBanner from '@/components/ui/InfoBanner';
import { SponsorService } from '@/lib/services/sponsors/SponsorService';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import { SponsorSponsorshipsViewModel } from '@/lib/view-models/SponsorSponsorshipsViewModel';
import {
Megaphone,
Trophy,
@@ -69,241 +72,6 @@ interface Sponsorship {
// Mock Data - Updated to show application workflow
// ============================================================================
const MOCK_SPONSORSHIPS: Sponsorship[] = [
// Active sponsorships (approved and started)
{
id: 's1',
type: 'leagues',
entityId: 'l1',
entityName: 'GT3 Masters Championship',
tier: 'main',
status: 'active',
applicationDate: new Date('2025-09-15'),
approvalDate: new Date('2025-09-18'),
startDate: new Date('2025-10-01'),
endDate: new Date('2026-02-28'),
price: 1200,
impressions: 45200,
impressionsChange: 12.5,
engagement: 4.2,
details: '48 drivers • 12 races',
entityOwner: 'Championship Admin',
},
{
id: 's2',
type: 'leagues',
entityId: 'l2',
entityName: 'Endurance Pro Series',
tier: 'secondary',
status: 'active',
applicationDate: new Date('2025-10-20'),
approvalDate: new Date('2025-10-25'),
startDate: new Date('2025-11-01'),
endDate: new Date('2026-03-31'),
price: 500,
impressions: 38400,
impressionsChange: 8.3,
engagement: 3.8,
details: '72 drivers • 6 races',
entityOwner: 'Endurance Racing LLC',
},
{
id: 's3',
type: 'teams',
entityId: 't1',
entityName: 'Velocity Racing',
status: 'active',
applicationDate: new Date('2025-08-25'),
approvalDate: new Date('2025-08-26'),
startDate: new Date('2025-09-01'),
endDate: new Date('2026-08-31'),
price: 400,
impressions: 12300,
impressionsChange: 5.2,
engagement: 5.1,
details: '4 drivers • GT3 & LMP',
entityOwner: 'Team Principal',
},
// Pending approval (waiting for entity owner)
{
id: 's9',
type: 'leagues',
entityId: 'l3',
entityName: 'Formula Sim Series',
tier: 'main',
status: 'pending_approval',
applicationDate: new Date('2025-12-14'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-06-30'),
price: 1500,
impressions: 0,
details: '36 drivers • F3 class',
entityOwner: 'Formula Sim Organization',
applicationMessage: 'We would love to be your main sponsor for the upcoming season.',
},
{
id: 's10',
type: 'teams',
entityId: 't3',
entityName: 'Phoenix Racing Team',
status: 'pending_approval',
applicationDate: new Date('2025-12-12'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 600,
impressions: 0,
details: '5 drivers • Multi-class',
entityOwner: 'Phoenix Team Manager',
applicationMessage: 'Interested in sponsoring your team for the full 2026 season.',
},
{
id: 's11',
type: 'drivers',
entityId: 'd3',
entityName: 'James Rodriguez',
status: 'pending_approval',
applicationDate: new Date('2025-12-10'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 250,
impressions: 0,
details: 'Rising rookie • GT3 Masters',
entityOwner: 'James Rodriguez',
applicationMessage: 'Would like to support your racing career.',
},
// Recently approved (not yet started)
{
id: 's12',
type: 'races',
entityId: 'r1',
entityName: 'Spa 24 Hours',
status: 'approved',
applicationDate: new Date('2025-12-01'),
approvalDate: new Date('2025-12-05'),
startDate: new Date('2025-12-20'),
endDate: new Date('2025-12-21'),
price: 300,
impressions: 0,
details: 'Endurance Pro Series • Dec 20-21',
entityOwner: 'Race Director',
},
{
id: 's13',
type: 'drivers',
entityId: 'd4',
entityName: 'Emma Wilson',
status: 'approved',
applicationDate: new Date('2025-12-08'),
approvalDate: new Date('2025-12-10'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 180,
impressions: 0,
details: 'Touring Car specialist',
entityOwner: 'Emma Wilson',
},
// Rejected applications
{
id: 's14',
type: 'leagues',
entityId: 'l4',
entityName: 'Elite GT Championship',
tier: 'main',
status: 'rejected',
applicationDate: new Date('2025-11-20'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-06-30'),
price: 2000,
impressions: 0,
details: '24 drivers • Invite-only',
entityOwner: 'Elite Racing Committee',
rejectionReason: 'Main sponsor position already filled for the upcoming season.',
},
{
id: 's15',
type: 'teams',
entityId: 't4',
entityName: 'Apex Motorsport',
status: 'rejected',
applicationDate: new Date('2025-11-15'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 450,
impressions: 0,
details: '3 drivers • LMP2',
entityOwner: 'Apex Team Owner',
rejectionReason: 'Already have exclusive sponsor agreement in this category.',
},
// Existing active ones
{
id: 's4',
type: 'teams',
entityId: 't2',
entityName: 'Storm Motorsport',
status: 'active',
applicationDate: new Date('2025-10-01'),
approvalDate: new Date('2025-10-05'),
startDate: new Date('2025-10-15'),
endDate: new Date('2026-10-14'),
price: 350,
impressions: 8900,
impressionsChange: -2.1,
engagement: 4.5,
details: '3 drivers • Formula',
entityOwner: 'Storm Racing LLC',
},
{
id: 's5',
type: 'drivers',
entityId: 'd1',
entityName: 'Max Velocity',
status: 'active',
applicationDate: new Date('2025-10-20'),
approvalDate: new Date('2025-10-21'),
startDate: new Date('2025-11-01'),
endDate: new Date('2026-10-31'),
price: 200,
impressions: 8200,
impressionsChange: 15.8,
engagement: 6.2,
details: 'Velocity Racing • P1 in GT3 Masters',
entityOwner: 'Max Velocity',
},
{
id: 's6',
type: 'drivers',
entityId: 'd2',
entityName: 'Sarah Storm',
status: 'active',
applicationDate: new Date('2025-09-25'),
approvalDate: new Date('2025-09-26'),
startDate: new Date('2025-10-01'),
endDate: new Date('2026-09-30'),
price: 150,
impressions: 6100,
impressionsChange: 22.4,
engagement: 5.8,
details: 'Storm Motorsport • Rising star',
entityOwner: 'Sarah Storm',
},
{
id: 's8',
type: 'platform',
entityId: 'p1',
entityName: 'Homepage Banner',
status: 'active',
applicationDate: new Date('2025-11-25'),
approvalDate: new Date('2025-11-25'),
startDate: new Date('2025-12-01'),
endDate: new Date('2025-12-31'),
price: 500,
impressions: 52000,
impressionsChange: 3.4,
engagement: 2.1,
details: 'Header position • All pages',
entityOwner: 'GridPilot',
},
];
// ============================================================================
// Configuration
@@ -360,7 +128,7 @@ const STATUS_CONFIG = {
// Components
// ============================================================================
function SponsorshipCard({ sponsorship }: { sponsorship: Sponsorship }) {
function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
const router = useRouter();
const shouldReduceMotion = useReducedMotion();
@@ -492,7 +260,7 @@ function SponsorshipCard({ sponsorship }: { sponsorship: Sponsorship }) {
Impressions
</div>
<div className="flex items-center gap-2">
<span className="text-white font-semibold">{sponsorship.impressions.toLocaleString()}</span>
<span className="text-white font-semibold">{sponsorship.formattedImpressions}</span>
{sponsorship.impressionsChange !== undefined && sponsorship.impressionsChange !== 0 && (
<span className={`text-xs flex items-center ${
sponsorship.impressionsChange > 0 ? 'text-performance-green' : 'text-racing-red'
@@ -529,7 +297,7 @@ function SponsorshipCard({ sponsorship }: { sponsorship: Sponsorship }) {
<Trophy className="w-3 h-3" />
Investment
</div>
<div className="text-white font-semibold">${sponsorship.price}</div>
<div className="text-white font-semibold">{sponsorship.formattedPrice}</div>
</div>
</div>
)}
@@ -539,11 +307,11 @@ function SponsorshipCard({ sponsorship }: { sponsorship: Sponsorship }) {
<div className="flex items-center gap-4 mb-4 text-sm">
<div className="flex items-center gap-1 text-gray-400">
<Calendar className="w-3.5 h-3.5" />
{sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
{sponsorship.periodDisplay}
</div>
<div className="flex items-center gap-1 text-gray-400">
<Trophy className="w-3.5 h-3.5" />
${sponsorship.price}
{sponsorship.formattedPrice}
</div>
</div>
)}
@@ -597,20 +365,59 @@ export default function SponsorCampaignsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const shouldReduceMotion = useReducedMotion();
const initialType = (searchParams.get('type') as SponsorshipType) || 'all';
const [typeFilter, setTypeFilter] = useState<SponsorshipType>(initialType);
const [statusFilter, setStatusFilter] = useState<SponsorshipStatus>('all');
const [searchQuery, setSearchQuery] = useState('');
const [data, setData] = useState<SponsorSponsorshipsViewModel | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const timer = setTimeout(() => setLoading(false), 300);
return () => clearTimeout(timer);
const loadSponsorships = async () => {
try {
const sponsorService = ServiceFactory.getSponsorService();
const sponsorshipsData = await sponsorService.getSponsorSponsorships('demo-sponsor-1');
if (sponsorshipsData) {
setData(sponsorshipsData);
} else {
setError('Failed to load sponsorships data');
}
} catch (err) {
console.error('Error loading sponsorships:', err);
setError('Failed to load sponsorships data');
} finally {
setLoading(false);
}
};
loadSponsorships();
}, []);
if (loading) {
return (
<div className="max-w-7xl 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 sponsorships...</p>
</div>
</div>
);
}
if (error || !data) {
return (
<div className="max-w-7xl 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 sponsorships data available'}</p>
</div>
</div>
);
}
// Filter sponsorships
const filteredSponsorships = MOCK_SPONSORSHIPS.filter(s => {
const filteredSponsorships = data.sponsorships.filter(s => {
if (typeFilter !== 'all' && s.type !== typeFilter) return false;
if (statusFilter !== 'all' && s.status !== statusFilter) return false;
if (searchQuery && !s.entityName.toLowerCase().includes(searchQuery.toLowerCase())) return false;
@@ -619,22 +426,22 @@ export default function SponsorCampaignsPage() {
// Calculate stats
const stats = {
total: MOCK_SPONSORSHIPS.length,
active: MOCK_SPONSORSHIPS.filter(s => s.status === 'active').length,
pending: MOCK_SPONSORSHIPS.filter(s => s.status === 'pending_approval').length,
approved: MOCK_SPONSORSHIPS.filter(s => s.status === 'approved').length,
rejected: MOCK_SPONSORSHIPS.filter(s => s.status === 'rejected').length,
totalInvestment: MOCK_SPONSORSHIPS.filter(s => s.status === 'active').reduce((sum, s) => sum + s.price, 0),
totalImpressions: MOCK_SPONSORSHIPS.reduce((sum, s) => sum + s.impressions, 0),
total: data.sponsorships.length,
active: data.sponsorships.filter(s => s.status === 'active').length,
pending: data.sponsorships.filter(s => s.status === 'pending_approval').length,
approved: data.sponsorships.filter(s => s.status === 'approved').length,
rejected: data.sponsorships.filter(s => s.status === 'rejected').length,
totalInvestment: data.sponsorships.filter(s => s.status === 'active').reduce((sum, s) => sum + s.price, 0),
totalImpressions: data.sponsorships.reduce((sum, s) => sum + s.impressions, 0),
};
// Stats by type
const statsByType = {
leagues: MOCK_SPONSORSHIPS.filter(s => s.type === 'leagues').length,
teams: MOCK_SPONSORSHIPS.filter(s => s.type === 'teams').length,
drivers: MOCK_SPONSORSHIPS.filter(s => s.type === 'drivers').length,
races: MOCK_SPONSORSHIPS.filter(s => s.type === 'races').length,
platform: MOCK_SPONSORSHIPS.filter(s => s.type === 'platform').length,
leagues: data.sponsorships.filter(s => s.type === 'leagues').length,
teams: data.sponsorships.filter(s => s.type === 'teams').length,
drivers: data.sponsorships.filter(s => s.type === 'drivers').length,
races: data.sponsorships.filter(s => s.type === 'races').length,
platform: data.sponsorships.filter(s => s.type === 'platform').length,
};
if (loading) {
@@ -796,9 +603,9 @@ export default function SponsorCampaignsPage() {
const config = status === 'all'
? { label: 'All', color: 'text-gray-400' }
: STATUS_CONFIG[status];
const count = status === 'all'
? stats.total
: MOCK_SPONSORSHIPS.filter(s => s.status === status).length;
const count = status === 'all'
? stats.total
: data.sponsorships.filter(s => s.status === status).length;
return (
<button
key={status}