error and load state

This commit is contained in:
2026-01-06 11:05:16 +01:00
parent 4a1bfa57a3
commit 6aad7897db
29 changed files with 5172 additions and 1462 deletions

View File

@@ -1,33 +1,88 @@
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import LeaderboardsInteractive from './LeaderboardsInteractive';
'use client';
import { useRouter } from 'next/navigation';
import LeaderboardsTemplate from '@/templates/LeaderboardsTemplate';
import type { DriverLeaderboardItemViewModel } from '@/lib/view-models/DriverLeaderboardItemViewModel';
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
// ============================================================================
// SERVER COMPONENT - Fetches data and passes to Interactive wrapper
// ============================================================================
// Shared state components
import { useDataFetching } from '@/components/shared/hooks/useDataFetching';
import { StateContainer } from '@/components/shared/state/StateContainer';
import { useServices } from '@/lib/services/ServiceProvider';
import { Trophy } from 'lucide-react';
export default async function LeaderboardsStatic() {
// Create services for server-side data fetching
const serviceFactory = new ServiceFactory(getWebsiteApiBaseUrl());
const driverService = serviceFactory.createDriverService();
const teamService = serviceFactory.createTeamService();
export default function LeaderboardsStatic() {
const router = useRouter();
const { driverService, teamService } = useServices();
// Fetch data server-side
let drivers: DriverLeaderboardItemViewModel[] = [];
let teams: TeamSummaryViewModel[] = [];
const { data: driverData, isLoading: driversLoading, error: driversError, retry: driversRetry } = useDataFetching({
queryKey: ['driverLeaderboard'],
queryFn: () => driverService.getDriverLeaderboard(),
});
try {
const driversViewModel = await driverService.getDriverLeaderboard();
drivers = driversViewModel.drivers;
teams = await teamService.getAllTeams();
} catch (error) {
console.error('Failed to load leaderboard data:', error);
drivers = [];
teams = [];
}
const { data: teams, isLoading: teamsLoading, error: teamsError, retry: teamsRetry } = useDataFetching({
queryKey: ['allTeams'],
queryFn: () => teamService.getAllTeams(),
});
// Pass data to Interactive wrapper which handles client-side interactions
return <LeaderboardsInteractive drivers={drivers} teams={teams} />;
const handleDriverClick = (driverId: string) => {
router.push(`/drivers/${driverId}`);
};
const handleTeamClick = (teamId: string) => {
router.push(`/teams/${teamId}`);
};
const handleNavigateToDrivers = () => {
router.push('/leaderboards/drivers');
};
const handleNavigateToTeams = () => {
router.push('/teams/leaderboard');
};
// Combine loading states
const isLoading = driversLoading || teamsLoading;
// Combine errors (prioritize drivers error)
const error = driversError || teamsError;
// Combine retry functions
const retry = async () => {
if (driversError) await driversRetry();
if (teamsError) await teamsRetry();
};
// Prepare data for template
const drivers = driverData?.drivers || [];
const teamsData = teams || [];
const hasData = drivers.length > 0 || teamsData.length > 0;
return (
<StateContainer
data={hasData ? { drivers, teams: teamsData } : null}
isLoading={isLoading}
error={error}
retry={retry}
config={{
loading: { variant: 'spinner', message: 'Loading leaderboards...' },
error: { variant: 'full-screen' },
empty: {
icon: Trophy,
title: 'No leaderboard data',
description: 'There is no leaderboard data available at the moment.',
}
}}
isEmpty={(data) => !data || (data.drivers.length === 0 && data.teams.length === 0)}
>
{(data) => (
<LeaderboardsTemplate
drivers={data.drivers}
teams={data.teams}
onDriverClick={handleDriverClick}
onTeamClick={handleTeamClick}
onNavigateToDrivers={handleNavigateToDrivers}
onNavigateToTeams={handleNavigateToTeams}
/>
)}
</StateContainer>
);
}

View File

@@ -1,28 +1,75 @@
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import DriverRankingsInteractive from './DriverRankingsInteractive';
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import DriverRankingsTemplate from '@/templates/DriverRankingsTemplate';
import type { DriverLeaderboardItemViewModel } from '@/lib/view-models/DriverLeaderboardItemViewModel';
// ============================================================================
// SERVER COMPONENT - Fetches data and passes to Interactive wrapper
// ============================================================================
// Shared state components
import { useDataFetching } from '@/components/shared/hooks/useDataFetching';
import { StateContainer } from '@/components/shared/state/StateContainer';
import { useServices } from '@/lib/services/ServiceProvider';
import { Users } from 'lucide-react';
export default async function DriverRankingsStatic() {
// Create services for server-side data fetching
const serviceFactory = new ServiceFactory(getWebsiteApiBaseUrl());
const driverService = serviceFactory.createDriverService();
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
type SortBy = 'rank' | 'rating' | 'wins' | 'podiums' | 'winRate';
// Fetch data server-side
let drivers: DriverLeaderboardItemViewModel[] = [];
export default function DriverRankingsStatic() {
const router = useRouter();
const { driverService } = useServices();
const [searchQuery, setSearchQuery] = useState('');
const [selectedSkill, setSelectedSkill] = useState<'all' | SkillLevel>('all');
const [sortBy, setSortBy] = useState<SortBy>('rank');
const [showFilters, setShowFilters] = useState(false);
try {
const driversViewModel = await driverService.getDriverLeaderboard();
drivers = driversViewModel.drivers;
} catch (error) {
console.error('Failed to load driver rankings:', error);
drivers = [];
}
const { data: driverData, isLoading, error, retry } = useDataFetching({
queryKey: ['driverLeaderboard'],
queryFn: () => driverService.getDriverLeaderboard(),
});
// Pass data to Interactive wrapper which handles client-side interactions
return <DriverRankingsInteractive drivers={drivers} />;
const handleDriverClick = (driverId: string) => {
if (driverId.startsWith('demo-')) return;
router.push(`/drivers/${driverId}`);
};
const handleBackToLeaderboards = () => {
router.push('/leaderboards');
};
const drivers = driverData?.drivers || [];
return (
<StateContainer
data={drivers}
isLoading={isLoading}
error={error}
retry={retry}
config={{
loading: { variant: 'spinner', message: 'Loading driver rankings...' },
error: { variant: 'full-screen' },
empty: {
icon: Users,
title: 'No drivers found',
description: 'There are no drivers in the system yet.',
}
}}
>
{(driversData) => (
<DriverRankingsTemplate
drivers={driversData}
searchQuery={searchQuery}
selectedSkill={selectedSkill}
sortBy={sortBy}
showFilters={showFilters}
onSearchChange={setSearchQuery}
onSkillChange={setSelectedSkill}
onSortChange={setSortBy}
onToggleFilters={() => setShowFilters(!showFilters)}
onDriverClick={handleDriverClick}
onBackToLeaderboards={handleBackToLeaderboards}
/>
)}
</StateContainer>
);
}