wip
This commit is contained in:
@@ -40,11 +40,11 @@ import {
|
||||
getDriverRepository,
|
||||
getDriverStats,
|
||||
getAllDriverRankings,
|
||||
getGetDriverTeamQuery,
|
||||
getGetDriverTeamUseCase,
|
||||
getSocialRepository,
|
||||
getImageService,
|
||||
getGetAllTeamsQuery,
|
||||
getGetTeamMembersQuery,
|
||||
getGetAllTeamsUseCase,
|
||||
getGetTeamMembersUseCase,
|
||||
} from '@/lib/di-container';
|
||||
import { Driver, EntityMappers, type Team } from '@gridpilot/racing';
|
||||
import type { DriverDTO } from '@gridpilot/racing';
|
||||
@@ -382,18 +382,23 @@ export default function DriverDetailPage({
|
||||
setDriver(driverDto);
|
||||
|
||||
// Load team data
|
||||
const teamQuery = getGetDriverTeamQuery();
|
||||
const teamResult = await teamQuery.execute({ driverId });
|
||||
setTeamData(teamResult);
|
||||
const teamUseCase = getGetDriverTeamUseCase();
|
||||
await teamUseCase.execute({ driverId });
|
||||
const teamViewModel = teamUseCase.presenter.getViewModel();
|
||||
setTeamData(teamViewModel.result);
|
||||
|
||||
// Load ALL team memberships
|
||||
const allTeamsQuery = getGetAllTeamsQuery();
|
||||
const allTeams = await allTeamsQuery.execute();
|
||||
const membershipsQuery = getGetTeamMembersQuery();
|
||||
const allTeamsUseCase = getGetAllTeamsUseCase();
|
||||
await allTeamsUseCase.execute();
|
||||
const allTeamsViewModel = allTeamsUseCase.presenter.getViewModel();
|
||||
const allTeams = allTeamsViewModel.teams;
|
||||
const membershipsUseCase = getGetTeamMembersUseCase();
|
||||
|
||||
const memberships: TeamMembershipInfo[] = [];
|
||||
for (const team of allTeams) {
|
||||
const members = await membershipsQuery.execute({ teamId: team.id });
|
||||
await membershipsUseCase.execute({ teamId: team.id });
|
||||
const membersViewModel = membershipsUseCase.presenter.getViewModel();
|
||||
const members = membersViewModel.members;
|
||||
const membership = members.find((m) => m.driverId === driverId);
|
||||
if (membership) {
|
||||
memberships.push({
|
||||
|
||||
@@ -27,27 +27,15 @@ import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import { getDriverRepository, getDriverStats, getAllDriverRankings, getImageService } from '@/lib/di-container';
|
||||
import { getGetDriversLeaderboardUseCase } from '@/lib/di-container';
|
||||
import type { DriverLeaderboardItemViewModel, SkillLevel } from '@gridpilot/racing/application/presenters/IDriversLeaderboardPresenter';
|
||||
import Image from 'next/image';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
|
||||
interface DriverListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
rating: number;
|
||||
skillLevel: SkillLevel;
|
||||
nationality: string;
|
||||
racesCompleted: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
isActive: boolean;
|
||||
rank: number;
|
||||
}
|
||||
type DriverListItem = DriverLeaderboardItemViewModel;
|
||||
|
||||
// ============================================================================
|
||||
// DEMO DATA
|
||||
@@ -87,7 +75,6 @@ interface FeaturedDriverCardProps {
|
||||
}
|
||||
|
||||
function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardProps) {
|
||||
const imageService = getImageService();
|
||||
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
||||
|
||||
const getBorderColor = (pos: number) => {
|
||||
@@ -131,7 +118,7 @@ function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardPro
|
||||
{/* Avatar & Name */}
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="relative w-16 h-16 rounded-full overflow-hidden border-2 border-charcoal-outline group-hover:border-primary-blue transition-colors">
|
||||
<Image src={imageService.getDriverAvatar(driver.id)} alt={driver.name} fill className="object-cover" />
|
||||
<Image src={driver.avatarUrl} alt={driver.name} fill className="object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white group-hover:text-primary-blue transition-colors">
|
||||
@@ -236,7 +223,6 @@ interface LeaderboardPreviewProps {
|
||||
|
||||
function LeaderboardPreview({ drivers, onDriverClick }: LeaderboardPreviewProps) {
|
||||
const router = useRouter();
|
||||
const imageService = getImageService();
|
||||
const top5 = drivers.slice(0, 5);
|
||||
|
||||
const getMedalColor = (position: number) => {
|
||||
@@ -300,7 +286,7 @@ function LeaderboardPreview({ drivers, onDriverClick }: LeaderboardPreviewProps)
|
||||
|
||||
{/* Avatar */}
|
||||
<div className="relative w-9 h-9 rounded-full overflow-hidden border-2 border-charcoal-outline">
|
||||
<Image src={imageService.getDriverAvatar(driver.id)} alt={driver.name} fill className="object-cover" />
|
||||
<Image src={driver.avatarUrl} alt={driver.name} fill className="object-cover" />
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
@@ -345,7 +331,6 @@ interface RecentActivityProps {
|
||||
}
|
||||
|
||||
function RecentActivity({ drivers, onDriverClick }: RecentActivityProps) {
|
||||
const imageService = getImageService();
|
||||
const activeDrivers = drivers.filter((d) => d.isActive).slice(0, 6);
|
||||
|
||||
return (
|
||||
@@ -371,7 +356,7 @@ function RecentActivity({ drivers, onDriverClick }: RecentActivityProps) {
|
||||
className="p-3 rounded-xl bg-iron-gray/40 border border-charcoal-outline hover:border-performance-green/40 transition-all group text-center"
|
||||
>
|
||||
<div className="relative w-12 h-12 mx-auto rounded-full overflow-hidden border-2 border-charcoal-outline mb-2">
|
||||
<Image src={imageService.getDriverAvatar(driver.id)} alt={driver.name} fill className="object-cover" />
|
||||
<Image src={driver.avatarUrl} alt={driver.name} fill className="object-cover" />
|
||||
<div className="absolute bottom-0 right-0 w-3 h-3 rounded-full bg-performance-green border-2 border-iron-gray" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-white truncate group-hover:text-performance-green transition-colors">
|
||||
@@ -395,57 +380,20 @@ export default function DriversPage() {
|
||||
const [drivers, setDrivers] = useState<DriverListItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [totalRaces, setTotalRaces] = useState(0);
|
||||
const [totalWins, setTotalWins] = useState(0);
|
||||
const [activeCount, setActiveCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const driverRepo = getDriverRepository();
|
||||
const allDrivers = await driverRepo.findAll();
|
||||
const rankings = getAllDriverRankings();
|
||||
const useCase = getGetDriversLeaderboardUseCase();
|
||||
await useCase.execute();
|
||||
const viewModel = useCase.presenter.getViewModel();
|
||||
|
||||
const items: DriverListItem[] = allDrivers.map((driver) => {
|
||||
const stats = getDriverStats(driver.id);
|
||||
const rating = stats?.rating ?? 0;
|
||||
const wins = stats?.wins ?? 0;
|
||||
const podiums = stats?.podiums ?? 0;
|
||||
const totalRaces = stats?.totalRaces ?? 0;
|
||||
|
||||
let effectiveRank = Number.POSITIVE_INFINITY;
|
||||
if (typeof stats?.overallRank === 'number' && stats.overallRank > 0) {
|
||||
effectiveRank = stats.overallRank;
|
||||
} else {
|
||||
const indexInGlobal = rankings.findIndex((entry) => entry.driverId === driver.id);
|
||||
if (indexInGlobal !== -1) {
|
||||
effectiveRank = indexInGlobal + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const skillLevel: SkillLevel =
|
||||
rating >= 3000 ? 'pro' : rating >= 2500 ? 'advanced' : rating >= 1800 ? 'intermediate' : 'beginner';
|
||||
|
||||
const isActive = rankings.some((r) => r.driverId === driver.id);
|
||||
|
||||
return {
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
rating,
|
||||
skillLevel,
|
||||
nationality: driver.country,
|
||||
racesCompleted: totalRaces,
|
||||
wins,
|
||||
podiums,
|
||||
isActive,
|
||||
rank: effectiveRank,
|
||||
};
|
||||
});
|
||||
|
||||
// Sort by rank
|
||||
items.sort((a, b) => {
|
||||
const rankA = Number.isFinite(a.rank) && a.rank > 0 ? a.rank : Number.POSITIVE_INFINITY;
|
||||
const rankB = Number.isFinite(b.rank) && b.rank > 0 ? b.rank : Number.POSITIVE_INFINITY;
|
||||
return rankA - rankB || b.rating - a.rating;
|
||||
});
|
||||
|
||||
setDrivers(items);
|
||||
setDrivers(viewModel.drivers);
|
||||
setTotalRaces(viewModel.totalRaces);
|
||||
setTotalWins(viewModel.totalWins);
|
||||
setActiveCount(viewModel.activeCount);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -465,10 +413,6 @@ export default function DriversPage() {
|
||||
);
|
||||
});
|
||||
|
||||
// Stats
|
||||
const totalRaces = drivers.reduce((sum, d) => sum + d.racesCompleted, 0);
|
||||
const totalWins = drivers.reduce((sum, d) => sum + d.wins, 0);
|
||||
const activeCount = drivers.filter((d) => d.isActive).length;
|
||||
|
||||
// Featured drivers (top 4)
|
||||
const featuredDrivers = filteredDrivers.slice(0, 4);
|
||||
|
||||
Reference in New Issue
Block a user