wip
This commit is contained in:
@@ -43,7 +43,7 @@ import type { INotificationRepository, INotificationPreferenceRepository } from
|
||||
import {
|
||||
SendNotificationUseCase,
|
||||
MarkNotificationReadUseCase,
|
||||
GetUnreadNotificationsQuery
|
||||
GetUnreadNotificationsUseCase
|
||||
} from '@gridpilot/notifications/application';
|
||||
import {
|
||||
InMemoryNotificationRepository,
|
||||
@@ -81,7 +81,7 @@ import {
|
||||
InMemoryFeedRepository,
|
||||
InMemorySocialGraphRepository,
|
||||
} from '@gridpilot/social/infrastructure/inmemory/InMemorySocialAndFeed';
|
||||
import { DemoImageServiceAdapter } from '@gridpilot/demo-infrastructure';
|
||||
import { DemoImageServiceAdapter } from '@gridpilot/testing-support';
|
||||
|
||||
// Application use cases and queries
|
||||
import {
|
||||
@@ -174,7 +174,7 @@ import { LeagueSchedulePreviewPresenter } from './presenters/LeagueSchedulePrevi
|
||||
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
||||
import { ProfileOverviewPresenter } from './presenters/ProfileOverviewPresenter';
|
||||
|
||||
// Testing support
|
||||
// Demo infrastructure (runtime demo seed & helpers)
|
||||
import {
|
||||
createStaticRacingSeed,
|
||||
getDemoLeagueArchetypeByName,
|
||||
@@ -1246,8 +1246,8 @@ export function configureDIContainer(): void {
|
||||
|
||||
// Register queries - Notifications
|
||||
container.registerInstance(
|
||||
DI_TOKENS.GetUnreadNotificationsQuery,
|
||||
new GetUnreadNotificationsQuery(notificationRepository)
|
||||
DI_TOKENS.GetUnreadNotificationsUseCase,
|
||||
new GetUnreadNotificationsUseCase(notificationRepository)
|
||||
);
|
||||
|
||||
// Register use cases - Sponsors
|
||||
|
||||
@@ -35,7 +35,7 @@ import type { INotificationRepository, INotificationPreferenceRepository } from
|
||||
import type {
|
||||
SendNotificationUseCase,
|
||||
MarkNotificationReadUseCase,
|
||||
GetUnreadNotificationsQuery
|
||||
GetUnreadNotificationsUseCase
|
||||
} from '@gridpilot/notifications/application';
|
||||
import type {
|
||||
JoinLeagueUseCase,
|
||||
@@ -457,9 +457,9 @@ class DIContainer {
|
||||
return getDIContainer().resolve<MarkNotificationReadUseCase>(DI_TOKENS.MarkNotificationReadUseCase);
|
||||
}
|
||||
|
||||
get getUnreadNotificationsQuery(): GetUnreadNotificationsQuery {
|
||||
get getUnreadNotificationsUseCase(): GetUnreadNotificationsUseCase {
|
||||
this.ensureInitialized();
|
||||
return getDIContainer().resolve<GetUnreadNotificationsQuery>(DI_TOKENS.GetUnreadNotificationsQuery);
|
||||
return getDIContainer().resolve<GetUnreadNotificationsUseCase>(DI_TOKENS.GetUnreadNotificationsUseCase);
|
||||
}
|
||||
|
||||
get fileProtestUseCase(): FileProtestUseCase {
|
||||
@@ -801,8 +801,8 @@ export function getMarkNotificationReadUseCase(): MarkNotificationReadUseCase {
|
||||
return DIContainer.getInstance().markNotificationReadUseCase;
|
||||
}
|
||||
|
||||
export function getGetUnreadNotificationsQuery(): GetUnreadNotificationsQuery {
|
||||
return DIContainer.getInstance().getUnreadNotificationsQuery;
|
||||
export function getGetUnreadNotificationsUseCase(): GetUnreadNotificationsUseCase {
|
||||
return DIContainer.getInstance().getUnreadNotificationsUseCase;
|
||||
}
|
||||
|
||||
export function getFileProtestUseCase(): FileProtestUseCase {
|
||||
|
||||
@@ -101,7 +101,7 @@ export const DI_TOKENS = {
|
||||
GetRacePenaltiesUseCase: Symbol.for('GetRacePenaltiesUseCase'),
|
||||
|
||||
// Queries - Notifications
|
||||
GetUnreadNotificationsQuery: Symbol.for('GetUnreadNotificationsQuery'),
|
||||
GetUnreadNotificationsUseCase: Symbol.for('GetUnreadNotificationsUseCase'),
|
||||
|
||||
// Use Cases - Sponsors
|
||||
GetSponsorDashboardUseCase: Symbol.for('GetSponsorDashboardUseCase'),
|
||||
|
||||
121
apps/website/lib/presenters/TeamAdminPresenter.ts
Normal file
121
apps/website/lib/presenters/TeamAdminPresenter.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import type { Team, TeamJoinRequest } from '@gridpilot/racing';
|
||||
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
|
||||
import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappers';
|
||||
import {
|
||||
getDriverRepository,
|
||||
getGetTeamJoinRequestsUseCase,
|
||||
getApproveTeamJoinRequestUseCase,
|
||||
getRejectTeamJoinRequestUseCase,
|
||||
getUpdateTeamUseCase,
|
||||
} from '@/lib/di-container';
|
||||
|
||||
export interface TeamAdminJoinRequestViewModel {
|
||||
id: string;
|
||||
teamId: string;
|
||||
driverId: string;
|
||||
requestedAt: Date;
|
||||
message?: string;
|
||||
driver?: DriverDTO;
|
||||
}
|
||||
|
||||
export interface TeamAdminTeamSummaryViewModel {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export interface TeamAdminViewModel {
|
||||
team: TeamAdminTeamSummaryViewModel;
|
||||
requests: TeamAdminJoinRequestViewModel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load join requests plus driver DTOs for a team.
|
||||
*/
|
||||
export async function loadTeamAdminViewModel(team: Team): Promise<TeamAdminViewModel> {
|
||||
const requests = await loadTeamJoinRequests(team.id);
|
||||
return {
|
||||
team: {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: team.tag,
|
||||
description: team.description,
|
||||
ownerId: team.ownerId,
|
||||
},
|
||||
requests,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadTeamJoinRequests(teamId: string): Promise<TeamAdminJoinRequestViewModel[]> {
|
||||
const getRequestsUseCase = getGetTeamJoinRequestsUseCase();
|
||||
await getRequestsUseCase.execute({ teamId });
|
||||
const presenterVm = getRequestsUseCase.presenter.getViewModel();
|
||||
|
||||
const driverRepo = getDriverRepository();
|
||||
const allDrivers = await driverRepo.findAll();
|
||||
const driversById: Record<string, DriverDTO> = {};
|
||||
|
||||
for (const driver of allDrivers) {
|
||||
const dto = EntityMappers.toDriverDTO(driver);
|
||||
if (dto) {
|
||||
driversById[dto.id] = dto;
|
||||
}
|
||||
}
|
||||
|
||||
return presenterVm.requests.map((req) => ({
|
||||
id: req.requestId,
|
||||
teamId: req.teamId,
|
||||
driverId: req.driverId,
|
||||
requestedAt: new Date(req.requestedAt),
|
||||
message: req.message,
|
||||
driver: driversById[req.driverId],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve a team join request and return updated request view models.
|
||||
*/
|
||||
export async function approveTeamJoinRequestAndReload(
|
||||
requestId: string,
|
||||
teamId: string,
|
||||
): Promise<TeamAdminJoinRequestViewModel[]> {
|
||||
const useCase = getApproveTeamJoinRequestUseCase();
|
||||
await useCase.execute({ requestId });
|
||||
return loadTeamJoinRequests(teamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a team join request and return updated request view models.
|
||||
*/
|
||||
export async function rejectTeamJoinRequestAndReload(
|
||||
requestId: string,
|
||||
teamId: string,
|
||||
): Promise<TeamAdminJoinRequestViewModel[]> {
|
||||
const useCase = getRejectTeamJoinRequestUseCase();
|
||||
await useCase.execute({ requestId });
|
||||
return loadTeamJoinRequests(teamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update team basic details.
|
||||
*/
|
||||
export async function updateTeamDetails(params: {
|
||||
teamId: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
updatedByDriverId: string;
|
||||
}): Promise<void> {
|
||||
const useCase = getUpdateTeamUseCase();
|
||||
await useCase.execute({
|
||||
teamId: params.teamId,
|
||||
updates: {
|
||||
name: params.name,
|
||||
tag: params.tag,
|
||||
description: params.description,
|
||||
},
|
||||
updatedBy: params.updatedByDriverId,
|
||||
});
|
||||
}
|
||||
76
apps/website/lib/presenters/TeamStandingsPresenter.ts
Normal file
76
apps/website/lib/presenters/TeamStandingsPresenter.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { getStandingRepository, getLeagueRepository, getTeamMembershipRepository } from '@/lib/di-container';
|
||||
|
||||
export interface TeamLeagueStandingViewModel {
|
||||
leagueId: string;
|
||||
leagueName: string;
|
||||
position: number;
|
||||
points: number;
|
||||
wins: number;
|
||||
racesCompleted: number;
|
||||
}
|
||||
|
||||
export interface TeamStandingsViewModel {
|
||||
standings: TeamLeagueStandingViewModel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute team standings across the given leagues for a team.
|
||||
* Mirrors the previous TeamStandings component logic but keeps it out of the UI layer.
|
||||
*/
|
||||
export async function loadTeamStandings(
|
||||
teamId: string,
|
||||
leagues: string[],
|
||||
): Promise<TeamStandingsViewModel> {
|
||||
const standingRepo = getStandingRepository();
|
||||
const leagueRepo = getLeagueRepository();
|
||||
const teamMembershipRepo = getTeamMembershipRepository();
|
||||
|
||||
const members = await teamMembershipRepo.getTeamMembers(teamId);
|
||||
const memberIds = members.map((m) => m.driverId);
|
||||
|
||||
const teamStandings: TeamLeagueStandingViewModel[] = [];
|
||||
|
||||
for (const leagueId of leagues) {
|
||||
const league = await leagueRepo.findById(leagueId);
|
||||
if (!league) continue;
|
||||
|
||||
const leagueStandings = await standingRepo.findByLeagueId(leagueId);
|
||||
|
||||
let totalPoints = 0;
|
||||
let totalWins = 0;
|
||||
let totalRaces = 0;
|
||||
|
||||
for (const standing of leagueStandings) {
|
||||
if (memberIds.includes(standing.driverId)) {
|
||||
totalPoints += standing.points;
|
||||
totalWins += standing.wins;
|
||||
totalRaces = Math.max(totalRaces, standing.racesCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified team position based on total points (same spirit as previous logic)
|
||||
const allTeamPoints = leagueStandings
|
||||
.filter((s) => memberIds.includes(s.driverId))
|
||||
.reduce((sum, s) => sum + s.points, 0);
|
||||
|
||||
const position =
|
||||
leagueStandings
|
||||
.filter((_, idx, arr) => {
|
||||
const teamPoints = arr
|
||||
.filter((s) => memberIds.includes(s.driverId))
|
||||
.reduce((sum, s) => sum + s.points, 0);
|
||||
return teamPoints > allTeamPoints;
|
||||
}).length + 1;
|
||||
|
||||
teamStandings.push({
|
||||
leagueId,
|
||||
leagueName: league.name,
|
||||
position,
|
||||
points: totalPoints,
|
||||
wins: totalWins,
|
||||
racesCompleted: totalRaces,
|
||||
});
|
||||
}
|
||||
|
||||
return { standings: teamStandings };
|
||||
}
|
||||
Reference in New Issue
Block a user