feat(tour): added tournament history page :D
This commit is contained in:
parent
75f3c2a769
commit
aca2dbd070
13 changed files with 245 additions and 4 deletions
|
|
@ -34,6 +34,12 @@ import {
|
|||
* @interface TournamentData200ResponsePayloadData
|
||||
*/
|
||||
export interface TournamentData200ResponsePayloadData {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof TournamentData200ResponsePayloadData
|
||||
*/
|
||||
playerCount: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
|
@ -64,6 +70,7 @@ export interface TournamentData200ResponsePayloadData {
|
|||
* Check if a given object implements the TournamentData200ResponsePayloadData interface.
|
||||
*/
|
||||
export function instanceOfTournamentData200ResponsePayloadData(value: object): value is TournamentData200ResponsePayloadData {
|
||||
if (!('playerCount' in value) || value['playerCount'] === undefined) return false;
|
||||
if (!('owner' in value) || value['owner'] === undefined) return false;
|
||||
if (!('users' in value) || value['users'] === undefined) return false;
|
||||
if (!('games' in value) || value['games'] === undefined) return false;
|
||||
|
|
@ -81,6 +88,7 @@ export function TournamentData200ResponsePayloadDataFromJSONTyped(json: any, ign
|
|||
}
|
||||
return {
|
||||
|
||||
'playerCount': json['playerCount'],
|
||||
'owner': json['owner'],
|
||||
'users': ((json['users'] as Array<any>).map(TournamentData200ResponsePayloadDataUsersInnerFromJSON)),
|
||||
'games': ((json['games'] as Array<any>).map(PongHistory200ResponsePayloadDataInnerFromJSON)),
|
||||
|
|
@ -99,6 +107,7 @@ export function TournamentData200ResponsePayloadDataToJSONTyped(value?: Tourname
|
|||
|
||||
return {
|
||||
|
||||
'playerCount': value['playerCount'],
|
||||
'owner': value['owner'],
|
||||
'users': ((value['users'] as Array<any>).map(TournamentData200ResponsePayloadDataUsersInnerToJSON)),
|
||||
'games': ((value['games'] as Array<any>).map(PongHistory200ResponsePayloadDataInnerToJSON)),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ import { mapValues } from '../runtime';
|
|||
* @interface TournamentList200ResponsePayloadDataInner
|
||||
*/
|
||||
export interface TournamentList200ResponsePayloadDataInner {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof TournamentList200ResponsePayloadDataInner
|
||||
*/
|
||||
playerCount: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
|
@ -43,6 +49,7 @@ export interface TournamentList200ResponsePayloadDataInner {
|
|||
* Check if a given object implements the TournamentList200ResponsePayloadDataInner interface.
|
||||
*/
|
||||
export function instanceOfTournamentList200ResponsePayloadDataInner(value: object): value is TournamentList200ResponsePayloadDataInner {
|
||||
if (!('playerCount' in value) || value['playerCount'] === undefined) return false;
|
||||
if (!('id' in value) || value['id'] === undefined) return false;
|
||||
if (!('owner' in value) || value['owner'] === undefined) return false;
|
||||
if (!('time' in value) || value['time'] === undefined) return false;
|
||||
|
|
@ -59,6 +66,7 @@ export function TournamentList200ResponsePayloadDataInnerFromJSONTyped(json: any
|
|||
}
|
||||
return {
|
||||
|
||||
'playerCount': json['playerCount'],
|
||||
'id': json['id'],
|
||||
'owner': json['owner'],
|
||||
'time': json['time'],
|
||||
|
|
@ -76,6 +84,7 @@ export function TournamentList200ResponsePayloadDataInnerToJSONTyped(value?: Tou
|
|||
|
||||
return {
|
||||
|
||||
'playerCount': value['playerCount'],
|
||||
'id': value['id'],
|
||||
'owner': value['owner'],
|
||||
'time': value['time'],
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import './profile/profile.ts'
|
|||
import './logout/logout.ts'
|
||||
import './pongHistory/pongHistory.ts'
|
||||
import './tttHistory/tttHistory.ts'
|
||||
import './tourHistory/tourHistory.ts'
|
||||
|
||||
// ---- Initial load ----
|
||||
setTitle("");
|
||||
|
|
|
|||
156
frontend/src/pages/tourHistory/tourHistory.ts
Normal file
156
frontend/src/pages/tourHistory/tourHistory.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import { addRoute, navigateTo, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
|
||||
import pageAll from './tourHistoryAll.html?raw';
|
||||
import pageSingle from './tourHistorySingle.html?raw';
|
||||
import { isNullish } from "@app/utils";
|
||||
import client from "@app/api";
|
||||
import { showError, showInfo } from "@app/toast";
|
||||
import { getUser } from "@app/auth";
|
||||
|
||||
|
||||
function getHHMM(d: Date): string {
|
||||
let h = d.getHours();
|
||||
let m = d.getMinutes();
|
||||
return `${h < 9 ? '0' : ''}${h}:${m < 9 ? '0' : ''}${m}`
|
||||
}
|
||||
|
||||
async function tourHistoryAll(_url: string, args: RouteHandlerParams): Promise<RouteHandlerReturn> {
|
||||
|
||||
let data = await client.tournamentList();
|
||||
if (data.kind !== 'success') {
|
||||
showError(`Failed to fetch data: ${data.msg}`);
|
||||
return {
|
||||
html: "", postInsert: async () => navigateTo('/')
|
||||
}
|
||||
}
|
||||
return {
|
||||
html: pageAll, postInsert: async (app) => {
|
||||
const tourDiv = app?.querySelector('#tourList');
|
||||
if (!tourDiv || !app)
|
||||
return showError('Fatal Error');
|
||||
let childrens = await Promise.all(data.payload.data.map(async (tour) => {
|
||||
const ownerUser = await client.getUser({ user: tour.owner });
|
||||
const ownerUserName = ownerUser.kind === 'success' ? ownerUser.payload.name : "User Not Found";
|
||||
return {
|
||||
...tour,
|
||||
ownerName: ownerUserName,
|
||||
}
|
||||
}))
|
||||
childrens.sort((l, r) => ((new Date(r.time).getTime()) - (new Date(l.time)).getTime()));
|
||||
|
||||
childrens.map(tour => {
|
||||
let time = new Date(tour.time);
|
||||
let owner = { id: tour.owner, name: tour.ownerName };
|
||||
let id = tour.id;
|
||||
let playerCount = tour.playerCount;
|
||||
|
||||
const eventItem = document.createElement('div');
|
||||
eventItem.classList.add(
|
||||
'bg-white',
|
||||
'p-4',
|
||||
'rounded-lg',
|
||||
'shadow-md',
|
||||
'mb-4',
|
||||
'flex',
|
||||
'justify-between',
|
||||
'items-center',
|
||||
);
|
||||
|
||||
eventItem.innerHTML = `
|
||||
<div class="text-left">
|
||||
<p class="text-lg font-semibold text-gray-700"><strong>Time:</strong> ${time.toDateString()} ${getHHMM(time)}</p>
|
||||
<p class="text-sm text-gray-500"><strong>Created By:</strong> ${owner.name} </p>
|
||||
<p class="text-sm text-gray-500"><strong>Player Count:</strong> ${playerCount}</p>
|
||||
</div>
|
||||
<button class="bg-blue-500 text-white px-3 py-2 rounded-lg hover:bg-blue-600" onclick="navigateTo('/tour/${id}')">View</button>`;
|
||||
return eventItem;
|
||||
}).forEach(v => tourDiv.appendChild(v));
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function tourHistorySingle(_url: string, args: RouteHandlerParams): Promise<RouteHandlerReturn> {
|
||||
if (isNullish(args.tourid)) {
|
||||
showError(`No Tournament Specified`);
|
||||
return {
|
||||
html: "", postInsert: async () => navigateTo('/tour')
|
||||
}
|
||||
}
|
||||
|
||||
let data = await client.tournamentData({ id: args.tourid });
|
||||
if (data.kind !== 'success') {
|
||||
showError(`Failed to fetch data: ${data.msg}`);
|
||||
return {
|
||||
html: "", postInsert: async () => navigateTo('/')
|
||||
}
|
||||
}
|
||||
|
||||
let d = data.payload.data;
|
||||
let user = getUser();
|
||||
if (user === null) {
|
||||
showError(`You must be logged in`);
|
||||
return {
|
||||
html: "", postInsert: async () => navigateTo('/')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
html: pageSingle, postInsert: async (app) => {
|
||||
const tourOwner = app?.querySelector<HTMLSpanElement>('#tourOwner');
|
||||
const tourTime = app?.querySelector<HTMLSpanElement>('#tourTime');
|
||||
const tourCount = app?.querySelector<HTMLSpanElement>('#tourCount');
|
||||
const tourPlayerList = app?.querySelector<HTMLTableElement>('#tourPlayerList');
|
||||
const tourGames = app?.querySelector<HTMLDivElement>('#tourGames');
|
||||
if (!tourOwner || !tourTime || !tourCount || !tourPlayerList || !tourGames || !app)
|
||||
return showError('Fatal Error');
|
||||
|
||||
|
||||
d.users.sort((l, r) => r.score - l.score);
|
||||
let time = new Date(d.time);
|
||||
|
||||
const medals = ["🥇", "🥈", "🥉"];
|
||||
tourPlayerList.innerHTML = d.users.map((player, idx) =>
|
||||
`<tr class="${player.id === user.id ? "bg-amber-400 hover:bg-amber-500" : "hover:bg-gray-50"}" key="${player.id}">
|
||||
<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-semibold min-w-100px">${idx < medals.length ? `<span class="font-lg">${medals[idx]}</span>` : ''}${player.nickname}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-bold min-w-100px">${player.score}</td></tr>`)
|
||||
.join("");
|
||||
|
||||
tourOwner.innerText = await (async () => {
|
||||
let req = await client.getUser({ user: d.owner });
|
||||
if (req.kind === 'success')
|
||||
return req.payload.name;
|
||||
return 'Unknown User';
|
||||
})();
|
||||
tourCount.innerText = d.playerCount.toString();
|
||||
tourTime.innerText = `${time.toDateString()} ${getHHMM(time)}`;
|
||||
|
||||
let gameElement = d.games.map(g => {
|
||||
let rdate = Date.parse(g.date);
|
||||
if (Number.isNaN(rdate)) return undefined;
|
||||
let date = new Date(rdate);
|
||||
const e = document.createElement('div');
|
||||
let color = 'bg-slate-300';
|
||||
e.className =
|
||||
'grid grid-cols-[1fr_auto_1fr] items-center rounded-lg px-4 py-3 ' + color;
|
||||
|
||||
e.innerHTML = `
|
||||
<div class="text-right">
|
||||
<div class="font-semibold ${g.outcome === 'winL' ? 'text-green-600' : 'text-red-600'}">${g.left.name}</div>
|
||||
<div class="text-lg ${g.outcome === 'winL' ? 'text-green-600' : 'text-red-600'}">${g.left.score}</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm text-gray-800 px-4 whitespace-nowrap">${date.toDateString()}<br />${getHHMM(date)}</div>
|
||||
|
||||
<div class="text-left">
|
||||
<div class="font-semibold ${g.outcome === 'winR' ? 'text-green-600' : 'text-red-600'}">${g.right.name}</div>
|
||||
<div class="text-lg ${g.outcome === 'winR' ? 'text-green-600' : 'text-red-600'}">${g.right.score}</div>
|
||||
</div>`;
|
||||
return e;
|
||||
}).filter(v => !isNullish(v));
|
||||
gameElement.forEach(e => tourGames.appendChild(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addRoute('/tour', tourHistoryAll);
|
||||
addRoute('/tour/:tourid', tourHistorySingle);
|
||||
9
frontend/src/pages/tourHistory/tourHistoryAll.html
Normal file
9
frontend/src/pages/tourHistory/tourHistoryAll.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<div class="fixed inset-0 flex items-center justify-center bg-[#43536b]">
|
||||
<div
|
||||
class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-200 w-212.5 p-6 rounded-xl shadow-2xl text-center z-50">
|
||||
<h2 class="text-2xl font-bold text-center mb-4 text-gray-700">
|
||||
List of All Tournaments
|
||||
</h2>
|
||||
<div id="tourList" class="max-w-3xl mx-auto space-y-2 max-h-[50dvh] overflow-scroll"></div>
|
||||
</div>
|
||||
</div>
|
||||
37
frontend/src/pages/tourHistory/tourHistorySingle.html
Normal file
37
frontend/src/pages/tourHistory/tourHistorySingle.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<div class="fixed inset-0 flex items-center justify-center bg-[#43536b]">
|
||||
<div
|
||||
class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-200 w-212.5 p-6 rounded-xl shadow-2xl text-center z-50">
|
||||
<h2 class="text-2xl font-bold text-center mb-4 text-gray-700">
|
||||
Tournament <span id="tour-date"> </span>
|
||||
</h2>
|
||||
<div id="tourInfo" class="bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<p class="text-lg"> <strong class="text-gray-700">Created By:<strong> <span class="text-amber-500"
|
||||
id="tourOwner"></span></p>
|
||||
<p class="text-lg"> <strong class="text-gray-700">Finished At:<strong> <span class="text-amber-500"
|
||||
id="tourTime"></span></p>
|
||||
<p class="text-lg"> <strong class="text-gray-700">Players:<strong> <span class="text-amber-500"
|
||||
id="tourCount"></span></p>
|
||||
</div>
|
||||
<div class="max-h-[320px] overflow-y-auto overflow-x-auto border rounded-lg mb-4">
|
||||
<table class="min-w-full border-collapse table-fixed">
|
||||
<thead class="sticky top-0 z-10 bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-center text-lg font-semibold text-gray-700 border-b">
|
||||
Name
|
||||
</th>
|
||||
<th class="px-4 py-2 text-center text-lg font-semibold text-gray-700 border-b">
|
||||
Score
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody id="tourPlayerList">
|
||||
<!-- rows -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="tourGames" class="max-w-3xl mx-auto space-y-2 max-h-[25dvh] overflow-scroll">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -5,8 +5,6 @@
|
|||
src: url("/fonts/DejaVuSansMono.woff2") format("woff2");
|
||||
}
|
||||
|
||||
|
||||
|
||||
.displaybox {
|
||||
@apply
|
||||
fixed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue