feat: split admin dashboard, improve match ops, and player closed-match UX
Admin: add match/player overview sub-nav; refine settlement flow and league match management UI; improve action button enabled/disabled styles; enhance logo upload and outright odds sync. API: expose matchPhase/bettingOpen for closed matches; league publish guards; settlement preview with auto score save; outright team auto-sync. Player: watermark for closed/settled states; keep match and bet details visible; remove default login credentials. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -168,11 +168,6 @@ export class OutrightService {
|
||||
this.getOutrightTitle(match.id, 'ms-MY'),
|
||||
]);
|
||||
|
||||
const addableFixtureTeams = await this.listAddableFixtureTeams(
|
||||
match.leagueId,
|
||||
fullMarket.id,
|
||||
);
|
||||
|
||||
return {
|
||||
id: match.id.toString(),
|
||||
leagueId: match.leagueId.toString(),
|
||||
@@ -192,49 +187,9 @@ export class OutrightService {
|
||||
playerVisible: visibility.playerVisible,
|
||||
playerHiddenReason: visibility.playerHiddenReason,
|
||||
selections,
|
||||
addableFixtureTeams,
|
||||
};
|
||||
}
|
||||
|
||||
private async listAddableFixtureTeams(leagueId: bigint, marketId: bigint) {
|
||||
const teams = await this.collectFixtureTeamsForLeague(leagueId);
|
||||
const openCodes = new Set(
|
||||
(
|
||||
await this.prisma.marketSelection.findMany({
|
||||
where: {
|
||||
marketId,
|
||||
status: 'OPEN',
|
||||
selectionCode: { not: PLACEHOLDER_TEAM_CODE },
|
||||
},
|
||||
select: { selectionCode: true },
|
||||
})
|
||||
).map((s) => s.selectionCode),
|
||||
);
|
||||
|
||||
const result: Array<{
|
||||
teamCode: string;
|
||||
teamZh: string;
|
||||
teamEn: string;
|
||||
logoUrl: string | null;
|
||||
}> = [];
|
||||
|
||||
for (const team of teams) {
|
||||
if (openCodes.has(team.code)) continue;
|
||||
const [teamZh, teamEn] = await Promise.all([
|
||||
this.getTranslation('TEAM', team.id, 'zh-CN'),
|
||||
this.getTranslation('TEAM', team.id, 'en-US'),
|
||||
]);
|
||||
result.push({
|
||||
teamCode: team.code,
|
||||
teamZh: teamZh || team.code,
|
||||
teamEn: teamEn || team.code,
|
||||
logoUrl: team.logoUrl ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 按联赛获取或创建冠军盘,并从单场赛程同步参赛队伍 */
|
||||
async getOrCreateAndSyncForLeague(leagueId: bigint) {
|
||||
let match = await this.prisma.match.findFirst({
|
||||
@@ -263,6 +218,22 @@ export class OutrightService {
|
||||
orderBy: { id: 'asc' },
|
||||
});
|
||||
}
|
||||
const sync = await this.syncSelectionsFromLeagueFixtures(match.id);
|
||||
const data = await this.getForAdmin(match.id);
|
||||
return {
|
||||
...data,
|
||||
fixtureSyncAdded: sync.addedCount,
|
||||
fixtureSyncReopened: sync.reopenedCount,
|
||||
};
|
||||
}
|
||||
|
||||
/** 若联赛已有冠军盘,则从单场同步球队(不自动创建冠军盘) */
|
||||
async syncOutrightTeamsForLeagueIfExists(leagueId: bigint) {
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: { leagueId, isOutright: true, deletedAt: null },
|
||||
orderBy: { id: 'asc' },
|
||||
});
|
||||
if (!match) return { addedCount: 0, reopenedCount: 0 };
|
||||
return this.syncSelectionsFromLeagueFixtures(match.id);
|
||||
}
|
||||
|
||||
@@ -279,19 +250,21 @@ export class OutrightService {
|
||||
},
|
||||
});
|
||||
|
||||
// 仅当球队重新出现在单场赛程时,恢复曾被关闭的选项;不因「暂无单场」而自动关闭
|
||||
const existingCodes = new Set(existing.map((s) => s.selectionCode));
|
||||
let sortOrder = existing.reduce((max, s) => Math.max(max, s.sortOrder), -1);
|
||||
let addedCount = 0;
|
||||
let reopenedCount = 0;
|
||||
|
||||
for (const sel of existing) {
|
||||
if (fixtureCodes.has(sel.selectionCode) && sel.status === 'CLOSED') {
|
||||
await this.prisma.marketSelection.update({
|
||||
where: { id: sel.id },
|
||||
data: { status: 'OPEN' },
|
||||
});
|
||||
reopenedCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const existingCodes = new Set(existing.map((s) => s.selectionCode));
|
||||
let sortOrder = existing.reduce((max, s) => Math.max(max, s.sortOrder), -1);
|
||||
|
||||
for (const team of teams) {
|
||||
if (existingCodes.has(team.code)) continue;
|
||||
const [teamZh, teamEn] = await Promise.all([
|
||||
@@ -309,9 +282,10 @@ export class OutrightService {
|
||||
status: 'OPEN',
|
||||
},
|
||||
});
|
||||
addedCount += 1;
|
||||
}
|
||||
|
||||
return this.getForAdmin(matchId);
|
||||
return { addedCount, reopenedCount };
|
||||
}
|
||||
|
||||
private async collectFixtureTeamsForLeague(leagueId: bigint) {
|
||||
|
||||
Reference in New Issue
Block a user