feat(frontend): Implement matches
parent
45ac071a7f
commit
caaee983f2
|
@ -21,6 +21,7 @@ export type { EventSchema } from './models/EventSchema';
|
||||||
export type { EventWithPlayerSchema } from './models/EventWithPlayerSchema';
|
export type { EventWithPlayerSchema } from './models/EventWithPlayerSchema';
|
||||||
export type { EventWithPlayerSchemaList } from './models/EventWithPlayerSchemaList';
|
export type { EventWithPlayerSchemaList } from './models/EventWithPlayerSchemaList';
|
||||||
export type { GetEventPlayersResponse } from './models/GetEventPlayersResponse';
|
export type { GetEventPlayersResponse } from './models/GetEventPlayersResponse';
|
||||||
|
export type { MatchSchema } from './models/MatchSchema';
|
||||||
export type { PlayerEventRolesSchema } from './models/PlayerEventRolesSchema';
|
export type { PlayerEventRolesSchema } from './models/PlayerEventRolesSchema';
|
||||||
export type { PlayerRoleSchema } from './models/PlayerRoleSchema';
|
export type { PlayerRoleSchema } from './models/PlayerRoleSchema';
|
||||||
export type { PlayerSchema } from './models/PlayerSchema';
|
export type { PlayerSchema } from './models/PlayerSchema';
|
||||||
|
@ -28,11 +29,14 @@ export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvaila
|
||||||
export type { PutScheduleForm } from './models/PutScheduleForm';
|
export type { PutScheduleForm } from './models/PutScheduleForm';
|
||||||
export type { RoleSchema } from './models/RoleSchema';
|
export type { RoleSchema } from './models/RoleSchema';
|
||||||
export type { SetUsernameJson } from './models/SetUsernameJson';
|
export type { SetUsernameJson } from './models/SetUsernameJson';
|
||||||
|
export type { SubmitMatchJson } from './models/SubmitMatchJson';
|
||||||
export type { TeamDiscordIntegrationSchema } from './models/TeamDiscordIntegrationSchema';
|
export type { TeamDiscordIntegrationSchema } from './models/TeamDiscordIntegrationSchema';
|
||||||
export type { TeamIntegrationSchema } from './models/TeamIntegrationSchema';
|
export type { TeamIntegrationSchema } from './models/TeamIntegrationSchema';
|
||||||
export type { TeamInviteSchema } from './models/TeamInviteSchema';
|
export type { TeamInviteSchema } from './models/TeamInviteSchema';
|
||||||
export type { TeamInviteSchemaList } from './models/TeamInviteSchemaList';
|
export type { TeamInviteSchemaList } from './models/TeamInviteSchemaList';
|
||||||
export type { TeamLogsTfIntegrationSchema } from './models/TeamLogsTfIntegrationSchema';
|
export type { TeamLogsTfIntegrationSchema } from './models/TeamLogsTfIntegrationSchema';
|
||||||
|
export type { TeamMatchSchema } from './models/TeamMatchSchema';
|
||||||
|
export type { TeamMatchSchemaList } from './models/TeamMatchSchemaList';
|
||||||
export { TeamRole } from './models/TeamRole';
|
export { TeamRole } from './models/TeamRole';
|
||||||
export type { TeamSchema } from './models/TeamSchema';
|
export type { TeamSchema } from './models/TeamSchema';
|
||||||
export type { TeamWithRoleSchema } from './models/TeamWithRoleSchema';
|
export type { TeamWithRoleSchema } from './models/TeamWithRoleSchema';
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type MatchSchema = {
|
||||||
|
blueScore: number;
|
||||||
|
createdAt: string;
|
||||||
|
duration: string;
|
||||||
|
logsTfId: number;
|
||||||
|
logsTfTitle: string;
|
||||||
|
matchTime: string;
|
||||||
|
redScore: number;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type SubmitMatchJson = {
|
||||||
|
matchIds: Array<number>;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { MatchSchema } from './MatchSchema';
|
||||||
|
export type TeamMatchSchema = {
|
||||||
|
match: MatchSchema;
|
||||||
|
ourScore: number;
|
||||||
|
theirScore: number;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { TeamMatchSchema } from './TeamMatchSchema';
|
||||||
|
export type TeamMatchSchemaList = Array<TeamMatchSchema>;
|
|
@ -12,12 +12,15 @@ import type { EventSchema } from '../models/EventSchema';
|
||||||
import type { EventWithPlayerSchema } from '../models/EventWithPlayerSchema';
|
import type { EventWithPlayerSchema } from '../models/EventWithPlayerSchema';
|
||||||
import type { EventWithPlayerSchemaList } from '../models/EventWithPlayerSchemaList';
|
import type { EventWithPlayerSchemaList } from '../models/EventWithPlayerSchemaList';
|
||||||
import type { GetEventPlayersResponse } from '../models/GetEventPlayersResponse';
|
import type { GetEventPlayersResponse } from '../models/GetEventPlayersResponse';
|
||||||
|
import type { MatchSchema } from '../models/MatchSchema';
|
||||||
import type { PlayerSchema } from '../models/PlayerSchema';
|
import type { PlayerSchema } from '../models/PlayerSchema';
|
||||||
import type { PutScheduleForm } from '../models/PutScheduleForm';
|
import type { PutScheduleForm } from '../models/PutScheduleForm';
|
||||||
import type { SetUsernameJson } from '../models/SetUsernameJson';
|
import type { SetUsernameJson } from '../models/SetUsernameJson';
|
||||||
|
import type { SubmitMatchJson } from '../models/SubmitMatchJson';
|
||||||
import type { TeamIntegrationSchema } from '../models/TeamIntegrationSchema';
|
import type { TeamIntegrationSchema } from '../models/TeamIntegrationSchema';
|
||||||
import type { TeamInviteSchema } from '../models/TeamInviteSchema';
|
import type { TeamInviteSchema } from '../models/TeamInviteSchema';
|
||||||
import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList';
|
import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList';
|
||||||
|
import type { TeamMatchSchemaList } from '../models/TeamMatchSchemaList';
|
||||||
import type { TeamSchema } from '../models/TeamSchema';
|
import type { TeamSchema } from '../models/TeamSchema';
|
||||||
import type { UpdateEventJson } from '../models/UpdateEventJson';
|
import type { UpdateEventJson } from '../models/UpdateEventJson';
|
||||||
import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse';
|
import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse';
|
||||||
|
@ -289,6 +292,79 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* submit_match <PUT>
|
||||||
|
* @param requestBody
|
||||||
|
* @returns void
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public submitMatch(
|
||||||
|
requestBody?: SubmitMatchJson,
|
||||||
|
): CancelablePromise<void> {
|
||||||
|
return this.httpRequest.request({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/match/',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Unprocessable Content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* get_match <GET>
|
||||||
|
* @param matchId
|
||||||
|
* @returns MatchSchema OK
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public getApiMatchIdMatchId(
|
||||||
|
matchId: number,
|
||||||
|
): CancelablePromise<MatchSchema> {
|
||||||
|
return this.httpRequest.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/match/id/{match_id}',
|
||||||
|
path: {
|
||||||
|
'match_id': matchId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Unprocessable Content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* get_matches_for_player_teams <GET>
|
||||||
|
* @returns TeamMatchSchemaList OK
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public getMatchesForPlayerTeams(): CancelablePromise<TeamMatchSchemaList> {
|
||||||
|
return this.httpRequest.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/match/player',
|
||||||
|
errors: {
|
||||||
|
422: `Unprocessable Content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* get_matches_for_team <GET>
|
||||||
|
* @param teamId
|
||||||
|
* @returns TeamMatchSchemaList OK
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public getMatchesForTeam(
|
||||||
|
teamId: number,
|
||||||
|
): CancelablePromise<TeamMatchSchemaList> {
|
||||||
|
return this.httpRequest.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/match/team/{team_id}',
|
||||||
|
path: {
|
||||||
|
'team_id': teamId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Unprocessable Content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* get <GET>
|
* get <GET>
|
||||||
* @param windowStart
|
* @param windowStart
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useMatchesStore } from "@/stores/matches";
|
||||||
|
import { DialogClose, DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from "radix-vue";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const matchesStore = useMatchesStore();
|
||||||
|
|
||||||
|
const urlsText = ref("");
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
const ids = urlsText.value.split("\n")
|
||||||
|
.map((url) => {
|
||||||
|
const matchId = url.match(/logs\.tf\/(\d+)/);
|
||||||
|
return matchId ? Number(matchId[1]) : NaN;
|
||||||
|
})
|
||||||
|
.filter((id) => !isNaN(id));
|
||||||
|
|
||||||
|
matchesStore.submitMatches(ids);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogRoot>
|
||||||
|
<DialogTrigger>
|
||||||
|
<i class="bi bi-file-earmark-plus-fill margin" />
|
||||||
|
Submit logs.tf matches
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay class="dialog-overlay" />
|
||||||
|
<DialogContent>
|
||||||
|
<DialogTitle>Submit logs.tf matches</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<p>
|
||||||
|
Enter up to 10 logs.tf URLs (or match IDs) to submit them. This
|
||||||
|
allows you to track your match stats and view them later.
|
||||||
|
</p>
|
||||||
|
</DialogDescription>
|
||||||
|
<div class="form-group margin">
|
||||||
|
<h3>logs.tf URLs</h3>
|
||||||
|
<textarea
|
||||||
|
v-model="urlsText"
|
||||||
|
placeholder="Paste logs.tf URLs here (limit: 10)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<DialogClose class="accent" aria-label="Close" @click="submit">
|
||||||
|
<i class="bi bi-check" />
|
||||||
|
Submit
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</DialogPortal>
|
||||||
|
</DialogRoot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
[role="dialog"] {
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -12,6 +12,7 @@ import TeamSettingsGeneralView from "@/views/TeamSettings/GeneralView.vue";
|
||||||
import TeamSettingsIntegrationsView from "@/views/TeamSettings/IntegrationsView.vue";
|
import TeamSettingsIntegrationsView from "@/views/TeamSettings/IntegrationsView.vue";
|
||||||
import TeamSettingsInvitesView from "@/views/TeamSettings/InvitesView.vue";
|
import TeamSettingsInvitesView from "@/views/TeamSettings/InvitesView.vue";
|
||||||
import UserSettingsView from "@/views/UserSettingsView.vue";
|
import UserSettingsView from "@/views/UserSettingsView.vue";
|
||||||
|
import MatchesView from "@/views/MatchesView.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
@ -78,6 +79,11 @@ const router = createRouter({
|
||||||
name: "user-settings",
|
name: "user-settings",
|
||||||
component: UserSettingsView,
|
component: UserSettingsView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/matches",
|
||||||
|
name: "matches",
|
||||||
|
component: MatchesView,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { MatchSchema, TeamMatchSchema } from "@/client";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useClientStore } from "./client";
|
||||||
|
|
||||||
|
export const useMatchesStore = defineStore("matches", () => {
|
||||||
|
const clientStore = useClientStore();
|
||||||
|
const client = clientStore.client;
|
||||||
|
|
||||||
|
const matches = ref<{ [id: number]: MatchSchema }>({ });
|
||||||
|
|
||||||
|
const teamMatches = ref<{ [id: number]: TeamMatchSchema }>({ });
|
||||||
|
|
||||||
|
function fetchMatches() {
|
||||||
|
return clientStore.call(
|
||||||
|
fetchMatches.name,
|
||||||
|
() => client.default.getMatchesForPlayerTeams(),
|
||||||
|
(response) => {
|
||||||
|
response.forEach((match) => {
|
||||||
|
matches.value[match.match.logsTfId] = match.match;
|
||||||
|
teamMatches.value[match.match.logsTfId] = match;
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitMatches(logsTfIds: number[]) {
|
||||||
|
return client.default.submitMatch({ matchIds: logsTfIds });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
matches,
|
||||||
|
teamMatches,
|
||||||
|
fetchMatches,
|
||||||
|
submitMatches,
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AddMatchDialog from "@/components/AddMatchDialog.vue";
|
||||||
|
import { useMatchesStore } from "@/stores/matches";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
|
const matchesStore = useMatchesStore();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
matchesStore.fetchMatches();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<div class="header">
|
||||||
|
<h1>
|
||||||
|
<i class="bi bi-trophy-fill margin"></i>
|
||||||
|
Matches you've played
|
||||||
|
</h1>
|
||||||
|
<div class="button-group">
|
||||||
|
<AddMatchDialog />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>RED</th>
|
||||||
|
<th>BLU</th>
|
||||||
|
<th>Match Date</th>
|
||||||
|
<th>logs.tf URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="match in matchesStore.matches" :key="match.logsTfId">
|
||||||
|
<td>{{ match.redScore }}</td>
|
||||||
|
<td>{{ match.blueScore }}</td>
|
||||||
|
<td>{{ match.matchTime }}</td>
|
||||||
|
<td>
|
||||||
|
<a :href="`https://logs.tf/${match.logsTfId}`" target="_blank">
|
||||||
|
#{{ match.logsTfId }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue