agregado filtro de eventos por sala en la que se realizaron
This commit is contained in:
@@ -15,6 +15,9 @@
|
||||
<span class="filter-tag player-tag" v-if="selectedPlayerUuid && activeFilters?.selectedPlayer">
|
||||
👤 {{ activeFilters.selectedPlayer }}
|
||||
</span>
|
||||
<span class="filter-tag room-tag" v-if="activeFilters?.selectedRoom">
|
||||
🏠 {{ activeFilters.selectedRoom }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading" class="placeholder">Cargando datos…</div>
|
||||
@@ -124,6 +127,7 @@ interface Props {
|
||||
game: string;
|
||||
hasFilters: boolean;
|
||||
selectedPlayer?: string;
|
||||
selectedRoom?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export interface DetailedEvent {
|
||||
kind: string;
|
||||
@@ -6,17 +6,20 @@ export interface DetailedEvent {
|
||||
gameVariant?: string;
|
||||
playerUuid?: string;
|
||||
playerName?: string;
|
||||
roomId?: string;
|
||||
}
|
||||
|
||||
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 function useEventFilters() {
|
||||
// Filter states
|
||||
const dataSource = ref<DataSource>('aggregated');
|
||||
const roundFilter = ref<RoundFilter>('all');
|
||||
const gameFilter = ref<GameFilter>('all');
|
||||
const roomFilter = ref<RoomFilter>('all');
|
||||
|
||||
// Event data stores
|
||||
const detailedEventsAggregated = ref<DetailedEvent[]>([]);
|
||||
@@ -33,7 +36,7 @@ export function useEventFilters() {
|
||||
? detailedEventsAggregated.value
|
||||
: detailedEventsActiveRooms.value;
|
||||
|
||||
// Filter events based on round and game
|
||||
// Filter events based on round, game, and room
|
||||
const filteredEvents = sourceEvents.filter(event => {
|
||||
if (roundFilter.value !== 'all' && event.round !== roundFilter.value) {
|
||||
return false;
|
||||
@@ -41,6 +44,9 @@ export function useEventFilters() {
|
||||
if (gameFilter.value !== 'all' && event.gameVariant !== gameFilter.value) {
|
||||
return false;
|
||||
}
|
||||
if (roomFilter.value !== 'all' && event.roomId !== roomFilter.value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -71,6 +77,7 @@ export function useEventFilters() {
|
||||
function resetFilters() {
|
||||
roundFilter.value = 'all';
|
||||
gameFilter.value = 'all';
|
||||
roomFilter.value = 'all';
|
||||
}
|
||||
|
||||
// Computed properties
|
||||
@@ -83,13 +90,14 @@ export function useEventFilters() {
|
||||
);
|
||||
|
||||
const hasActiveFilters = computed(() =>
|
||||
roundFilter.value !== 'all' || gameFilter.value !== 'all'
|
||||
roundFilter.value !== 'all' || gameFilter.value !== 'all' || roomFilter.value !== 'all'
|
||||
);
|
||||
|
||||
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)}`);
|
||||
return parts.length > 0 ? parts.join(' + ') : 'Sin filtros';
|
||||
});
|
||||
|
||||
@@ -98,6 +106,7 @@ export function useEventFilters() {
|
||||
dataSource,
|
||||
roundFilter,
|
||||
gameFilter,
|
||||
roomFilter,
|
||||
detailedEventsAggregated,
|
||||
detailedEventsActiveRooms,
|
||||
globalEventCounts,
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
<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
|
||||
</div>
|
||||
<div class="player-chips">
|
||||
<div class="search-controls">
|
||||
@@ -68,6 +70,33 @@
|
||||
<button v-if="selectedUuid" class="chip clear" @click="clearPlayer">Quitar selección</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Room Filter Section -->
|
||||
<div class="room-chips">
|
||||
<div class="search-controls">
|
||||
<input class="search" v-model="roomSearch" placeholder="Buscar sala…" />
|
||||
<div class="pagination compact" v-if="roomPageCount > 1">
|
||||
<button class="pg-btn compact" @click="prevRoomPage" :disabled="roomPage <= 1">‹</button>
|
||||
<span class="pg-ind">{{ roomPage }}/{{ roomPageCount }}</span>
|
||||
<button class="pg-btn compact" @click="nextRoomPage" :disabled="roomPage >= roomPageCount">›</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="r in roomsPage"
|
||||
:key="r.roomId"
|
||||
class="chip room-chip"
|
||||
:class="{ active: r.roomId === selectedRoomId }"
|
||||
@click="selectRoom(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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -85,8 +114,9 @@
|
||||
dataSource: eventFilters.dataSource.value,
|
||||
round: eventFilters.roundFilter.value.toString(),
|
||||
game: eventFilters.gameFilter.value,
|
||||
hasFilters: eventFilters.hasActiveFilters.value,
|
||||
selectedPlayer: selectedUuid ? players.find(p => p.uuid === selectedUuid)?.name || 'Jugador' : undefined
|
||||
hasFilters: eventFilters.hasActiveFilters.value || !!selectedRoomId,
|
||||
selectedPlayer: selectedUuid ? players.find(p => p.uuid === selectedUuid)?.name || 'Jugador' : undefined,
|
||||
selectedRoom: selectedRoomId ? availableRooms.find(r => r.roomId === selectedRoomId)?.name || 'Sala' : undefined
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -235,7 +265,7 @@ function computeMetrics(roomDetails: any) {
|
||||
}
|
||||
|
||||
// Function to compute metrics for a selected player
|
||||
function computeSelectedPlayerMetrics(uuid: string) {
|
||||
function computeSelectedPlayerMetrics(_uuid: string) {
|
||||
// Individual player metrics are simpler - just show if they're seated, their score, etc.
|
||||
// For now, set basic values. In a real implementation, we'd need to query current player state
|
||||
selectedPlayerMetrics.value = {
|
||||
@@ -258,7 +288,7 @@ const combinedPlayerCounts = computed(() => ({
|
||||
}));
|
||||
|
||||
// Watch for changes in filters and data source
|
||||
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter], () => {
|
||||
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter, eventFilters.roomFilter], () => {
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
});
|
||||
|
||||
@@ -266,18 +296,33 @@ watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilte
|
||||
|
||||
const rooms = ref<RoomInfo[]>([]);
|
||||
const players = ref<{ uuid: string; name: string; color?: string }[]>([]);
|
||||
const availableRooms = ref<{ roomId: string; name: string; playerCount?: number }[]>([]);
|
||||
const search = ref('');
|
||||
const roomSearch = ref('');
|
||||
const playersFiltered = computed(() => {
|
||||
const q = (search.value || '').toLowerCase();
|
||||
if (!q) return players.value;
|
||||
return players.value.filter(p => (p.name || '').toLowerCase().includes(q) || (p.uuid || '').toLowerCase().includes(q));
|
||||
});
|
||||
|
||||
const roomsFiltered = computed(() => {
|
||||
const q = (roomSearch.value || '').toLowerCase();
|
||||
if (!q) return availableRooms.value;
|
||||
return availableRooms.value.filter(r =>
|
||||
(r.roomId || '').toLowerCase().includes(q) ||
|
||||
(r.name || '').toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
// Reset page when search changes
|
||||
watch(search, () => {
|
||||
page.value = 1;
|
||||
});
|
||||
watch(roomSearch, () => {
|
||||
roomPage.value = 1;
|
||||
});
|
||||
const page = ref(1);
|
||||
const roomPage = ref(1);
|
||||
const containerWidth = ref(1200); // Default width, will be updated dynamically
|
||||
const dynamicPageSize = computed(() => {
|
||||
// Estimate space needed per chip and controls
|
||||
@@ -293,13 +338,35 @@ const dynamicPageSize = computed(() => {
|
||||
return Math.min(maxChips, 15); // Maximum 15 chips per page
|
||||
});
|
||||
|
||||
const roomDynamicPageSize = computed(() => {
|
||||
// Similar calculation for rooms
|
||||
const chipWidth = 160; // Room chips might be slightly wider
|
||||
const clearBtnWidth = selectedRoomId.value ? 150 : 0;
|
||||
const searchWidth = 240;
|
||||
const paginationWidth = roomPageCount.value > 1 ? 100 : 0;
|
||||
const margin = 40;
|
||||
|
||||
const availableWidth = containerWidth.value - searchWidth - paginationWidth - clearBtnWidth - margin;
|
||||
const maxChips = Math.max(3, Math.floor(availableWidth / chipWidth));
|
||||
|
||||
return Math.min(maxChips, 10); // Maximum 10 room chips per page
|
||||
});
|
||||
|
||||
const pageCount = computed(() => Math.max(1, Math.ceil((playersFiltered.value.length || 0) / dynamicPageSize.value)));
|
||||
const roomPageCount = computed(() => Math.max(1, Math.ceil((roomsFiltered.value.length || 0) / roomDynamicPageSize.value)));
|
||||
|
||||
const playersPage = computed(() => {
|
||||
const start = (page.value - 1) * dynamicPageSize.value;
|
||||
return playersFiltered.value.slice(start, start + dynamicPageSize.value);
|
||||
});
|
||||
|
||||
const roomsPage = computed(() => {
|
||||
const start = (roomPage.value - 1) * roomDynamicPageSize.value;
|
||||
return roomsFiltered.value.slice(start, start + roomDynamicPageSize.value);
|
||||
});
|
||||
|
||||
const selectedUuid = ref('');
|
||||
const selectedRoomId = ref('');
|
||||
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>>>({});
|
||||
@@ -319,6 +386,10 @@ function initials(name: string): string {
|
||||
return chars || '🙂';
|
||||
}
|
||||
|
||||
function shortRoomId(roomId: string): string {
|
||||
return roomId.slice(0, 8);
|
||||
}
|
||||
|
||||
function selectPlayer(uuid: string) {
|
||||
if (selectedUuid.value === uuid) return;
|
||||
selectedUuid.value = uuid;
|
||||
@@ -329,8 +400,23 @@ function clearPlayer() {
|
||||
selectedUuid.value = '';
|
||||
}
|
||||
|
||||
function selectRoom(roomId: string) {
|
||||
if (selectedRoomId.value === roomId) return;
|
||||
selectedRoomId.value = roomId;
|
||||
eventFilters.roomFilter.value = roomId;
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
}
|
||||
|
||||
function clearRoom() {
|
||||
selectedRoomId.value = '';
|
||||
eventFilters.roomFilter.value = 'all';
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
}
|
||||
|
||||
function prevPage() { page.value = Math.max(1, 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 nextRoomPage() { roomPage.value = Math.min(roomPageCount.value, roomPage.value + 1); }
|
||||
|
||||
// Ensure page doesn't exceed pageCount when players change
|
||||
watch(pageCount, (newCount) => {
|
||||
@@ -339,6 +425,12 @@ watch(pageCount, (newCount) => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(roomPageCount, (newCount) => {
|
||||
if (roomPage.value > newCount) {
|
||||
roomPage.value = Math.max(1, newCount);
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic per-player overlay bar gradient and label color
|
||||
const playerBarGradient = computed(() => {
|
||||
const p = players.value.find(x => x.uuid === selectedUuid.value);
|
||||
@@ -372,11 +464,13 @@ function setupStreams() {
|
||||
const data = JSON.parse((e as MessageEvent).data || '{}');
|
||||
const details = data?.roomDetails || {};
|
||||
|
||||
// Room details are for active rooms, we'll get room data from player history instead
|
||||
|
||||
// Collect detailed events for active rooms
|
||||
const detailedEvents: Array<{ kind: string; round?: number; gameVariant?: string }> = [];
|
||||
const detailedEvents: Array<{ kind: string; round?: number; gameVariant?: string; roomId?: string }> = [];
|
||||
const counts: Record<string, number> = Object.fromEntries(EVENTS.map(k => [k, 0])) as any;
|
||||
|
||||
Object.values(details).forEach((d: any) => {
|
||||
Object.entries(details).forEach(([roomId, d]: [string, any]) => {
|
||||
(Array.isArray(d?.systemMessages) ? d.systemMessages : []).forEach((m: any) => {
|
||||
const k = (m?.kind || '').toString();
|
||||
if (EVENTS.includes(k)) {
|
||||
@@ -384,7 +478,8 @@ function setupStreams() {
|
||||
detailedEvents.push({
|
||||
kind: k,
|
||||
round: m?.round,
|
||||
gameVariant: m?.gameVariant || m?.variant
|
||||
gameVariant: m?.gameVariant || m?.variant,
|
||||
roomId: m?.roomId || roomId // Use message roomId if available, otherwise use room key
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -395,6 +490,8 @@ function setupStreams() {
|
||||
// Compute additional metrics
|
||||
computeMetrics(details);
|
||||
|
||||
// Don't extract rooms from active data - we'll get them from aggregated player history
|
||||
|
||||
// Apply filters and update display
|
||||
if (eventFilters.dataSource.value === 'active-rooms') {
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
@@ -445,8 +542,9 @@ function setupStreams() {
|
||||
const list = Array.isArray(data?.players) ? data.players : [];
|
||||
allPlayersActions.value = list.map((p: any) => ({ uuid: String(p.uuid||''), name: String(p.name||''), total: Number(p.total||0) }));
|
||||
|
||||
|
||||
// Collect all detailed events from all players
|
||||
const allDetailedEvents: Array<{ kind: string; round?: number; gameVariant?: string }> = [];
|
||||
const allDetailedEvents: Array<{ kind: string; round?: number; gameVariant?: string; roomId?: string }> = [];
|
||||
const aggregatedCounts: Record<string, number> = Object.fromEntries(EVENTS.map(k => [k, 0])) as any;
|
||||
|
||||
// Update detailed counts map
|
||||
@@ -473,6 +571,21 @@ function setupStreams() {
|
||||
playersActionsByUuid.value = byUuid;
|
||||
eventFilters.updateAggregatedData(allDetailedEvents, aggregatedCounts);
|
||||
|
||||
// Extract unique room IDs from aggregated events - this is our primary source for rooms
|
||||
const roomIds = new Set<string>();
|
||||
allDetailedEvents.forEach(event => {
|
||||
if (event.roomId && event.roomId.trim()) {
|
||||
roomIds.add(event.roomId);
|
||||
}
|
||||
});
|
||||
|
||||
// Build available rooms list from aggregated events
|
||||
availableRooms.value = Array.from(roomIds).map(roomId => ({
|
||||
roomId,
|
||||
name: `Sala ${roomId.slice(0, 8)}`,
|
||||
playerCount: allDetailedEvents.filter(e => e.roomId === roomId).length
|
||||
}));
|
||||
|
||||
// Apply filters and update display if viewing aggregated data
|
||||
if (eventFilters.dataSource.value === 'aggregated') {
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
@@ -580,11 +693,12 @@ watch(() => playersFiltered.value.length, () => {
|
||||
// Removed totals table and sorting; keep actions stream for per-player counts only
|
||||
const allPlayersActions = ref<{ uuid: string; name: string; total: number }[]>([]);
|
||||
|
||||
|
||||
function downloadCSV() {
|
||||
const currentEvents = eventFilters.currentSourceEvents.value;
|
||||
|
||||
// Create CSV headers
|
||||
const headers = ['Type', 'Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'DataSource'];
|
||||
const headers = ['Type', 'Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'RoomId', 'DataSource'];
|
||||
|
||||
// Create CSV rows - one row per individual event occurrence
|
||||
const rows: string[][] = [];
|
||||
@@ -600,6 +714,7 @@ function downloadCSV() {
|
||||
event.gameVariant || '',
|
||||
event.playerUuid || '',
|
||||
event.playerName || '',
|
||||
event.roomId || '',
|
||||
eventFilters.dataSource.value
|
||||
]);
|
||||
}
|
||||
@@ -621,6 +736,7 @@ function downloadCSV() {
|
||||
'', // GameVariant info not available in aggregated player data
|
||||
uuid,
|
||||
player?.name || '',
|
||||
'', // Room info not available in aggregated player data
|
||||
'player-aggregated'
|
||||
]);
|
||||
}
|
||||
@@ -644,6 +760,7 @@ function downloadCSV() {
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
eventFilters.dataSource.value
|
||||
]);
|
||||
}
|
||||
@@ -695,14 +812,21 @@ function downloadCSV() {
|
||||
.key { width: 12px; height: 12px; border-radius: 999px; display:inline-block; }
|
||||
.key.global { background: linear-gradient(90deg, #34d399, #10b981); box-shadow: 0 0 8px rgba(16,185,129,0.35); }
|
||||
.key.player { background: linear-gradient(90deg, #a78bfa, #6366f1); box-shadow: 0 0 8px rgba(99,102,241,0.35); }
|
||||
.key.room { background: linear-gradient(90deg, #f59e0b, #d97706); box-shadow: 0 0 8px rgba(245,158,11,0.35); }
|
||||
.sep { opacity: 0.6; }
|
||||
|
||||
.player-chips {
|
||||
.player-chips, .room-chips {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.room-chips {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(203, 213, 225, 0.5);
|
||||
}
|
||||
|
||||
.search-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -736,6 +860,16 @@ function downloadCSV() {
|
||||
.chip:hover { transform: translateY(-1px); background: color-mix(in srgb, var(--primary) 10%, white); box-shadow: 0 6px 18px rgba(102,126,234,0.18); }
|
||||
.chip.active { background: color-mix(in srgb, var(--primary) 18%, white); border-color: color-mix(in srgb, var(--primary) 45%, #c7d2fe); box-shadow: 0 6px 22px rgba(99,102,241,0.22); }
|
||||
.chip.clear { background:#fff; border-style:dashed; color:#334155; }
|
||||
.chip.room-chip { --primary: #f59e0b; }
|
||||
.chip.room-chip .count {
|
||||
background: color-mix(in srgb, var(--primary) 20%, white);
|
||||
color: color-mix(in srgb, var(--primary) 80%, #111);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.avatar { width: 24px; height: 24px; border-radius: 50%; background: color-mix(in srgb, var(--primary) 25%, #eef2ff); display:grid; place-items:center; font-weight:900; color: color-mix(in srgb, var(--primary) 80%, #111); }
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user