website refactor

This commit is contained in:
2026-01-21 02:02:01 +01:00
parent c06f93f1b6
commit a6e93acb37
4 changed files with 57 additions and 49 deletions

View File

@@ -30,7 +30,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
function isRaceDTO(value: unknown): value is RaceDTO {
if (!isRecord(value)) return false;
return typeof value.id === 'string' && typeof value.name === 'string' && typeof value.date === 'string';
return typeof value.id === 'string' && typeof value.name === 'string';
}
function parseRaceDTOArray(value: unknown): RaceDTO[] {
@@ -71,8 +71,11 @@ export class LeaguesApiClient extends BaseApiClient {
}
/** Get league memberships */
getMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
return this.get<LeagueMembershipsDTO>(`/leagues/${leagueId}/memberships`);
async getMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
const response = await this.get<any>(`/leagues/${leagueId}/memberships`);
if (Array.isArray(response)) return { members: response };
if (response?.members) return response;
return { members: [] };
}
/** Create a new league */
@@ -160,8 +163,9 @@ export class LeaguesApiClient extends BaseApiClient {
/** Get races for a league */
async getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> {
const response = await this.get<{ races?: unknown }>(`/leagues/${leagueId}/races`);
return { races: parseRaceDTOArray(response?.races) };
const response = await this.get<any>(`/leagues/${leagueId}/races`);
const races = Array.isArray(response) ? response : (response?.races || []);
return { races: parseRaceDTOArray(races) };
}
/** Admin roster: list current members (admin/owner only; actor derived from session) */

View File

@@ -35,10 +35,16 @@ export class LeagueDetailViewDataBuilder {
// Calculate info data
const membersCount = Array.isArray(memberships.members) ? memberships.members.length : 0;
const completedRacesCount = races.filter(r => (r as any).status === 'completed').length;
const completedRacesCount = races.filter(r => {
const status = (r as any).status;
return status === 'completed' || status === 'past';
}).length;
// Compute real avgSOF from races
const racesWithSOF = races.filter(r => typeof (r as any).strengthOfField === 'number' && (r as any).strengthOfField > 0);
const racesWithSOF = races.filter(r => {
const sof = (r as any).strengthOfField;
return typeof sof === 'number' && sof > 0;
});
const avgSOF = racesWithSOF.length > 0
? Math.round(racesWithSOF.reduce((sum, r) => sum + ((r as any).strengthOfField || 0), 0) / racesWithSOF.length)
: null;

View File

@@ -1,37 +1,31 @@
import { injectable, unmanaged } from 'inversify';
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient";
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
import { RacesApiClient } from "@/lib/api/races/RacesApiClient";
import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO";
import { CreateLeagueOutputDTO } from "@/lib/types/generated/CreateLeagueOutputDTO";
import type { MembershipRole } from "@/lib/types/MembershipRole";
import type { LeagueRosterJoinRequestDTO } from "@/lib/types/generated/LeagueRosterJoinRequestDTO";
import type { TotalLeaguesDTO } from '@/lib/types/generated/TotalLeaguesDTO';
import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO';
import type { LeagueScheduleDTO } from '@/lib/types/generated/LeagueScheduleDTO';
import type { CreateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceInputDTO';
import type { CreateLeagueScheduleRaceOutputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceOutputDTO';
import type { UpdateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/UpdateLeagueScheduleRaceInputDTO';
import type { LeagueScheduleRaceMutationSuccessDTO } from '@/lib/types/generated/LeagueScheduleRaceMutationSuccessDTO';
import type { LeagueSeasonSchedulePublishOutputDTO } from '@/lib/types/generated/LeagueSeasonSchedulePublishOutputDTO';
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
import type { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO';
import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
import { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
import { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient";
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/AllLeaguesWithCapacityAndScoringDTO';
import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO";
import type { CreateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceInputDTO';
import type { CreateLeagueScheduleRaceOutputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceOutputDTO';
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
import type { LeagueRosterJoinRequestDTO } from "@/lib/types/generated/LeagueRosterJoinRequestDTO";
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import type { LeagueScheduleDTO } from '@/lib/types/generated/LeagueScheduleDTO';
import type { LeagueScheduleRaceMutationSuccessDTO } from '@/lib/types/generated/LeagueScheduleRaceMutationSuccessDTO';
import type { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO';
import type { LeagueSeasonSchedulePublishOutputDTO } from '@/lib/types/generated/LeagueSeasonSchedulePublishOutputDTO';
import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO';
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
import type { UpdateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/UpdateLeagueScheduleRaceInputDTO';
import type { MembershipRole } from "@/lib/types/MembershipRole";
import { injectable, unmanaged } from 'inversify';
export interface LeagueScheduleAdminData {
leagueId: string;
@@ -328,6 +322,7 @@ export class LeagueService implements Service {
}
}
// TODO wtf what a stupid method??
async getLeagueScheduleDto(leagueId: string, seasonId: string): Promise<Result<LeagueScheduleDTO, DomainError>> {
return this.getAdminSchedule(leagueId, seasonId);
}

View File

@@ -64,20 +64,23 @@ export function LeagueOverviewTemplate({ viewData }: LeagueOverviewTemplateProps
<Box border borderColor="zinc-800" bg="zinc-900/30" overflow="hidden">
<table className="w-full text-left border-collapse">
<tbody>
{viewData.adminSummaries.concat(viewData.stewardSummaries).concat(viewData.memberSummaries).slice(0, 5).map((member) => (
<tr key={member.driverId} className="border-b border-zinc-800/50">
<td className="px-6 py-3">
<Box display="flex" alignItems="center" gap={3}>
<Box w="6" h="6" bg="zinc-800" rounded="full" />
<Text size="sm" weight="bold" color="text-white">{member.driverName}</Text>
</Box>
</td>
<td className="px-6 py-3 text-right">
<Text size="xs" color="text-zinc-500" uppercase weight="bold">{member.roleBadgeText}</Text>
</td>
</tr>
))}
{viewData.adminSummaries.length === 0 && viewData.stewardSummaries.length === 0 && viewData.memberSummaries.length === 0 && (
{[viewData.ownerSummary, ...viewData.adminSummaries, ...viewData.stewardSummaries, ...viewData.memberSummaries]
.filter((m): m is any => m !== null)
.slice(0, 5)
.map((member) => (
<tr key={member.driverId} className="border-b border-zinc-800/50">
<td className="px-6 py-3">
<Box display="flex" alignItems="center" gap={3}>
<Box w="6" h="6" bg="zinc-800" rounded="full" />
<Text size="sm" weight="bold" color="text-white">{member.driverName}</Text>
</Box>
</td>
<td className="px-6 py-3 text-right">
<Text size="xs" color="text-zinc-500" uppercase weight="bold">{member.roleBadgeText}</Text>
</td>
</tr>
))}
{viewData.adminSummaries.length === 0 && viewData.stewardSummaries.length === 0 && viewData.memberSummaries.length === 0 && !viewData.ownerSummary && (
<tr>
<td className="px-6 py-8 text-center">
<Text size="sm" color="text-zinc-600" italic>No members to display</Text>