286 lines
6.8 KiB
Vue
286 lines
6.8 KiB
Vue
<template>
|
|
<div class="d-flex flex-column">
|
|
<v-select class="date-selector" v-model="dateSelect" variant="underlined" :items="dateItems">
|
|
</v-select>
|
|
<div class="stat-bar d-flex flex-row justify-space-around">
|
|
<div class="d-flex flex-column">
|
|
<v-label class="title">Completed Games</v-label>
|
|
<v-label>{{ gameStats.Complete }}</v-label>
|
|
</div>
|
|
<div class="d-flex flex-column">
|
|
<v-label class="title">Postponed Games</v-label>
|
|
<v-label>{{ gameStats.Postponed }}</v-label>
|
|
</div>
|
|
<div class="d-flex flex-column">
|
|
<v-label class="title">Events / Fixes</v-label>
|
|
<v-label>{{ gameStats.Events }} / {{ gameStats.Fixes }}</v-label>
|
|
</div>
|
|
<div class="d-flex flex-column">
|
|
<v-label class="title">Total / Average EB</v-label>
|
|
<v-label
|
|
>{{ Math.floor(gameStats.TotalEB) }} / {{ Math.floor(gameStats.AverageEB) }}</v-label
|
|
>
|
|
</div>
|
|
<div class="d-flex flex-column">
|
|
<v-label class="title">Total / Average IP</v-label>
|
|
<v-label
|
|
>{{ Math.floor(gameStats.TotalIP) }} / {{ Math.floor(gameStats.AverageIP) }}</v-label
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="role-table d-flex flex-row">
|
|
<v-table class="flex-1-1">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-left">Role</th>
|
|
<th class="text-left">
|
|
Characters
|
|
</th>
|
|
<th class="text-left">App Count</th>
|
|
<th class="text-left">Games Played</th>
|
|
<th class="text-left">Pick Rate %</th>
|
|
<th class="text-left">% Games With Role</th>
|
|
<th cladd="text-ledt">Apps Per Game</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(roleStat, roleName) in roleStats">
|
|
<td>{{ roleName }}</td>
|
|
<td class="text-left">
|
|
{{ roleStat.active }}
|
|
</td>
|
|
<td class="text-left">{{ roleStat.apps }}</td>
|
|
<td class="text-left">{{ roleStat.picks }}</td>
|
|
<td class="text-left">{{ Math.floor((roleStat.picks / roleStat.apps) * 100) }}%</td>
|
|
<td class="text-left">
|
|
{{ Math.floor((roleStat.picks / gameStats.Complete) * 100) }}%
|
|
</td>
|
|
<td class="text-left">
|
|
{{ Math.round((roleStat.apps / gameStats.Complete) * 100) / 100 }}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</v-table>
|
|
<v-sheet class="chart d-flex flex-column">
|
|
<v-select v-model="chartSelect" :items="chartItems"></v-select>
|
|
<canvas id="piechart"></canvas>
|
|
<div class="flex-1-1"></div>
|
|
</v-sheet>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.date-selector {
|
|
margin-left: 15px;
|
|
margin-right: 15px;
|
|
}
|
|
.graph-selector {
|
|
margin-left: 15px;
|
|
margin-right: 15px;
|
|
}
|
|
.stat-bar {
|
|
padding: 10px;
|
|
margin-left: 15px;
|
|
margin-right: 15px;
|
|
margin-bottom: 20px;
|
|
.title {
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
.role-table {
|
|
margin-left: 15px;
|
|
margin-right: 15px;
|
|
}
|
|
.chart {
|
|
margin-left: 20px;
|
|
width: 25%;
|
|
width: 450px;
|
|
}
|
|
</style>
|
|
|
|
<script setup lang="ts">
|
|
import { GameStats, RoleStats } from '../types'
|
|
import { onMounted, watch, ref } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import Chart from 'chart.js/auto'
|
|
import axios from 'axios'
|
|
|
|
type ChartType = 'gametypes' | 'apps' | 'picks' | 'active'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const dateSelect = ref(-1)
|
|
const dateItems = buildDateItems()
|
|
const chartSelect = ref<ChartType>('active')
|
|
const chartItems = [
|
|
{ title: 'Game Types', value: 'gametypes' },
|
|
{ title: 'Active Characters', value: 'active' },
|
|
{ title: 'Role Picks', value: 'picks' },
|
|
{ title: 'Role Applications', value: 'apps' }
|
|
]
|
|
const gameStats = ref<GameStats>({
|
|
Complete: 0,
|
|
Postponed: 0,
|
|
Pending: 0,
|
|
Fixes: 0,
|
|
Events: 0,
|
|
AverageIP: 0,
|
|
AverageEB: 0,
|
|
TotalIP: 0,
|
|
TotalEB: 0
|
|
})
|
|
const roleStats = ref<RoleStats>({
|
|
Fixer: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Tech: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Medtech: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Media: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Netrunner: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Solo: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Nomad: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Exec: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Lawman: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
},
|
|
Rocker: {
|
|
apps: 0,
|
|
picks: 0,
|
|
active: 0
|
|
}
|
|
})
|
|
|
|
let chart: Chart<'pie', number[], string>
|
|
|
|
async function loadData() {
|
|
const gameStatsResponse = await axios.post('/api/serverstats/gamestats', {
|
|
monthId: dateSelect.value
|
|
})
|
|
gameStats.value = gameStatsResponse.data
|
|
|
|
const roleStatsResponse = await axios.post('/api/serverstats/rolestats', {
|
|
monthId: dateSelect.value
|
|
})
|
|
roleStats.value = roleStatsResponse.data
|
|
}
|
|
|
|
function buildDateItems() {
|
|
const items = [{ title: 'All Time', value: -1 }]
|
|
|
|
const date = new Date()
|
|
while (date.getUTCFullYear() != 2023 || date.getUTCMonth() != 0) {
|
|
items.push(buildDateEntry(date))
|
|
date.setUTCMonth(date.getUTCMonth() - 1)
|
|
}
|
|
items.push(buildDateEntry(date))
|
|
|
|
return items
|
|
}
|
|
|
|
function buildDateEntry(date: Date) {
|
|
const monthId = dateToMonthId(date)
|
|
return {
|
|
title: date.toLocaleString('en-us', { month: 'short', year: 'numeric' }),
|
|
value: monthId
|
|
}
|
|
}
|
|
|
|
function dateToMonthId(date: Date) {
|
|
return (date.getUTCFullYear() - 2023) * 12 + date.getUTCMonth()
|
|
}
|
|
|
|
function updateChart(stats: RoleStats, tag: ChartType) {
|
|
if (tag === 'gametypes') {
|
|
chart.data = {
|
|
labels: ['Standard', 'Postponed', 'Pending', 'Event', 'Fix'],
|
|
datasets: [
|
|
{
|
|
label: 'Game Type',
|
|
data: [
|
|
gameStats.value.Complete - gameStats.value.Events - gameStats.value.Fixes,
|
|
gameStats.value.Postponed,
|
|
gameStats.value.Pending,
|
|
gameStats.value.Events,
|
|
gameStats.value.Fixes
|
|
]
|
|
}
|
|
]
|
|
}
|
|
} else {
|
|
chart.data = {
|
|
labels: Object.keys(stats),
|
|
datasets: [
|
|
{
|
|
label: tag,
|
|
data: Object.values(stats).map((p) => p[tag])
|
|
}
|
|
]
|
|
}
|
|
}
|
|
chart.update()
|
|
}
|
|
|
|
watch(dateSelect, async (newValue, oldValue) => {
|
|
router.replace({ query: { monthId: dateSelect.value } })
|
|
loadData()
|
|
})
|
|
|
|
watch(roleStats, async (newValue, oldValue) => {
|
|
updateChart(newValue, chartSelect.value)
|
|
})
|
|
|
|
watch(chartSelect, async (newValue: ChartType, oldValue: ChartType) => {
|
|
updateChart(roleStats.value, newValue)
|
|
})
|
|
|
|
onMounted(async () => {
|
|
if (!route.query.monthId) {
|
|
router.replace({ query: { monthId: -1 } })
|
|
} else {
|
|
dateSelect.value = Number(route.query.monthId)
|
|
}
|
|
loadData()
|
|
chart = new Chart(document.getElementById('piechart')! as HTMLCanvasElement, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: [],
|
|
datasets: []
|
|
}
|
|
})
|
|
})
|
|
</script>
|