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

View File

@@ -35,10 +35,16 @@ export class LeagueDetailViewDataBuilder {
// Calculate info data // Calculate info data
const membersCount = Array.isArray(memberships.members) ? memberships.members.length : 0; 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 // 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 const avgSOF = racesWithSOF.length > 0
? Math.round(racesWithSOF.reduce((sum, r) => sum + ((r as any).strengthOfField || 0), 0) / racesWithSOF.length) ? Math.round(racesWithSOF.reduce((sum, r) => sum + ((r as any).strengthOfField || 0), 0) / racesWithSOF.length)
: null; : 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 { 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 { RacesApiClient } from "@/lib/api/races/RacesApiClient";
import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO"; import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient";
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 { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env'; 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 { 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 { 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 { export interface LeagueScheduleAdminData {
leagueId: string; 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>> { async getLeagueScheduleDto(leagueId: string, seasonId: string): Promise<Result<LeagueScheduleDTO, DomainError>> {
return this.getAdminSchedule(leagueId, seasonId); 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"> <Box border borderColor="zinc-800" bg="zinc-900/30" overflow="hidden">
<table className="w-full text-left border-collapse"> <table className="w-full text-left border-collapse">
<tbody> <tbody>
{viewData.adminSummaries.concat(viewData.stewardSummaries).concat(viewData.memberSummaries).slice(0, 5).map((member) => ( {[viewData.ownerSummary, ...viewData.adminSummaries, ...viewData.stewardSummaries, ...viewData.memberSummaries]
<tr key={member.driverId} className="border-b border-zinc-800/50"> .filter((m): m is any => m !== null)
<td className="px-6 py-3"> .slice(0, 5)
<Box display="flex" alignItems="center" gap={3}> .map((member) => (
<Box w="6" h="6" bg="zinc-800" rounded="full" /> <tr key={member.driverId} className="border-b border-zinc-800/50">
<Text size="sm" weight="bold" color="text-white">{member.driverName}</Text> <td className="px-6 py-3">
</Box> <Box display="flex" alignItems="center" gap={3}>
</td> <Box w="6" h="6" bg="zinc-800" rounded="full" />
<td className="px-6 py-3 text-right"> <Text size="sm" weight="bold" color="text-white">{member.driverName}</Text>
<Text size="xs" color="text-zinc-500" uppercase weight="bold">{member.roleBadgeText}</Text> </Box>
</td> </td>
</tr> <td className="px-6 py-3 text-right">
))} <Text size="xs" color="text-zinc-500" uppercase weight="bold">{member.roleBadgeText}</Text>
{viewData.adminSummaries.length === 0 && viewData.stewardSummaries.length === 0 && viewData.memberSummaries.length === 0 && ( </td>
</tr>
))}
{viewData.adminSummaries.length === 0 && viewData.stewardSummaries.length === 0 && viewData.memberSummaries.length === 0 && !viewData.ownerSummary && (
<tr> <tr>
<td className="px-6 py-8 text-center"> <td className="px-6 py-8 text-center">
<Text size="sm" color="text-zinc-600" italic>No members to display</Text> <Text size="sm" color="text-zinc-600" italic>No members to display</Text>