wip
This commit is contained in:
@@ -22,31 +22,19 @@ import {
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import { getGetAllTeamsUseCase, getGetTeamMembersUseCase, getDriverStats } from '@/lib/di-container';
|
||||
import type { Team } from '@gridpilot/racing';
|
||||
import { getGetTeamsLeaderboardUseCase } from '@/lib/di-container';
|
||||
import type {
|
||||
TeamLeaderboardItemViewModel,
|
||||
SkillLevel,
|
||||
} from '@gridpilot/racing/application/presenters/ITeamsLeaderboardPresenter';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
type SortBy = 'rating' | 'wins' | 'winRate' | 'races';
|
||||
|
||||
interface TeamDisplayData {
|
||||
id: string;
|
||||
name: string;
|
||||
memberCount: number;
|
||||
rating: number | null;
|
||||
totalWins: number;
|
||||
totalRaces: number;
|
||||
performanceLevel: SkillLevel;
|
||||
isRecruiting: boolean;
|
||||
createdAt: Date;
|
||||
description?: string;
|
||||
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
}
|
||||
type TeamDisplayData = TeamLeaderboardItemViewModel;
|
||||
|
||||
// ============================================================================
|
||||
// SKILL LEVEL CONFIG
|
||||
@@ -248,83 +236,29 @@ function TopThreePodium({ teams, onTeamClick }: TopThreePodiumProps) {
|
||||
|
||||
export default function TeamLeaderboardPage() {
|
||||
const router = useRouter();
|
||||
const [realTeams, setRealTeams] = useState<TeamDisplayData[]>([]);
|
||||
const [teams, setTeams] = useState<TeamDisplayData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState<SortBy>('rating');
|
||||
const [filterLevel, setFilterLevel] = useState<SkillLevel | 'all'>('all');
|
||||
|
||||
useEffect(() => {
|
||||
loadTeams();
|
||||
const loadTeams = async () => {
|
||||
try {
|
||||
const useCase = getGetTeamsLeaderboardUseCase();
|
||||
await useCase.execute();
|
||||
const viewModel = useCase.presenter.getViewModel();
|
||||
setTeams(viewModel.teams);
|
||||
} catch (error) {
|
||||
console.error('Failed to load teams:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void loadTeams();
|
||||
}, []);
|
||||
|
||||
const loadTeams = async () => {
|
||||
try {
|
||||
const allTeamsUseCase = getGetAllTeamsUseCase();
|
||||
const teamMembersUseCase = getGetTeamMembersUseCase();
|
||||
|
||||
await allTeamsUseCase.execute();
|
||||
const allTeamsViewModel = allTeamsUseCase.presenter.getViewModel();
|
||||
const allTeams = allTeamsViewModel.teams;
|
||||
const teamData: TeamDisplayData[] = [];
|
||||
|
||||
await Promise.all(
|
||||
allTeams.map(async (team: Team) => {
|
||||
await teamMembersUseCase.execute({ teamId: team.id });
|
||||
const membershipsViewModel = teamMembersUseCase.presenter.getViewModel();
|
||||
const memberships = membershipsViewModel.members;
|
||||
const memberCount = memberships.length;
|
||||
|
||||
let ratingSum = 0;
|
||||
let ratingCount = 0;
|
||||
let totalWins = 0;
|
||||
let totalRaces = 0;
|
||||
|
||||
for (const membership of memberships) {
|
||||
const stats = getDriverStats(membership.driverId);
|
||||
if (!stats) continue;
|
||||
|
||||
if (typeof stats.rating === 'number') {
|
||||
ratingSum += stats.rating;
|
||||
ratingCount += 1;
|
||||
}
|
||||
|
||||
totalWins += stats.wins ?? 0;
|
||||
totalRaces += stats.totalRaces ?? 0;
|
||||
}
|
||||
|
||||
const averageRating = ratingCount > 0 ? ratingSum / ratingCount : null;
|
||||
|
||||
let performanceLevel: TeamDisplayData['performanceLevel'] = 'beginner';
|
||||
if (averageRating !== null) {
|
||||
if (averageRating >= 4500) performanceLevel = 'pro';
|
||||
else if (averageRating >= 3000) performanceLevel = 'advanced';
|
||||
else if (averageRating >= 2000) performanceLevel = 'intermediate';
|
||||
}
|
||||
|
||||
teamData.push({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount,
|
||||
rating: averageRating,
|
||||
totalWins,
|
||||
totalRaces,
|
||||
performanceLevel,
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
setRealTeams(teamData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load teams:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const teams = realTeams;
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
if (teamId.startsWith('demo-team-')) {
|
||||
|
||||
@@ -298,19 +298,13 @@ function FeaturedRecruiting({ teams, onTeamClick }: FeaturedRecruitingProps) {
|
||||
// ============================================================================
|
||||
|
||||
interface TeamLeaderboardPreviewProps {
|
||||
teams: TeamDisplayData[];
|
||||
topTeams: TeamDisplayData[];
|
||||
onTeamClick: (id: string) => void;
|
||||
}
|
||||
|
||||
function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewProps) {
|
||||
function TeamLeaderboardPreview({ topTeams, onTeamClick }: TeamLeaderboardPreviewProps) {
|
||||
const router = useRouter();
|
||||
|
||||
// Sort teams by rating and get top 5
|
||||
const topTeams = [...teams]
|
||||
.filter((t) => t.rating !== null)
|
||||
.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))
|
||||
.slice(0, 5);
|
||||
|
||||
const getMedalColor = (position: number) => {
|
||||
switch (position) {
|
||||
case 0:
|
||||
@@ -437,6 +431,13 @@ function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewPr
|
||||
export default function TeamsPage() {
|
||||
const router = useRouter();
|
||||
const [realTeams, setRealTeams] = useState<TeamDisplayData[]>([]);
|
||||
const [groupsBySkillLevel, setGroupsBySkillLevel] = useState<Record<SkillLevel, TeamDisplayData[]>>({
|
||||
beginner: [],
|
||||
intermediate: [],
|
||||
advanced: [],
|
||||
pro: [],
|
||||
});
|
||||
const [topTeams, setTopTeams] = useState<TeamDisplayData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showCreateForm, setShowCreateForm] = useState(false);
|
||||
@@ -451,6 +452,8 @@ export default function TeamsPage() {
|
||||
await useCase.execute();
|
||||
const viewModel = useCase.presenter.getViewModel();
|
||||
setRealTeams(viewModel.teams);
|
||||
setGroupsBySkillLevel(viewModel.groupsBySkillLevel);
|
||||
setTopTeams(viewModel.topTeams);
|
||||
} catch (error) {
|
||||
console.error('Failed to load teams:', error);
|
||||
} finally {
|
||||
@@ -486,12 +489,20 @@ export default function TeamsPage() {
|
||||
});
|
||||
|
||||
// Group teams by skill level
|
||||
const teamsByLevel = SKILL_LEVELS.reduce(
|
||||
const teamsByLevel: Record<SkillLevel, TeamDisplayData[]> = SKILL_LEVELS.reduce(
|
||||
(acc, level) => {
|
||||
acc[level.id] = filteredTeams.filter((t) => t.performanceLevel === level.id);
|
||||
const fromGroup = groupsBySkillLevel[level.id] ?? [];
|
||||
acc[level.id] = filteredTeams.filter((team) =>
|
||||
fromGroup.some((groupTeam) => groupTeam.id === team.id),
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<SkillLevel, TeamDisplayData[]>,
|
||||
{
|
||||
beginner: [],
|
||||
intermediate: [],
|
||||
advanced: [],
|
||||
pro: [],
|
||||
} as Record<SkillLevel, TeamDisplayData[]>,
|
||||
);
|
||||
|
||||
const recruitingCount = teams.filter((t) => t.isRecruiting).length;
|
||||
@@ -647,7 +658,7 @@ export default function TeamsPage() {
|
||||
{!searchQuery && <WhyJoinTeamSection />}
|
||||
|
||||
{/* Team Leaderboard Preview */}
|
||||
{!searchQuery && <TeamLeaderboardPreview teams={teams} onTeamClick={handleTeamClick} />}
|
||||
{!searchQuery && <TeamLeaderboardPreview topTeams={topTeams} onTeamClick={handleTeamClick} />}
|
||||
|
||||
{/* Featured Recruiting */}
|
||||
{!searchQuery && <FeaturedRecruiting teams={teams} onTeamClick={handleTeamClick} />}
|
||||
|
||||
Reference in New Issue
Block a user