nuevas metricas agregadas
This commit is contained in:
@@ -72,10 +72,10 @@
|
||||
</Transition>
|
||||
|
||||
<EventChart
|
||||
:event-types="EVENTS"
|
||||
:event-types="ALL_CHART_TYPES"
|
||||
:event-styles="EVENT_STYLES"
|
||||
:global-event-counts="eventFilters.globalEventCounts.value"
|
||||
:player-event-counts="playerEventCounts"
|
||||
:global-event-counts="combinedGlobalCounts"
|
||||
:player-event-counts="combinedPlayerCounts"
|
||||
:selected-player-uuid="selectedUuid"
|
||||
:player-bar-gradient="playerBarGradient"
|
||||
:view-mode="viewMode"
|
||||
@@ -132,8 +132,16 @@ const EVENTS = [
|
||||
'p1_shame', 'p1_no_shame', 'p1_report', 'p1_no_report'
|
||||
];
|
||||
|
||||
// Event styles matching OfferActions and OfferControls components
|
||||
// New metric types for additional charts
|
||||
const METRICS = [
|
||||
'players_seated', 'score_p1', 'score_p2', 'players_with_shame'
|
||||
];
|
||||
|
||||
const ALL_CHART_TYPES = [...EVENTS, ...METRICS];
|
||||
|
||||
// Event and metric styles matching OfferActions and OfferControls components
|
||||
const EVENT_STYLES: Record<string, { icon: string; color: string; gradient: string }> = {
|
||||
// Event types
|
||||
'p1_propose': { icon: '✨', color: '#667eea', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
|
||||
'p1_no_offer': { icon: '❌', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
|
||||
'p2_accept': { icon: '✓', color: '#10b981', gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)' },
|
||||
@@ -144,14 +152,118 @@ const EVENT_STYLES: Record<string, { icon: string; color: string; gradient: stri
|
||||
'p1_shame': { icon: '😶', color: '#fbbf24', gradient: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)' },
|
||||
'p1_no_shame': { icon: '🙂', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
|
||||
'p1_report': { icon: '⚖️', color: '#8b5cf6', gradient: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)' },
|
||||
'p1_no_report': { icon: '🤝', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' }
|
||||
'p1_no_report': { icon: '🤝', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
|
||||
|
||||
// Metric types
|
||||
'players_seated': { icon: '👥', color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)' },
|
||||
'score_p1': { icon: '🦃', color: '#16a34a', gradient: 'linear-gradient(135deg, #16a34a 0%, #15803d 100%)' },
|
||||
'score_p2': { icon: '🌽', color: '#d97706', gradient: 'linear-gradient(135deg, #d97706 0%, #b45309 100%)' },
|
||||
'players_with_shame': { icon: '😶', color: '#dc2626', gradient: 'linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)' }
|
||||
};
|
||||
|
||||
// Player score calculation functions (from PlayerStats.vue)
|
||||
function calculateP1Score(pavoTokens: number, eloteTokens: number): number {
|
||||
return pavoTokens * 1 + eloteTokens * 2;
|
||||
}
|
||||
|
||||
function calculateP2Score(pavoTokens: number, eloteTokens: number): number {
|
||||
return eloteTokens * 1 + pavoTokens * 2;
|
||||
}
|
||||
|
||||
// Additional metrics computation
|
||||
const additionalMetrics = ref<Record<string, number>>({
|
||||
players_seated: 0,
|
||||
score_p1: 0,
|
||||
score_p2: 0,
|
||||
players_with_shame: 0
|
||||
});
|
||||
|
||||
const selectedPlayerMetrics = ref<Record<string, number>>({
|
||||
players_seated: 0,
|
||||
score_p1: 0,
|
||||
score_p2: 0,
|
||||
players_with_shame: 0
|
||||
});
|
||||
|
||||
// Function to compute additional metrics from room data
|
||||
function computeMetrics(roomDetails: any) {
|
||||
let playersSeated = 0;
|
||||
let totalP1Score = 0;
|
||||
let totalP2Score = 0;
|
||||
let playersWithShame = 0;
|
||||
|
||||
Object.values(roomDetails || {}).forEach((room: any) => {
|
||||
const roomPlayers = room?.players || [];
|
||||
roomPlayers.forEach((player: any) => {
|
||||
// Count seated players (have a name)
|
||||
if (player?.name && player.name.trim()) {
|
||||
playersSeated++;
|
||||
|
||||
const pavoTokens = player.pavoTokens || 0;
|
||||
const eloteTokens = player.eloteTokens || 0;
|
||||
const shameTokens = player.shameTokens || 0;
|
||||
const role = player.role;
|
||||
|
||||
// Add scores based on role
|
||||
if (role === 'P1') {
|
||||
totalP1Score += calculateP1Score(pavoTokens, eloteTokens);
|
||||
} else if (role === 'P2') {
|
||||
totalP2Score += calculateP2Score(pavoTokens, eloteTokens);
|
||||
}
|
||||
|
||||
// Count players with shame
|
||||
if (shameTokens > 0) {
|
||||
playersWithShame++;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
additionalMetrics.value = {
|
||||
players_seated: playersSeated,
|
||||
score_p1: totalP1Score,
|
||||
score_p2: totalP2Score,
|
||||
players_with_shame: playersWithShame
|
||||
};
|
||||
|
||||
// Update selected player metrics if one is selected (using setTimeout to ensure selectedUuid is available)
|
||||
setTimeout(() => {
|
||||
if (selectedUuid.value) {
|
||||
computeSelectedPlayerMetrics(selectedUuid.value);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Function to compute metrics for a selected player
|
||||
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 = {
|
||||
players_seated: 1, // If player is selected, they're seated
|
||||
score_p1: 0, // Would need current player data to calculate
|
||||
score_p2: 0, // Would need current player data to calculate
|
||||
players_with_shame: 0 // Would need current player data to determine
|
||||
};
|
||||
}
|
||||
|
||||
// Combined counts for charts (events + metrics)
|
||||
const combinedGlobalCounts = computed(() => ({
|
||||
...eventFilters.globalEventCounts.value,
|
||||
...additionalMetrics.value
|
||||
}));
|
||||
|
||||
const combinedPlayerCounts = computed(() => ({
|
||||
...playerEventCounts.value,
|
||||
...selectedPlayerMetrics.value
|
||||
}));
|
||||
|
||||
// Watch for changes in filters and data source
|
||||
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter], () => {
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
});
|
||||
|
||||
// selectedUuid watch will be added after selectedUuid is declared
|
||||
|
||||
const rooms = ref<RoomInfo[]>([]);
|
||||
const players = ref<{ uuid: string; name: string; color?: string }[]>([]);
|
||||
const search = ref('');
|
||||
@@ -192,6 +304,13 @@ 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);
|
||||
}
|
||||
});
|
||||
|
||||
function initials(name: string): string {
|
||||
const n = (name || '').trim();
|
||||
if (!n) return '🙂';
|
||||
@@ -273,6 +392,9 @@ function setupStreams() {
|
||||
|
||||
eventFilters.updateActiveRoomsData(detailedEvents, counts);
|
||||
|
||||
// Compute additional metrics
|
||||
computeMetrics(details);
|
||||
|
||||
// Apply filters and update display
|
||||
if (eventFilters.dataSource.value === 'active-rooms') {
|
||||
eventFilters.applyFilters(EVENTS);
|
||||
@@ -462,7 +584,7 @@ function downloadCSV() {
|
||||
const currentEvents = eventFilters.currentSourceEvents.value;
|
||||
|
||||
// Create CSV headers
|
||||
const headers = ['Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'DataSource'];
|
||||
const headers = ['Type', 'Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'DataSource'];
|
||||
|
||||
// Create CSV rows - one row per individual event occurrence
|
||||
const rows: string[][] = [];
|
||||
@@ -471,6 +593,7 @@ function downloadCSV() {
|
||||
currentEvents.forEach(event => {
|
||||
if (EVENTS.includes(event.kind)) {
|
||||
rows.push([
|
||||
'event',
|
||||
event.kind,
|
||||
'1',
|
||||
event.round?.toString() || '',
|
||||
@@ -491,6 +614,7 @@ function downloadCSV() {
|
||||
// Add one row per occurrence for proper import compatibility
|
||||
for (let i = 0; i < count; i++) {
|
||||
rows.push([
|
||||
'event',
|
||||
eventType,
|
||||
'1',
|
||||
'', // Round info not available in aggregated player data
|
||||
@@ -504,6 +628,27 @@ function downloadCSV() {
|
||||
});
|
||||
});
|
||||
|
||||
// Add metric data
|
||||
const currentMetrics = eventFilters.dataSource.value === 'aggregated'
|
||||
? additionalMetrics.value
|
||||
: additionalMetrics.value; // Same source for now
|
||||
|
||||
METRICS.forEach(metricType => {
|
||||
const count = currentMetrics[metricType] || 0;
|
||||
if (count > 0) {
|
||||
rows.push([
|
||||
'metric',
|
||||
metricType,
|
||||
count.toString(),
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
eventFilters.dataSource.value
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Convert to CSV string
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
@@ -520,7 +665,7 @@ function downloadCSV() {
|
||||
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`;
|
||||
const filename = `datos_${dataSourceLabel}${filterLabel}_${timestamp}.csv`;
|
||||
|
||||
link.setAttribute('download', filename);
|
||||
link.style.visibility = 'hidden';
|
||||
|
||||
Reference in New Issue
Block a user