269 lines
6.8 KiB
TypeScript
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;
|
|
});
|
|
} |