diff --git a/client/src/components/EventChart.vue b/client/src/components/EventChart.vue index faa57f9..c8f8e03 100644 --- a/client/src/components/EventChart.vue +++ b/client/src/components/EventChart.vue @@ -1,6 +1,22 @@ @@ -80,6 +104,7 @@ interface RoomState { players?: any[]; systemMessages?: { kind: string }[] } const loading = ref(false); const eventFilters = useEventFilters(); +const filtersCollapsed = ref(false); // View mode cycling type ViewMode = 'count' | 'percent' | 'ratio'; @@ -433,6 +458,78 @@ 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 = ['Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'DataSource']; + + // Create CSV rows - one row per individual event occurrence + const rows: string[][] = []; + + // Add detailed individual event rows if we have detailed data + currentEvents.forEach(event => { + if (EVENTS.includes(event.kind)) { + rows.push([ + event.kind, + '1', + event.round?.toString() || '', + event.gameVariant || '', + event.playerUuid || '', + event.playerName || '', + eventFilters.dataSource.value + ]); + } + }); + + // Also add per-player aggregated data from playersActionsByUuid + Object.entries(playersActionsByUuid.value).forEach(([uuid, counts]) => { + const player = players.value.find(p => p.uuid === uuid); + EVENTS.forEach(eventType => { + const count = counts[eventType] || 0; + if (count > 0) { + // Add one row per occurrence for proper import compatibility + for (let i = 0; i < count; i++) { + rows.push([ + eventType, + '1', + '', // Round info not available in aggregated player data + '', // GameVariant info not available in aggregated player data + uuid, + player?.name || '', + 'player-aggregated' + ]); + } + } + }); + }); + + // Convert to CSV string + const csvContent = [ + headers.join(','), + ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) + ].join('\n'); + + // Create and download file + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + + // Generate filename with current filters and timestamp + const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); + const dataSourceLabel = eventFilters.dataSource.value === 'aggregated' ? 'agregados' : 'activos'; + const filterLabel = eventFilters.hasActiveFilters.value ? `_${eventFilters.filterSummary.value.replace(/\s+/g, '_')}` : ''; + const filename = `eventos_${dataSourceLabel}${filterLabel}_${timestamp}.csv`; + + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} +