diff --git a/client/src/components/EventFilters.vue b/client/src/components/EventFilters.vue index bc65df6..51e5de2 100644 --- a/client/src/components/EventFilters.vue +++ b/client/src/components/EventFilters.vue @@ -185,6 +185,10 @@ interface Props { players?: PlayerData[]; activeRooms?: any; aggregatedEvents?: any[]; + aggregated?: { + detailedEvents?: any[]; + counts?: Record; + }; }; compact?: boolean; } @@ -413,57 +417,76 @@ function applyFilters() { players: [], events: [], metrics: {}, - aggregatedCounts: {} + aggregatedCounts: {}, + sourceData: filters.value.timeMode }; - // Filter based on time mode + let sourceEvents: any[] = []; + + // Get events based on time mode if (filters.value.timeMode === 'active') { - // Use active rooms data + // Extract events from active rooms result.activeRooms = props.rawData.activeRooms; - // TODO: Extract events from active rooms - } else { - // Filter by time range - const fromMs = Date.parse(filters.value.rangeFrom || ''); - const toMs = Date.parse(filters.value.rangeTo || ''); - - if (props.rawData.aggregatedEvents) { - result.events = props.rawData.aggregatedEvents.filter((ev: any) => { - // Time filter - if (!Number.isNaN(fromMs) && !Number.isNaN(toMs)) { - const t = ev.timestamp; - if (typeof t === 'number' && (t < fromMs || t > toMs)) return false; - } - - // Round filter - if (filters.value.rounds.length > 0) { - if (!filters.value.rounds.includes(ev.round)) return false; - } - - // Game filter - if (filters.value.games.length > 0) { - if (!filters.value.games.includes(ev.gameVariant)) return false; - } - - // Room filter - if (filters.value.rooms.length > 0) { - if (!filters.value.rooms.includes(ev.roomId)) return false; - } - - return true; - }); + if (props.rawData.activeRooms?.rooms) { + sourceEvents = props.rawData.activeRooms.rooms.flatMap((room: any) => + (room.systemMessages || []).map((msg: any) => ({ + ...msg, + playerUuid: msg.playerUuid || undefined + })) + ); } + } else { + // Use aggregated events from multiple possible sources + sourceEvents = props.rawData.aggregatedEvents || + props.rawData.aggregated?.detailedEvents || + []; } + // Apply filters to events + const fromMs = Date.parse(filters.value.rangeFrom || ''); + const toMs = Date.parse(filters.value.rangeTo || ''); + + result.events = sourceEvents.filter((ev: any) => { + // Time filter (only for range mode) + if (filters.value.timeMode === 'range' && !Number.isNaN(fromMs) && !Number.isNaN(toMs)) { + const t = ev.timestamp; + if (typeof t === 'number' && (t < fromMs || t > toMs)) return false; + } + + // Round filter + if (filters.value.rounds.length > 0) { + if (!filters.value.rounds.includes(ev.round)) return false; + } + + // Game filter + if (filters.value.games.length > 0) { + if (!filters.value.games.includes(ev.gameVariant)) return false; + } + + // Room filter + if (filters.value.rooms.length > 0) { + if (!filters.value.rooms.includes(ev.roomId)) return false; + } + + // Player filter + if (filters.value.playerUuids.length > 0 && ev.playerUuid) { + if (!filters.value.playerUuids.includes(ev.playerUuid)) return false; + } + + return true; + }); + // Filter players if (props.rawData.players) { - result.players = props.rawData.players.filter((p: PlayerData) => { - if (filters.value.playerUuids.length > 0) { - return filters.value.playerUuids.includes(p.uuid); - } - return true; - }); + if (filters.value.playerUuids.length > 0) { + result.players = props.rawData.players.filter((p: PlayerData) => + filters.value.playerUuids.includes(p.uuid) + ); + } else { + result.players = [...props.rawData.players]; + } - // Calculate metrics from filtered players + // Calculate metrics from filtered players and their score history let totalP1Scores = 0; let totalP2Scores = 0; let p1Count = 0; @@ -478,17 +501,27 @@ function applyFilters() { if (player.roomScoreHistory) { player.roomScoreHistory.forEach(roomScore => { roomScore.scores.forEach(score => { - // Apply filters to scores - if (filters.value.rounds.length > 0 && !filters.value.rounds.includes(score.round)) return; - if (filters.value.games.length > 0 && !filters.value.games.includes(score.variant)) return; - if (filters.value.rooms.length > 0 && !filters.value.rooms.includes(roomScore.roomId)) return; + // Apply same filters to score history + let includeScore = true; - if (score.role === 'P1') { - totalP1Scores += score.score; - p1Count++; - } else if (score.role === 'P2') { - totalP2Scores += score.score; - p2Count++; + if (filters.value.rounds.length > 0 && !filters.value.rounds.includes(score.round)) { + includeScore = false; + } + if (filters.value.games.length > 0 && !filters.value.games.includes(score.variant)) { + includeScore = false; + } + if (filters.value.rooms.length > 0 && !filters.value.rooms.includes(roomScore.roomId)) { + includeScore = false; + } + + if (includeScore) { + if (score.role === 'P1') { + totalP1Scores += score.score; + p1Count++; + } else if (score.role === 'P2') { + totalP2Scores += score.score; + p2Count++; + } } }); }); @@ -504,7 +537,7 @@ function applyFilters() { }; } - // Count events by type + // Count events by type from filtered events const eventTypes = [ 'p1_propose', 'p1_no_offer', 'p2_snatch', 'p2_accept', 'p2_force', 'p2_no_force', 'p2_reject', @@ -516,6 +549,25 @@ function applyFilters() { result.aggregatedCounts[type] = result.events.filter((e: any) => e.kind === type).length; }); + // Add debug info + result._debug = { + totalSourceEvents: sourceEvents.length, + filteredEvents: result.events.length, + activeFilters: { + timeMode: filters.value.timeMode, + rounds: filters.value.rounds, + games: filters.value.games, + players: filters.value.playerUuids, + rooms: filters.value.rooms + }, + timeRange: filters.value.timeMode === 'range' ? { + from: filters.value.rangeFrom, + to: filters.value.rangeTo, + fromMs, + toMs + } : null + }; + emit('filtered', result); } diff --git a/client/src/components/FilterDataViewer.vue b/client/src/components/FilterDataViewer.vue new file mode 100644 index 0000000..3acbedf --- /dev/null +++ b/client/src/components/FilterDataViewer.vue @@ -0,0 +1,237 @@ + + + + + \ No newline at end of file diff --git a/client/src/views/Leaderboard.vue b/client/src/views/Leaderboard.vue index 845a8f4..ac0d923 100644 --- a/client/src/views/Leaderboard.vue +++ b/client/src/views/Leaderboard.vue @@ -50,6 +50,7 @@ :raw-data="{ players: allPlayersWithScores, aggregatedEvents: fullAggregatedEvents, + aggregated: rawActionsPayload?.aggregated, activeRooms: rawActionsPayload?.activeRooms }" :compact="true" @@ -84,6 +85,13 @@ }" /> + + + @@ -93,6 +101,7 @@ import { ref, computed, onMounted, onUnmounted, watch } from 'vue'; import { useRouter } from 'vue-router'; import EventChart from '../components/EventChart.vue'; import EventFilters from '../components/EventFilters.vue'; +import FilterDataViewer from '../components/FilterDataViewer.vue'; import GameLogo from '../components/GameLogo.vue'; import AppCredits from '../components/AppCredits.vue'; import { useEventFilters } from '../composables/useEventFilters'; @@ -432,6 +441,7 @@ const periodLabel = computed(() => { + function closeStreams() { try { esActions.value?.close(); } catch {} esActions.value = null;