diff --git a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.html b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.html index 73cf064f..dd8f8425 100644 --- a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.html +++ b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.html @@ -5,6 +5,10 @@
+
+ {{team.rank}} +
+ diff --git a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.ts b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.ts index 53e27096..6af56b66 100644 --- a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.ts +++ b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-players/game-center-players.component.ts @@ -31,7 +31,7 @@ export class GameCenterPlayersComponent implements OnInit { private async load() { this.isLoading = true; - this.results = await this.adminService.getGameCenterTeams(this.gameId!); + this.results = await this.adminService.getGameCenterTeams(this.gameId!, { sort: "rank" }); this.isLoading = false; } } diff --git a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html index d6b15707..b578da2a 100644 --- a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html +++ b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html @@ -318,7 +318,7 @@

Execution

-
+
The game's open date must be less than its close date.
diff --git a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts index 0a4187c7..4ada0005 100644 --- a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts +++ b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts @@ -35,6 +35,7 @@ export class GameEditorComponent implements AfterViewInit, OnChanges { viewing = 1; showCertificateInfo = false; showExternalGameFields = false; + protected executionRangeInvalid = false; protected specCount = 0; // store unique values of each game field with their frequencies for ordered suggestion lists @@ -184,8 +185,16 @@ export class GameEditorComponent implements AfterViewInit, OnChanges { if (game.minTeamSize > game.maxTeamSize) return false; - if (game.gameStart > game.gameEnd) + this.executionRangeInvalid = false; + const gameStart = game.gameStart ? new Date(game.gameStart) : new Date(0); + const gameEnd = game.gameEnd ? new Date(game.gameEnd) : new Date(0); + if (gameStart.getFullYear() > 1 && gameEnd?.getFullYear() > 1 && game.gameStart > game.gameEnd) { + this.executionRangeInvalid = true; return false; + } + + game.gameStart = gameStart; + game.gameEnd = gameEnd; if (game.registrationType == GameRegistrationType.open && game.registrationOpen > game.registrationClose) return false; diff --git a/projects/gameboard-ui/src/app/api/admin.models.ts b/projects/gameboard-ui/src/app/api/admin.models.ts index c993aafd..955d421f 100644 --- a/projects/gameboard-ui/src/app/api/admin.models.ts +++ b/projects/gameboard-ui/src/app/api/admin.models.ts @@ -76,6 +76,10 @@ export interface GameCenterContext { pointsAvailable: number; } +export interface GameCenterTeamsRequestArgs { + sort: "name" | "rank" | "timeRemaining" | "timeSinceStart"; +} + export interface GameCenterTeamsResults { teams: PagedArray; } diff --git a/projects/gameboard-ui/src/app/api/admin.service.ts b/projects/gameboard-ui/src/app/api/admin.service.ts index f5c82843..4b822d6d 100644 --- a/projects/gameboard-ui/src/app/api/admin.service.ts +++ b/projects/gameboard-ui/src/app/api/admin.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, firstValueFrom, map, tap } from 'rxjs'; import { ApiUrlService } from '@/services/api-url.service'; -import { GameCenterContext, GameCenterTeamsResults, GetAppActiveChallengesResponse, GetAppActiveTeamsResponse, GetSiteOverviewStatsResponse, SendAnnouncement } from './admin.models'; +import { GameCenterContext, GameCenterTeamsRequestArgs, GameCenterTeamsResults, GetAppActiveChallengesResponse, GetAppActiveTeamsResponse, GetSiteOverviewStatsResponse, SendAnnouncement } from './admin.models'; import { PlayerMode } from './player-models'; import { DateTime } from 'luxon'; @@ -48,8 +48,8 @@ export class AdminService { )); } - async getGameCenterTeams(gameId: string): Promise { - return firstValueFrom(this.http.get(this.apiUrl.build(`admin/games/${gameId}/game-center/teams`)).pipe( + async getGameCenterTeams(gameId: string, args: GameCenterTeamsRequestArgs): Promise { + return firstValueFrom(this.http.get(this.apiUrl.build(`admin/games/${gameId}/game-center/teams`, args)).pipe( tap(results => { for (const team of results.teams.items) { if (team.registeredOn) diff --git a/projects/gameboard-ui/src/app/api/support-models.ts b/projects/gameboard-ui/src/app/api/support-models.ts index 36b9c01a..8cee9fe0 100644 --- a/projects/gameboard-ui/src/app/api/support-models.ts +++ b/projects/gameboard-ui/src/app/api/support-models.ts @@ -27,6 +27,7 @@ export interface Ticket { label: string; selfCreated: boolean; staffCreated: boolean; + timeTilSessionEndMs?: number; created: Date; lastUpdated: Date; diff --git a/projects/gameboard-ui/src/app/home/landing/landing.component.html b/projects/gameboard-ui/src/app/home/landing/landing.component.html index 532a3da2..2b0190cf 100644 --- a/projects/gameboard-ui/src/app/home/landing/landing.component.html +++ b/projects/gameboard-ui/src/app/home/landing/landing.component.html @@ -26,6 +26,18 @@

Featured Games

+ +
+ +
+

Ongoing Games

+
+ + + +
+
+
@@ -113,6 +125,12 @@

Open Game

+ +
+ Loading ongoing games... +
+
+
Loading games... diff --git a/projects/gameboard-ui/src/app/home/landing/landing.component.ts b/projects/gameboard-ui/src/app/home/landing/landing.component.ts index 7a4cda3b..1c288f5e 100644 --- a/projects/gameboard-ui/src/app/home/landing/landing.component.ts +++ b/projects/gameboard-ui/src/app/home/landing/landing.component.ts @@ -18,6 +18,7 @@ import { GameService } from '../../api/game.service'; export class LandingComponent implements OnInit { refresh$ = new BehaviorSubject(true); featured$: Observable; + ongoing$: Observable; past$: Observable; present$: Observable; future$: Observable; @@ -38,22 +39,26 @@ export class LandingComponent implements OnInit { ) { this.featured$ = this.refresh$.pipe( debounceTime(400), - switchMap(() => api.list({ isFeatured: true, filter: [], term: this.searchText })), + switchMap(() => api.list({ isFeatured: true, term: this.searchText })), tap(g => { if (g.length > 0) { this.showSearchBar = true; } }), ); - this.past$ = this.refresh$.pipe( + this.ongoing$ = this.refresh$.pipe( debounceTime(400), - switchMap(() => api.listGrouped({ filter: ['past', "competitive"], term: this.searchText })), - tap(g => { if (g.length > 0) { this.showSearchBar = true; } }), - ); + switchMap(() => api.list({ isOngoing: true, term: this.searchText })) + ), + this.past$ = this.refresh$.pipe( + debounceTime(400), + switchMap(() => api.listGrouped({ filter: ['past', "competitive"], isOngoing: false, term: this.searchText })), + tap(g => { if (g.length > 0) { this.showSearchBar = true; } }), + ); this.present$ = this.refresh$.pipe( debounceTime(200), - switchMap(() => api.list({ filter: ["present", "competitive"], term: this.searchText })), + switchMap(() => api.list({ filter: ["present", "competitive"], isOngoing: false, term: this.searchText })), tap(g => { if (g.length > 0) { this.showSearchBar = true; } }) ); this.future$ = this.refresh$.pipe( debounceTime(300), - switchMap(() => api.listGrouped({ filter: ['future', "competitive"], term: this.searchText })), + switchMap(() => api.listGrouped({ filter: ['future', "competitive"], isOngoing: false, term: this.searchText })), tap(g => { if (g.length > 0) { this.showSearchBar = true; } }) ); } diff --git a/projects/gameboard-ui/src/app/services/router.service.ts b/projects/gameboard-ui/src/app/services/router.service.ts index 6696f48f..176dc18b 100644 --- a/projects/gameboard-ui/src/app/services/router.service.ts +++ b/projects/gameboard-ui/src/app/services/router.service.ts @@ -60,7 +60,7 @@ export class RouterService implements OnDestroy { } public getObserveChallengeUrl(gameId: string, challengeId: string) { - return `/admin/observer/challenges/${gameId}?search=${challengeId}`; + return `admin/observer/challenges/${gameId}?search=${challengeId}`; } public getObserveTeamsUrl(gameId: string, teamId: string) { diff --git a/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts b/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts index 368380e2..eb590165 100644 --- a/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts +++ b/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts @@ -8,6 +8,7 @@ export interface TicketSupportToolsContext { challenge?: SimpleEntity; player?: SimpleEntity; game?: SimpleEntity; + timeTilSessionEndMs?: number; team: { id: string, name: string, @@ -20,7 +21,7 @@ export interface TicketSupportToolsContext { styleUrls: ['./ticket-support-tools.component.scss'], template: `
    -
  • +
  • Observe
    • @@ -72,6 +73,7 @@ export class TicketSupportToolsComponent implements OnInit { protected hasGameContext = false; protected challengeStateUrl?: string; protected gameboardUrl?: string; + protected isActiveSession = false; protected observeChallengeUrl?: string; protected observeTeamUrl?: string; protected playerAdminUrl?: string; @@ -100,6 +102,7 @@ export class TicketSupportToolsComponent implements OnInit { } } + this.isActiveSession = !!this.context?.timeTilSessionEndMs && this.context.timeTilSessionEndMs > 0; this.hasGameContext = hasGame || !!this.challengeStateUrl || !!this.gameboardUrl || !!this.playerAdminUrl; } diff --git a/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts b/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts index 3d0780fd..f9561766 100644 --- a/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts +++ b/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts @@ -135,6 +135,7 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy { challenge: t.challenge ? { id: t.challengeId, name: t.challenge?.name } : undefined, game: hasGame ? { id: t.player!.gameId, name: t.player!.gameName } : undefined, player: hasPlayer ? { id: t.playerId, name: t.player!.approvedName } : undefined, + timeTilSessionEndMs: t.timeTilSessionEndMs, team: { id: t.teamId, name: t.teamName,