filtros mejorados en leaderboard
This commit is contained in:
@@ -566,6 +566,15 @@ function friendlyEventName(eventType: string): string {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Place the group total just next to the title */
|
||||
.ratio-card .card-header {
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
.ratio-card .group-total {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.ratio-bar {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<div class="filter-buttons">
|
||||
<button
|
||||
class="filter-btn"
|
||||
:class="{ active: roundFilter === 'all' }"
|
||||
@click="$emit('update:roundFilter', 'all')"
|
||||
:class="{ active: (roundFilter?.length||0) === 0 }"
|
||||
@click="$emit('update:roundFilter', [])"
|
||||
title="Mostrar todas las rondas"
|
||||
>
|
||||
Todas
|
||||
@@ -15,8 +15,8 @@
|
||||
v-for="r in [1, 2, 3]"
|
||||
:key="r"
|
||||
class="filter-btn"
|
||||
:class="{ active: roundFilter === r }"
|
||||
@click="$emit('update:roundFilter', r)"
|
||||
:class="{ active: roundFilter?.includes(r) }"
|
||||
@click="$emit('update:roundFilter', roundFilter?.includes(r) ? roundFilter.filter(x=>x!==r) : [...(roundFilter||[]), r])"
|
||||
:title="`Mostrar solo Round ${r}`"
|
||||
>
|
||||
R{{ r }}
|
||||
@@ -29,8 +29,8 @@
|
||||
<div class="filter-buttons">
|
||||
<button
|
||||
class="filter-btn"
|
||||
:class="{ active: gameFilter === 'all' }"
|
||||
@click="$emit('update:gameFilter', 'all')"
|
||||
:class="{ active: (gameFilter?.length||0) === 0 }"
|
||||
@click="$emit('update:gameFilter', [])"
|
||||
title="Mostrar todas las variantes"
|
||||
>
|
||||
Todas
|
||||
@@ -39,8 +39,8 @@
|
||||
v-for="g in ['G1', 'G2', 'G3', 'G4', 'G5']"
|
||||
:key="g"
|
||||
class="filter-btn"
|
||||
:class="{ active: gameFilter === g }"
|
||||
@click="$emit('update:gameFilter', g)"
|
||||
:class="{ active: gameFilter?.includes(g) }"
|
||||
@click="$emit('update:gameFilter', gameFilter?.includes(g) ? gameFilter.filter(x=>x!==g) : [...(gameFilter||[]), g])"
|
||||
:title="`Mostrar solo variante ${g}`"
|
||||
>
|
||||
{{ g }}
|
||||
@@ -63,11 +63,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { RoundFilter, GameFilter } from '../composables/useEventFilters';
|
||||
import type { RoundFilterMulti, GameFilterMulti } from '../composables/useEventFilters';
|
||||
|
||||
interface Props {
|
||||
roundFilter: RoundFilter;
|
||||
gameFilter: GameFilter;
|
||||
roundFilter: RoundFilterMulti;
|
||||
gameFilter: GameFilterMulti;
|
||||
hasActiveFilters: boolean;
|
||||
filterSummary: string;
|
||||
}
|
||||
@@ -75,8 +75,8 @@ interface Props {
|
||||
defineProps<Props>();
|
||||
|
||||
defineEmits<{
|
||||
'update:roundFilter': [value: RoundFilter];
|
||||
'update:gameFilter': [value: GameFilter];
|
||||
'update:roundFilter': [value: RoundFilterMulti];
|
||||
'update:gameFilter': [value: GameFilterMulti];
|
||||
'resetFilters': [];
|
||||
}>();
|
||||
</script>
|
||||
@@ -229,4 +229,4 @@ defineEmits<{
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -10,16 +10,16 @@ export interface DetailedEvent {
|
||||
}
|
||||
|
||||
export type DataSource = 'aggregated' | 'active-rooms';
|
||||
export type RoundFilter = 'all' | 1 | 2 | 3;
|
||||
export type GameFilter = 'all' | 'G1' | 'G2' | 'G3' | 'G4' | 'G5';
|
||||
export type RoomFilter = 'all' | string;
|
||||
export type RoundFilterMulti = number[]; // empty means all
|
||||
export type GameFilterMulti = string[]; // empty means all
|
||||
export type RoomFilterMulti = string[]; // empty means all
|
||||
|
||||
export function useEventFilters() {
|
||||
// Filter states
|
||||
const dataSource = ref<DataSource>('aggregated');
|
||||
const roundFilter = ref<RoundFilter>('all');
|
||||
const gameFilter = ref<GameFilter>('all');
|
||||
const roomFilter = ref<RoomFilter>('all');
|
||||
const roundFilter = ref<RoundFilterMulti>([]);
|
||||
const gameFilter = ref<GameFilterMulti>([]);
|
||||
const roomFilter = ref<RoomFilterMulti>([]);
|
||||
|
||||
// Event data stores
|
||||
const detailedEventsAggregated = ref<DetailedEvent[]>([]);
|
||||
@@ -38,13 +38,13 @@ export function useEventFilters() {
|
||||
|
||||
// Filter events based on round, game, and room
|
||||
const filteredEvents = sourceEvents.filter(event => {
|
||||
if (roundFilter.value !== 'all' && event.round !== roundFilter.value) {
|
||||
if (roundFilter.value.length && !roundFilter.value.includes(Number(event.round))) {
|
||||
return false;
|
||||
}
|
||||
if (gameFilter.value !== 'all' && event.gameVariant !== gameFilter.value) {
|
||||
if (gameFilter.value.length && !gameFilter.value.includes(String(event.gameVariant))) {
|
||||
return false;
|
||||
}
|
||||
if (roomFilter.value !== 'all' && event.roomId !== roomFilter.value) {
|
||||
if (roomFilter.value.length && !roomFilter.value.includes(String(event.roomId))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -75,9 +75,9 @@ export function useEventFilters() {
|
||||
|
||||
// Reset filters
|
||||
function resetFilters() {
|
||||
roundFilter.value = 'all';
|
||||
gameFilter.value = 'all';
|
||||
roomFilter.value = 'all';
|
||||
roundFilter.value = [];
|
||||
gameFilter.value = [];
|
||||
roomFilter.value = [];
|
||||
}
|
||||
|
||||
// Computed properties
|
||||
@@ -90,14 +90,14 @@ export function useEventFilters() {
|
||||
);
|
||||
|
||||
const hasActiveFilters = computed(() =>
|
||||
roundFilter.value !== 'all' || gameFilter.value !== 'all' || roomFilter.value !== 'all'
|
||||
roundFilter.value.length > 0 || gameFilter.value.length > 0 || roomFilter.value.length > 0
|
||||
);
|
||||
|
||||
const filterSummary = computed(() => {
|
||||
const parts = [];
|
||||
if (roundFilter.value !== 'all') parts.push(`Round ${roundFilter.value}`);
|
||||
if (gameFilter.value !== 'all') parts.push(`Game ${gameFilter.value}`);
|
||||
if (roomFilter.value !== 'all') parts.push(`Room ${roomFilter.value.slice(0, 8)}`);
|
||||
if (roundFilter.value.length) parts.push(`Round ${roundFilter.value.join(',')}`);
|
||||
if (gameFilter.value.length) parts.push(`Game ${gameFilter.value.join(',')}`);
|
||||
if (roomFilter.value.length) parts.push(`Rooms ${roomFilter.value.map(r => r.slice(0,8)).join(',')}`);
|
||||
return parts.length > 0 ? parts.join(' + ') : 'Sin filtros';
|
||||
});
|
||||
|
||||
@@ -125,4 +125,4 @@ export function useEventFilters() {
|
||||
hasActiveFilters,
|
||||
filterSummary
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
<div class="legend">
|
||||
<span class="key global"></span> Global
|
||||
<span class="sep">·</span>
|
||||
<span class="key player" v-if="selectedUuid"></span> Jugador
|
||||
<span class="sep" v-if="selectedRoomId">·</span>
|
||||
<span class="key room" v-if="selectedRoomId"></span> Sala
|
||||
<span class="key player" v-if="selectedUuids.length"></span> Jugadores
|
||||
<span class="sep" v-if="selectedRoomIds.length">·</span>
|
||||
<span class="key room" v-if="selectedRoomIds.length"></span> Salas
|
||||
</div>
|
||||
<div class="player-chips">
|
||||
<div class="search-controls">
|
||||
@@ -60,15 +60,15 @@
|
||||
v-for="p in playersPage"
|
||||
:key="p.uuid"
|
||||
class="chip"
|
||||
:class="{ active: p.uuid === selectedUuid }"
|
||||
@click="selectPlayer(p.uuid)"
|
||||
:class="{ active: selectedUuids.includes(p.uuid) }"
|
||||
@click="togglePlayer(p.uuid)"
|
||||
:title="p.uuid"
|
||||
:style="{ '--primary': p.color || '#667eea' } as any"
|
||||
>
|
||||
<span class="avatar">{{ initials(p.name) }}</span>
|
||||
<span class="label">{{ p.name || 'Jugador' }}</span>
|
||||
</button>
|
||||
<button v-if="selectedUuid" class="chip clear" @click="clearPlayer">Quitar selección</button>
|
||||
<button v-if="selectedUuids.length" class="chip clear" @click="clearPlayers">Quitar selección</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -87,15 +87,15 @@
|
||||
v-for="r in roomsPage"
|
||||
:key="r.roomId"
|
||||
class="chip room-chip"
|
||||
:class="{ active: r.roomId === selectedRoomId }"
|
||||
@click="selectRoom(r.roomId)"
|
||||
:class="{ active: selectedRoomIds.includes(r.roomId) }"
|
||||
@click="toggleRoom(r.roomId)"
|
||||
:title="`Sala: ${r.roomId} (${r.playerCount || 0} jugadores)`"
|
||||
>
|
||||
<span class="avatar">🏠</span>
|
||||
<span class="label">{{ r.name }}</span>
|
||||
<span class="count" v-if="r.playerCount">{{ r.playerCount }}</span>
|
||||
</button>
|
||||
<button v-if="selectedRoomId" class="chip clear" @click="clearRoom">Quitar selección</button>
|
||||
<button v-if="selectedRoomIds.length" class="chip clear" @click="clearRooms">Quitar selección</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,7 +106,7 @@
|
||||
:event-styles="EVENT_STYLES"
|
||||
:global-event-counts="combinedGlobalCounts"
|
||||
:player-event-counts="combinedPlayerCounts"
|
||||
:selected-player-uuid="selectedUuid"
|
||||
:selected-player-uuid="selectedUuids.length ? selectedUuids[0] : ''"
|
||||
:player-bar-gradient="playerBarGradient"
|
||||
view-mode="ratio"
|
||||
:loading="loading"
|
||||
@@ -201,21 +201,18 @@ 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;
|
||||
}
|
||||
|
||||
// Room filter (array): empty means all
|
||||
const rf = eventFilters.roomFilter.value;
|
||||
if (rf.length && !rf.includes(String(roomId))) return false;
|
||||
|
||||
// Round filter (array)
|
||||
const rds = eventFilters.roundFilter.value;
|
||||
if (rds.length && !rds.includes(Number(score.round))) return false;
|
||||
|
||||
// Game variant filter (array)
|
||||
const gfs = eventFilters.gameFilter.value;
|
||||
if (gfs.length && !gfs.includes(String(score.variant))) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -274,16 +271,16 @@ function computeMetricsFromScores() {
|
||||
};
|
||||
}
|
||||
|
||||
// Function to compute metrics for a selected player
|
||||
function computeSelectedPlayerMetrics(uuid: string) {
|
||||
const playerData = allPlayersWithScores.value.find(p => p.uuid === uuid);
|
||||
if (!playerData) {
|
||||
// Function to compute metrics for selected players (multi-select)
|
||||
function computeSelectedPlayersMetrics() {
|
||||
const uuids = selectedUuids.value;
|
||||
if (!uuids.length) {
|
||||
selectedPlayerMetrics.value = {
|
||||
players_seated: 1,
|
||||
players_seated: 0,
|
||||
score_p1: 0,
|
||||
score_p2: 0,
|
||||
players_with_shame: 0,
|
||||
players_without_shame: 1
|
||||
players_without_shame: 0
|
||||
};
|
||||
return;
|
||||
}
|
||||
@@ -292,34 +289,29 @@ function computeSelectedPlayerMetrics(uuid: string) {
|
||||
let totalP2Scores = 0;
|
||||
let p1Count = 0;
|
||||
let p2Count = 0;
|
||||
|
||||
const hasShame = playerData.shameTokens && playerData.shameTokens > 0;
|
||||
let playersWithShame = 0;
|
||||
|
||||
if (playerData.roomScoreHistory) {
|
||||
playerData.roomScoreHistory.forEach((roomScore: any) => {
|
||||
roomScore.scores.forEach((score: any) => {
|
||||
// Apply filters to scores
|
||||
if (!scorePassesFilters(score, roomScore.roomId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (score.role === 'P1') {
|
||||
totalP1Scores += score.score;
|
||||
p1Count++;
|
||||
} else if (score.role === 'P2') {
|
||||
totalP2Scores += score.score;
|
||||
p2Count++;
|
||||
}
|
||||
uuids.forEach(uuid => {
|
||||
const playerData = allPlayersWithScores.value.find(p => p.uuid === uuid);
|
||||
if (!playerData) return;
|
||||
if (playerData.shameTokens && playerData.shameTokens > 0) playersWithShame++;
|
||||
if (playerData.roomScoreHistory) {
|
||||
playerData.roomScoreHistory.forEach((roomScore: any) => {
|
||||
roomScore.scores.forEach((score: any) => {
|
||||
if (!scorePassesFilters(score, roomScore.roomId)) return;
|
||||
if (score.role === 'P1') { totalP1Scores += score.score; p1Count++; }
|
||||
else if (score.role === 'P2') { totalP2Scores += score.score; p2Count++; }
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
selectedPlayerMetrics.value = {
|
||||
players_seated: 1,
|
||||
players_seated: uuids.length,
|
||||
score_p1: p1Count > 0 ? Math.round((totalP1Scores / p1Count) * 10) / 10 : 0,
|
||||
score_p2: p2Count > 0 ? Math.round((totalP2Scores / p2Count) * 10) / 10 : 0,
|
||||
players_with_shame: hasShame ? 1 : 0,
|
||||
players_without_shame: hasShame ? 0 : 1
|
||||
players_with_shame: playersWithShame,
|
||||
players_without_shame: uuids.length - playersWithShame
|
||||
};
|
||||
}
|
||||
|
||||
@@ -400,11 +392,11 @@ const totalPlayersCount = computed(() => {
|
||||
// Active filters object
|
||||
const activeFilters = computed(() => ({
|
||||
dataSource: eventFilters.dataSource.value,
|
||||
round: eventFilters.roundFilter.value.toString(),
|
||||
game: eventFilters.gameFilter.value,
|
||||
hasFilters: eventFilters.hasActiveFilters.value || !!selectedRoomId.value,
|
||||
selectedPlayer: selectedUuid.value ? players.value.find(p => p.uuid === selectedUuid.value)?.name || 'Jugador' : undefined,
|
||||
selectedRoom: selectedRoomId.value ? availableRooms.value.find(r => r.roomId === selectedRoomId.value)?.name || 'Sala' : undefined
|
||||
round: eventFilters.roundFilter.value.join(',') || 'all',
|
||||
game: eventFilters.gameFilter.value.join(',') || 'all',
|
||||
hasFilters: eventFilters.hasActiveFilters.value || selectedRoomIds.value.length > 0,
|
||||
selectedPlayer: selectedUuids.value.length ? `${selectedUuids.value.length} jugadores` : undefined,
|
||||
selectedRoom: selectedRoomIds.value.length ? `${selectedRoomIds.value.length} salas` : undefined
|
||||
}));
|
||||
|
||||
// Watch for changes in filters and data source
|
||||
@@ -449,7 +441,7 @@ const containerWidth = ref(1200); // Default width, will be updated dynamically
|
||||
const dynamicPageSize = computed(() => {
|
||||
// Estimate space needed per chip and controls
|
||||
const chipWidth = 140; // Average chip width including gap
|
||||
const clearBtnWidth = selectedUuid.value ? 150 : 0; // "Quitar selección" button
|
||||
const clearBtnWidth = selectedUuids.value.length ? 150 : 0; // "Quitar selección" button
|
||||
const searchWidth = 240; // Search input
|
||||
const paginationWidth = pageCount.value > 1 ? 100 : 0; // Compact pagination
|
||||
const margin = 40; // Container margins
|
||||
@@ -463,7 +455,7 @@ const dynamicPageSize = computed(() => {
|
||||
const roomDynamicPageSize = computed(() => {
|
||||
// Similar calculation for rooms
|
||||
const chipWidth = 160; // Room chips might be slightly wider
|
||||
const clearBtnWidth = selectedRoomId.value ? 150 : 0;
|
||||
const clearBtnWidth = selectedRoomIds.value.length ? 150 : 0;
|
||||
const searchWidth = 240;
|
||||
const paginationWidth = roomPageCount.value > 1 ? 100 : 0;
|
||||
const margin = 40;
|
||||
@@ -487,18 +479,17 @@ const roomsPage = computed(() => {
|
||||
return roomsFiltered.value.slice(start, start + roomDynamicPageSize.value);
|
||||
});
|
||||
|
||||
const selectedUuid = ref('');
|
||||
const selectedRoomId = ref('');
|
||||
const selectedUuids = ref<string[]>([]);
|
||||
const selectedRoomIds = ref<string[]>([]);
|
||||
const playerLoading = ref(false);
|
||||
const playerEventCounts = ref<Record<string, number>>((Object.fromEntries(EVENTS.map(k => [k, 0])) as Record<string, number>));
|
||||
const playersActionsByUuid = ref<Record<string, Record<string, number>>>({});
|
||||
|
||||
// Watch for selected player changes to update their metrics
|
||||
watch(selectedUuid, (newUuid) => {
|
||||
if (newUuid) {
|
||||
computeSelectedPlayerMetrics(newUuid);
|
||||
}
|
||||
});
|
||||
// Watch for selected players changes to update their metrics
|
||||
watch(selectedUuids, () => {
|
||||
computeSelectedPlayersMetrics();
|
||||
updateSelectedPlayersCounts();
|
||||
}, { deep: true });
|
||||
|
||||
function initials(name: string): string {
|
||||
const n = (name || '').trim();
|
||||
@@ -509,26 +500,29 @@ function initials(name: string): string {
|
||||
}
|
||||
|
||||
|
||||
function selectPlayer(uuid: string) {
|
||||
if (selectedUuid.value === uuid) return;
|
||||
selectedUuid.value = uuid;
|
||||
loadPlayerHistory();
|
||||
function togglePlayer(uuid: string) {
|
||||
const idx = selectedUuids.value.indexOf(uuid);
|
||||
if (idx >= 0) selectedUuids.value.splice(idx, 1);
|
||||
else selectedUuids.value.push(uuid);
|
||||
updateSelectedPlayersCounts();
|
||||
}
|
||||
|
||||
function clearPlayer() {
|
||||
selectedUuid.value = '';
|
||||
function clearPlayers() {
|
||||
selectedUuids.value = [];
|
||||
updateSelectedPlayersCounts();
|
||||
}
|
||||
|
||||
function selectRoom(roomId: string) {
|
||||
if (selectedRoomId.value === roomId) return;
|
||||
selectedRoomId.value = roomId;
|
||||
eventFilters.roomFilter.value = roomId;
|
||||
function toggleRoom(roomId: string) {
|
||||
const idx = selectedRoomIds.value.indexOf(roomId);
|
||||
if (idx >= 0) selectedRoomIds.value.splice(idx, 1);
|
||||
else selectedRoomIds.value.push(roomId);
|
||||
eventFilters.roomFilter.value = [...selectedRoomIds.value];
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
}
|
||||
|
||||
function clearRoom() {
|
||||
selectedRoomId.value = '';
|
||||
eventFilters.roomFilter.value = 'all';
|
||||
function clearRooms() {
|
||||
selectedRoomIds.value = [];
|
||||
eventFilters.roomFilter.value = [];
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
}
|
||||
|
||||
@@ -555,11 +549,7 @@ watch(roomPageCount, (newCount) => {
|
||||
});
|
||||
|
||||
// Dynamic per-player overlay bar gradient and label color
|
||||
const playerBarGradient = computed(() => {
|
||||
const p = players.value.find(x => x.uuid === selectedUuid.value);
|
||||
const c = p?.color || '#667eea';
|
||||
return `linear-gradient(90deg, ${c}, ${c})`;
|
||||
});
|
||||
const playerBarGradient = computed(() => '#8b5cf6');
|
||||
|
||||
|
||||
|
||||
@@ -720,10 +710,8 @@ function setupStreams() {
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
}
|
||||
// If a player is selected, update playerEventCounts live
|
||||
if (selectedUuid.value) {
|
||||
const counts = byUuid[selectedUuid.value];
|
||||
if (counts) playerEventCounts.value = counts as any;
|
||||
}
|
||||
// Update selected players combined counts
|
||||
updateSelectedPlayersCounts();
|
||||
// Merge names into players list; preserve colors from previous streams
|
||||
const existing = new Map<string, { name: string; color?: string }>();
|
||||
players.value.forEach(p => existing.set(p.uuid, { name: p.name, color: p.color }));
|
||||
@@ -782,13 +770,14 @@ async function refreshAll() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlayerHistory() {
|
||||
function updateSelectedPlayersCounts() {
|
||||
playerLoading.value = true;
|
||||
const next: Record<string, number> = Object.fromEntries(EVENTS.map(k => [k, 0])) as any;
|
||||
const counts = playersActionsByUuid.value[selectedUuid.value || ''];
|
||||
if (counts) {
|
||||
EVENTS.forEach(k => { next[k] = Number(counts[k] || 0); });
|
||||
}
|
||||
const byUuid = playersActionsByUuid.value;
|
||||
selectedUuids.value.forEach(uuid => {
|
||||
const counts = byUuid[uuid] || {};
|
||||
EVENTS.forEach(k => { next[k] = (next[k] || 0) + Number(counts[k] || 0); });
|
||||
});
|
||||
playerEventCounts.value = next as any;
|
||||
playerLoading.value = false;
|
||||
}
|
||||
@@ -835,14 +824,8 @@ function downloadJSON() {
|
||||
game: eventFilters.gameFilter.value,
|
||||
hasActiveFilters: eventFilters.hasActiveFilters.value,
|
||||
filterSummary: eventFilters.filterSummary.value,
|
||||
selectedPlayer: selectedUuid.value ? {
|
||||
uuid: selectedUuid.value,
|
||||
name: players.value.find(p => p.uuid === selectedUuid.value)?.name || 'Jugador'
|
||||
} : null,
|
||||
selectedRoom: selectedRoomId.value ? {
|
||||
roomId: selectedRoomId.value,
|
||||
name: availableRooms.value.find(r => r.roomId === selectedRoomId.value)?.name || 'Sala'
|
||||
} : null
|
||||
selectedPlayers: selectedUuids.value,
|
||||
selectedRooms: selectedRoomIds.value
|
||||
}
|
||||
},
|
||||
|
||||
@@ -861,17 +844,17 @@ function downloadJSON() {
|
||||
globalEventCounts: eventFilters.globalEventCounts.value,
|
||||
|
||||
// Selected player event counts (if applicable)
|
||||
selectedPlayerEventCounts: selectedUuid.value ? playerEventCounts.value : null,
|
||||
selectedPlayersEventCounts: selectedUuids.value.length ? playerEventCounts.value : null,
|
||||
|
||||
// Additional metrics being displayed
|
||||
additionalMetrics: additionalMetrics.value,
|
||||
|
||||
// Selected player metrics (if applicable)
|
||||
selectedPlayerMetrics: selectedUuid.value ? selectedPlayerMetrics.value : null,
|
||||
selectedPlayersMetrics: selectedUuids.value.length ? selectedPlayerMetrics.value : null,
|
||||
|
||||
// Combined counts used in charts
|
||||
combinedGlobalCounts: combinedGlobalCounts.value,
|
||||
combinedPlayerCounts: selectedUuid.value ? combinedPlayerCounts.value : null,
|
||||
combinedPlayerCounts: selectedUuids.value.length ? combinedPlayerCounts.value : null,
|
||||
|
||||
// Players data
|
||||
players: players.value,
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<div class="offer-card" :class="{ disabled: isFinished }" :aria-disabled="isFinished ? 'true' : 'false'">
|
||||
<div v-if="isFinished" class="banner finished">🏁 Juego finalizado</div>
|
||||
|
||||
<!-- Header (tap 5x to unlock Básico) -->
|
||||
<div class="offer-header" :class="{ clickable: !advancedUnlocked }" @click="onHeaderClick" :title="unlockTitle">
|
||||
Controles de oferta
|
||||
</div>
|
||||
|
||||
<!-- Mode Toggle -->
|
||||
<div class="mode-toggle" v-if="!isFinished">
|
||||
<!-- Show toggles only after unlocking (5 taps) -->
|
||||
<div class="mode-toggle" v-if="!isFinished && advancedUnlocked">
|
||||
<button
|
||||
class="mode-btn"
|
||||
:class="{ active: !advancedMode }"
|
||||
@@ -12,6 +18,7 @@
|
||||
🎯 Básico
|
||||
</button>
|
||||
<button
|
||||
v-if="advancedUnlocked"
|
||||
class="mode-btn"
|
||||
:class="{ active: advancedMode }"
|
||||
@click="advancedMode = true"
|
||||
@@ -20,8 +27,8 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Basic Mode -->
|
||||
<div v-if="!advancedMode" class="basic-mode">
|
||||
<!-- Basic Mode (always visible until advanced is unlocked and selected) -->
|
||||
<div v-if="!advancedUnlocked || !advancedMode" class="basic-mode">
|
||||
<div class="basic-offer">
|
||||
<div class="offer-text">
|
||||
Ofrecer <span class="token pill">
|
||||
@@ -112,7 +119,25 @@ const offerPavo = ref(0);
|
||||
const offerElote = ref(0);
|
||||
const requestPavo = ref(0);
|
||||
const requestElote = ref(0);
|
||||
const advancedMode = ref(false); // Start in basic mode
|
||||
const advancedMode = ref(false); // Start in basic mode; 'Avanzado' unlocks after 5 taps
|
||||
|
||||
// Hidden unlock: 5 rapid taps on header to reveal "Avanzado"
|
||||
const advancedUnlocked = ref(false);
|
||||
const clickCount = ref(0);
|
||||
let clickResetTimer: any = null;
|
||||
|
||||
function onHeaderClick() {
|
||||
if (advancedUnlocked.value) return;
|
||||
if (clickResetTimer) { clearTimeout(clickResetTimer); clickResetTimer = null; }
|
||||
clickCount.value += 1;
|
||||
if (clickCount.value >= 5) {
|
||||
advancedUnlocked.value = true;
|
||||
} else {
|
||||
clickResetTimer = setTimeout(() => { clickCount.value = 0; }, 1200);
|
||||
}
|
||||
}
|
||||
|
||||
const unlockTitle = computed(() => advancedUnlocked.value ? 'Modo Avanzado disponible' : `Clicks: ${clickCount.value}/5 para desbloquear Avanzado`);
|
||||
|
||||
const room = computed(() => colyseusService.gameRoom.value as any);
|
||||
const isFinished = ref(false);
|
||||
@@ -263,6 +288,9 @@ function noOffer() {
|
||||
.offer-card.disabled { opacity: 0.6; filter: grayscale(0.15); pointer-events: none; }
|
||||
.banner { margin-bottom:8px; padding:8px 10px; border-radius:10px; font-weight:700; font-size:13px; display:flex; align-items:center; gap:8px; }
|
||||
.banner.finished { background:#f8fafc; border:1px solid #e5e9f0; color:#334155; }
|
||||
.offer-header { font-weight: 800; font-size: 14px; color:#334155; margin: 4px 2px 8px; }
|
||||
.offer-header.clickable { cursor: pointer; user-select: none; opacity: 0.85; }
|
||||
.offer-header.clickable:hover { filter: brightness(0.95); }
|
||||
.offer-grid { display:grid; grid-template-columns: 1fr; gap:12px; }
|
||||
@media (min-width: 500px) { .offer-grid { grid-template-columns: 1fr 1fr; } }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user