mejoras UI,
This commit is contained in:
@@ -70,6 +70,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="group in ratioData"
|
v-for="group in ratioData"
|
||||||
:key="group.name"
|
:key="group.name"
|
||||||
|
v-show="group.total > 0"
|
||||||
class="ratio-group"
|
class="ratio-group"
|
||||||
:class="{ highlight: highlighted === group.name }"
|
:class="{ highlight: highlighted === group.name }"
|
||||||
@mouseenter="highlighted = group.name"
|
@mouseenter="highlighted = group.name"
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="leaderboard light">
|
<div class="leaderboard light">
|
||||||
<div class="header glass light">
|
<div class="header glass light">
|
||||||
<h1>📈 Leaderboard</h1>
|
<div class="header-left">
|
||||||
|
<button class="btn-back" @click="goHome" title="Volver al inicio">
|
||||||
|
← Inicio
|
||||||
|
</button>
|
||||||
|
<h1>📈 Leaderboard</h1>
|
||||||
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn-collapse" @click="filtersCollapsed = !filtersCollapsed" :title="filtersCollapsed ? 'Mostrar filtros' : 'Ocultar filtros'">
|
<button class="btn-collapse" @click="filtersCollapsed = !filtersCollapsed" :title="filtersCollapsed ? 'Mostrar filtros' : 'Ocultar filtros'">
|
||||||
<span class="collapse-icon" :class="{ rotated: filtersCollapsed }">▼</span>
|
<span class="collapse-icon" :class="{ rotated: filtersCollapsed }">▼</span>
|
||||||
@@ -123,6 +128,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import EventChart from '../components/EventChart.vue';
|
import EventChart from '../components/EventChart.vue';
|
||||||
import EventFilters from '../components/EventFilters.vue';
|
import EventFilters from '../components/EventFilters.vue';
|
||||||
import DataSourceSelector from '../components/DataSourceSelector.vue';
|
import DataSourceSelector from '../components/DataSourceSelector.vue';
|
||||||
@@ -131,6 +137,7 @@ import { useEventFilters } from '../composables/useEventFilters';
|
|||||||
interface RoomInfo { roomId: string; metadata?: any; }
|
interface RoomInfo { roomId: string; metadata?: any; }
|
||||||
interface RoomState { players?: any[]; systemMessages?: { kind: string }[] }
|
interface RoomState { players?: any[]; systemMessages?: { kind: string }[] }
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const eventFilters = useEventFilters();
|
const eventFilters = useEventFilters();
|
||||||
const filtersCollapsed = ref(false);
|
const filtersCollapsed = ref(false);
|
||||||
@@ -193,6 +200,26 @@ const selectedPlayerMetrics = ref<Record<string, number>>({
|
|||||||
// Store room score history from players
|
// Store room score history from players
|
||||||
const allPlayersWithScores = ref<any[]>([]);
|
const allPlayersWithScores = ref<any[]>([]);
|
||||||
|
|
||||||
|
// Function to check if a score passes the current filters
|
||||||
|
function scorePassesFilters(score: any, roomId: string) {
|
||||||
|
// Room filter
|
||||||
|
if (eventFilters.roomFilter.value !== 'all' && roomId !== eventFilters.roomFilter.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round filter
|
||||||
|
if (eventFilters.roundFilter.value !== 'all' && score.round !== eventFilters.roundFilter.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game variant filter
|
||||||
|
if (eventFilters.gameFilter.value !== 'all' && score.variant !== eventFilters.gameFilter.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to compute additional metrics from players' score history
|
// Function to compute additional metrics from players' score history
|
||||||
function computeMetricsFromScores() {
|
function computeMetricsFromScores() {
|
||||||
if (!allPlayersWithScores.value.length) return;
|
if (!allPlayersWithScores.value.length) return;
|
||||||
@@ -213,6 +240,11 @@ function computeMetricsFromScores() {
|
|||||||
if (player.roomScoreHistory) {
|
if (player.roomScoreHistory) {
|
||||||
player.roomScoreHistory.forEach((roomScore: any) => {
|
player.roomScoreHistory.forEach((roomScore: any) => {
|
||||||
roomScore.scores.forEach((score: any) => {
|
roomScore.scores.forEach((score: any) => {
|
||||||
|
// Apply filters to scores
|
||||||
|
if (!scorePassesFilters(score, roomScore.roomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (score.role === 'P1') {
|
if (score.role === 'P1') {
|
||||||
totalP1Scores += score.score;
|
totalP1Scores += score.score;
|
||||||
p1Count++;
|
p1Count++;
|
||||||
@@ -259,6 +291,11 @@ function computeSelectedPlayerMetrics(uuid: string) {
|
|||||||
|
|
||||||
playerData.roomScoreHistory.forEach((roomScore: any) => {
|
playerData.roomScoreHistory.forEach((roomScore: any) => {
|
||||||
roomScore.scores.forEach((score: any) => {
|
roomScore.scores.forEach((score: any) => {
|
||||||
|
// Apply filters to scores
|
||||||
|
if (!scorePassesFilters(score, roomScore.roomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (score.role === 'P1') {
|
if (score.role === 'P1') {
|
||||||
totalP1Scores += score.score;
|
totalP1Scores += score.score;
|
||||||
p1Count++;
|
p1Count++;
|
||||||
@@ -328,11 +365,16 @@ const averageScoreTotal = computed(() => {
|
|||||||
let totalScores = 0;
|
let totalScores = 0;
|
||||||
let totalScoreCount = 0;
|
let totalScoreCount = 0;
|
||||||
|
|
||||||
// Sum all individual scores from all players regardless of role
|
// Sum all individual scores from all players regardless of role, applying filters
|
||||||
allPlayersWithScores.value.forEach((player: any) => {
|
allPlayersWithScores.value.forEach((player: any) => {
|
||||||
if (player.roomScoreHistory) {
|
if (player.roomScoreHistory) {
|
||||||
player.roomScoreHistory.forEach((roomScore: any) => {
|
player.roomScoreHistory.forEach((roomScore: any) => {
|
||||||
roomScore.scores.forEach((score: any) => {
|
roomScore.scores.forEach((score: any) => {
|
||||||
|
// Apply filters to scores
|
||||||
|
if (!scorePassesFilters(score, roomScore.roomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
totalScores += score.score;
|
totalScores += score.score;
|
||||||
totalScoreCount++;
|
totalScoreCount++;
|
||||||
});
|
});
|
||||||
@@ -360,6 +402,8 @@ const activeFilters = computed(() => ({
|
|||||||
// Watch for changes in filters and data source
|
// Watch for changes in filters and data source
|
||||||
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter, eventFilters.roomFilter], () => {
|
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter, eventFilters.roomFilter], () => {
|
||||||
eventFilters.applyFilters(EVENTS);
|
eventFilters.applyFilters(EVENTS);
|
||||||
|
// Recalculate metrics when filters change
|
||||||
|
computeMetricsFromScores();
|
||||||
});
|
});
|
||||||
|
|
||||||
// selectedUuid watch will be added after selectedUuid is declared
|
// selectedUuid watch will be added after selectedUuid is declared
|
||||||
@@ -480,6 +524,10 @@ function clearRoom() {
|
|||||||
eventFilters.applyFilters(EVENTS);
|
eventFilters.applyFilters(EVENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goHome() {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
function prevPage() { page.value = Math.max(1, page.value - 1); }
|
function prevPage() { page.value = Math.max(1, page.value - 1); }
|
||||||
function nextPage() { page.value = Math.min(pageCount.value, page.value + 1); }
|
function nextPage() { page.value = Math.min(pageCount.value, page.value + 1); }
|
||||||
function prevRoomPage() { roomPage.value = Math.max(1, roomPage.value - 1); }
|
function prevRoomPage() { roomPage.value = Math.max(1, roomPage.value - 1); }
|
||||||
@@ -875,6 +923,9 @@ function downloadCSV() {
|
|||||||
|
|
||||||
.header { display:flex; align-items:center; justify-content:space-between; padding: 12px 14px; margin-bottom: 14px; }
|
.header { display:flex; align-items:center; justify-content:space-between; padding: 12px 14px; margin-bottom: 14px; }
|
||||||
.header h1 { margin: 0; }
|
.header h1 { margin: 0; }
|
||||||
|
.header-left { display:flex; align-items:center; gap: 16px; }
|
||||||
|
.btn-back { background:#667eea; color:#fff; border:none; border-radius:8px; padding:8px 14px; font-weight:600; cursor:pointer; transition: all 0.3s ease; font-size: 14px; }
|
||||||
|
.btn-back:hover { background:#5b6bda; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); }
|
||||||
.actions { display:flex; gap: 8px; }
|
.actions { display:flex; gap: 8px; }
|
||||||
.actions .btn { background:#667eea; color:#fff; border:none; border-radius:10px; padding:8px 12px; font-weight:800; cursor:pointer; }
|
.actions .btn { background:#667eea; color:#fff; border:none; border-radius:10px; padding:8px 12px; font-weight:800; cursor:pointer; }
|
||||||
.actions .btn.toggle { background:#eef2ff; color:#3949ab; border:1px solid #c7d2fe; }
|
.actions .btn.toggle { background:#eef2ff; color:#3949ab; border:1px solid #c7d2fe; }
|
||||||
|
|||||||
@@ -48,6 +48,9 @@
|
|||||||
<button @click="goToDashboard" class="btn-dashboard">
|
<button @click="goToDashboard" class="btn-dashboard">
|
||||||
🎛️ Dashboard Admin
|
🎛️ Dashboard Admin
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="goToLeaderboard" class="btn-leaderboard">
|
||||||
|
📈 Leaderboard
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- QR Mode Toggle -->
|
<!-- QR Mode Toggle -->
|
||||||
@@ -230,6 +233,10 @@ function goToDashboard() {
|
|||||||
router.push('/dashboard');
|
router.push('/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToLeaderboard() {
|
||||||
|
router.push('/leaderboard');
|
||||||
|
}
|
||||||
|
|
||||||
// Context menu functions
|
// Context menu functions
|
||||||
function showContextMenu(event: MouseEvent, uuidInfo: UuidInfo) {
|
function showContextMenu(event: MouseEvent, uuidInfo: UuidInfo) {
|
||||||
contextMenu.value = {
|
contextMenu.value = {
|
||||||
@@ -796,7 +803,8 @@ async function copyToClipboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-random,
|
.btn-random,
|
||||||
.btn-dashboard {
|
.btn-dashboard,
|
||||||
|
.btn-leaderboard {
|
||||||
padding: 12px 30px;
|
padding: 12px 30px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -826,6 +834,16 @@ async function copyToClipboard() {
|
|||||||
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
|
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-leaderboard {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-leaderboard:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
/* Context Menu */
|
/* Context Menu */
|
||||||
.context-menu {
|
.context-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -1038,7 +1056,8 @@ async function copyToClipboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-random,
|
.btn-random,
|
||||||
.btn-dashboard {
|
.btn-dashboard,
|
||||||
|
.btn-leaderboard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user