Files
gridpilot.gg/apps/website/lib/presenters/LeagueAdminPresenter.ts
2025-12-16 10:50:15 +01:00

269 lines
6.8 KiB
TypeScript

/**
* LeagueAdminPresenter - Pure data transformer
* Transforms API responses to view models without DI dependencies.
* All data fetching is done via apiClient.
*/
import { apiClient } from '@/lib/apiClient';
import type {
LeagueJoinRequestViewModel as ApiLeagueJoinRequestViewModel,
LeagueConfigFormModelDto,
LeagueSeasonSummaryViewModel as ApiLeagueSeasonSummaryViewModel,
DriverDTO,
} from '@/lib/apiClient';
// ============================================================================
// View Model Types
// ============================================================================
export interface LeagueJoinRequestViewModel {
id: string;
leagueId: string;
driverId: string;
requestedAt: Date;
message?: string | undefined;
driver?: DriverDTO | undefined;
}
export interface ProtestDriverSummary {
[driverId: string]: DriverDTO;
}
export interface ProtestRaceSummary {
[raceId: string]: {
id: string;
name: string;
scheduledTime: string;
};
}
export interface LeagueOwnerSummaryViewModel {
driver: DriverDTO;
rating: number | null;
rank: number | null;
}
export interface LeagueSummaryViewModel {
id: string;
ownerId: string;
settings: {
pointsSystem: string;
};
}
export interface LeagueAdminProtestsViewModel {
protests: Array<{
id: string;
raceId: string;
complainantId: string;
defendantId: string;
description: string;
status: string;
createdAt: string;
}>;
racesById: ProtestRaceSummary;
driversById: ProtestDriverSummary;
}
export interface LeagueAdminConfigViewModel {
form: LeagueConfigFormModelDto | null;
}
export interface LeagueAdminPermissionsViewModel {
canRemoveMember: boolean;
canUpdateRoles: boolean;
}
export interface LeagueSeasonSummaryViewModel {
seasonId: string;
name: string;
status: string;
startDate?: Date | undefined;
endDate?: Date | undefined;
isPrimary: boolean;
isParallelActive: boolean;
}
export interface LeagueAdminViewModel {
joinRequests: LeagueJoinRequestViewModel[];
ownerSummary: LeagueOwnerSummaryViewModel | null;
config: LeagueAdminConfigViewModel;
protests: LeagueAdminProtestsViewModel;
}
export type MembershipRole = 'owner' | 'admin' | 'member';
// ============================================================================
// Data Fetching Functions (using apiClient)
// ============================================================================
/**
* Load join requests for a league via API.
*/
export async function loadLeagueJoinRequests(leagueId: string): Promise<LeagueJoinRequestViewModel[]> {
const requests = await apiClient.leagues.getJoinRequests(leagueId);
return requests.map((request: ApiLeagueJoinRequestViewModel) => {
const viewModel: LeagueJoinRequestViewModel = {
id: request.id,
leagueId: request.leagueId,
driverId: request.driverId,
requestedAt: new Date(request.requestedAt),
};
if (request.message) {
viewModel.message = request.message;
}
return viewModel;
});
}
/**
* Approve a league join request and return updated join requests.
*/
export async function approveLeagueJoinRequest(
leagueId: string,
requestId: string
): Promise<LeagueJoinRequestViewModel[]> {
await apiClient.leagues.approveJoinRequest(leagueId, requestId);
return loadLeagueJoinRequests(leagueId);
}
/**
* Reject a league join request.
*/
export async function rejectLeagueJoinRequest(
leagueId: string,
requestId: string
): Promise<LeagueJoinRequestViewModel[]> {
await apiClient.leagues.rejectJoinRequest(leagueId, requestId);
return loadLeagueJoinRequests(leagueId);
}
/**
* Get permissions for a performer on league membership actions.
*/
export async function getLeagueAdminPermissions(
leagueId: string,
performerDriverId: string
): Promise<LeagueAdminPermissionsViewModel> {
const permissions = await apiClient.leagues.getAdminPermissions(leagueId, performerDriverId);
return {
canRemoveMember: permissions.canManageMembers || permissions.isOwner || permissions.isAdmin,
canUpdateRoles: permissions.isOwner,
};
}
/**
* Remove a member from the league.
*/
export async function removeLeagueMember(
leagueId: string,
performerDriverId: string,
targetDriverId: string
): Promise<void> {
await apiClient.leagues.removeMember(leagueId, performerDriverId, targetDriverId);
}
/**
* Update a member's role.
*/
export async function updateLeagueMemberRole(
leagueId: string,
performerDriverId: string,
targetDriverId: string,
newRole: MembershipRole
): Promise<void> {
await apiClient.leagues.updateMemberRole(leagueId, performerDriverId, targetDriverId, newRole);
}
/**
* Load owner summary for a league.
*/
export async function loadLeagueOwnerSummary(params: {
leagueId: string;
ownerId: string;
}): Promise<LeagueOwnerSummaryViewModel | null> {
const ownerSummary = await apiClient.leagues.getOwnerSummary(params.leagueId, params.ownerId);
if (!ownerSummary) {
return null;
}
// For now, return a simplified version - the API should provide driver details
return {
driver: {
id: params.ownerId,
name: ownerSummary.leagueName, // This would need to be populated from API
},
rating: null,
rank: null,
};
}
/**
* Load league full config form.
*/
export async function loadLeagueConfig(
leagueId: string
): Promise<LeagueAdminConfigViewModel> {
const config = await apiClient.leagues.getConfig(leagueId);
return {
form: config,
};
}
/**
* Load protests for a league.
*/
export async function loadLeagueProtests(leagueId: string): Promise<LeagueAdminProtestsViewModel> {
const protestsData = await apiClient.leagues.getProtests(leagueId);
// Transform the API response
const racesById: ProtestRaceSummary = {};
const driversById: ProtestDriverSummary = {};
return {
protests: protestsData.protests.map((p) => ({
id: p.id,
raceId: p.raceId,
complainantId: p.complainantId,
defendantId: p.defendantId,
description: p.description,
status: p.status,
createdAt: p.createdAt,
})),
racesById,
driversById,
};
}
/**
* Load seasons for a league.
*/
export async function loadLeagueSeasons(leagueId: string): Promise<LeagueSeasonSummaryViewModel[]> {
const seasons = await apiClient.leagues.getSeasons(leagueId);
const activeCount = seasons.filter((s: ApiLeagueSeasonSummaryViewModel) => s.status === 'active').length;
return seasons.map((s: ApiLeagueSeasonSummaryViewModel) => {
const viewModel: LeagueSeasonSummaryViewModel = {
seasonId: s.id,
name: s.name,
status: s.status,
isPrimary: false, // Would need to be provided by API
isParallelActive: activeCount > 1 && s.status === 'active',
};
if (s.startDate) {
viewModel.startDate = new Date(s.startDate);
}
if (s.endDate) {
viewModel.endDate = new Date(s.endDate);
}
return viewModel;
});
}