mejoras de codigo 2

This commit is contained in:
2025-08-29 13:56:26 -06:00
parent 82ab8d7709
commit 52051c4b63
2 changed files with 154 additions and 339 deletions

View File

@@ -43,7 +43,7 @@
class="bar-fill global shimmer" class="bar-fill global shimmer"
:style="{ :style="{
width: globalBarWidth(eventType) + '%', width: globalBarWidth(eventType) + '%',
background: eventStyles[eventType]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)' background: EVENT_STYLES[eventType]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)'
}" }"
></div> ></div>
<div <div
@@ -61,7 +61,7 @@
borderColor: getEventBorderColor(eventType) borderColor: getEventBorderColor(eventType)
}" }"
> >
<span class="event-icon">{{ eventStyles[eventType]?.icon || '📊' }}</span> <span class="event-icon">{{ EVENT_STYLES[eventType]?.icon || '📊' }}</span>
<span class="chip-label">{{ friendlyEventName(eventType) }}</span> <span class="chip-label">{{ friendlyEventName(eventType) }}</span>
<span class="chip-count global">{{ globalValueLabel(eventType) }}</span> <span class="chip-count global">{{ globalValueLabel(eventType) }}</span>
<span v-if="selectedPlayerUuid" class="chip-count player">{{ playerValueLabel(eventType) }}</span> <span v-if="selectedPlayerUuid" class="chip-count player">{{ playerValueLabel(eventType) }}</span>
@@ -94,7 +94,7 @@
class="ratio-segment" class="ratio-segment"
:style="{ :style="{
width: group.percentages[actionIndex] + '%', width: group.percentages[actionIndex] + '%',
background: eventStyles[action]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)' background: EVENT_STYLES[action]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)'
}" }"
> >
<div <div
@@ -105,7 +105,7 @@
borderColor: getEventBorderColor(action) borderColor: getEventBorderColor(action)
}" }"
> >
<span class="ratio-icon">{{ eventStyles[action]?.icon || '📊' }}</span> <span class="ratio-icon">{{ EVENT_STYLES[action]?.icon || '📊' }}</span>
<span class="ratio-label">{{ group.labels[actionIndex] }}</span> <span class="ratio-label">{{ group.labels[actionIndex] }}</span>
<span class="ratio-count">{{ group.values[actionIndex] }} ({{ Math.round(group.percentages[actionIndex]) }}%)</span> <span class="ratio-count">{{ group.values[actionIndex] }} ({{ Math.round(group.percentages[actionIndex]) }}%)</span>
</div> </div>
@@ -123,12 +123,14 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
interface Props { interface Props {
eventTypes: string[]; filteredData?: {
eventStyles: Record<string, { icon: string; color: string; gradient: string }>; events: any[];
globalEventCounts: Record<string, number>; players: any[];
playerEventCounts: Record<string, number>; metrics: Record<string, number>;
aggregatedCounts: Record<string, number>;
sourceData: string;
};
selectedPlayerUuid?: string; selectedPlayerUuid?: string;
playerBarGradient: string;
viewMode: 'count' | 'percent' | 'ratio'; viewMode: 'count' | 'percent' | 'ratio';
loading?: boolean; loading?: boolean;
filtersCollapsed?: boolean; filtersCollapsed?: boolean;
@@ -140,15 +142,6 @@ interface Props {
selectedPlayer?: string; selectedPlayer?: string;
selectedRoom?: string; selectedRoom?: string;
}; };
groupTotals?: {
offers: number;
responses: number;
force: number;
shame: number;
report: number;
averageScore: number;
totalPlayers: number;
};
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -156,51 +149,174 @@ const props = withDefaults(defineProps<Props>(), {
loading: false loading: false
}); });
// Event types and styles
const EVENTS = [
'p1_propose', 'p1_no_offer',
'p2_snatch', 'p2_accept', 'p2_force', 'p2_no_force', 'p2_reject',
'p1_shame', 'p1_no_shame', 'p1_report', 'p1_no_report'
];
const METRICS = [
'players_seated', 'score_p1', 'score_p2', 'players_with_shame', 'players_without_shame'
];
const ALL_CHART_TYPES = [...EVENTS, ...METRICS];
const EVENT_STYLES: Record<string, { icon: string; color: string; gradient: string }> = {
'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%)' },
'p2_reject': { icon: '✕', color: '#f59e0b', gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)' },
'p2_snatch': { icon: '👹', color: '#ef4444', gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' },
'p2_force': { icon: '⚡', color: '#667eea', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
'p2_no_force': { icon: '🚫', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
'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%)' },
'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%)' },
'players_without_shame': { icon: '👥', color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)' }
};
const highlighted = ref(''); const highlighted = ref('');
// Main data computations
const eventTypes = computed(() => ALL_CHART_TYPES);
const globalEventCounts = computed(() => {
if (!props.filteredData) return {};
return {
...props.filteredData.aggregatedCounts,
...props.filteredData.metrics
};
});
const playerEventCounts = computed(() => {
if (!props.selectedPlayerUuid || !props.filteredData) return {};
// Calculate player-specific counts from filtered events
const playerCounts: Record<string, number> = {};
EVENTS.forEach(eventType => {
playerCounts[eventType] = props.filteredData!.events.filter(
(e: any) => e.kind === eventType && e.playerUuid === props.selectedPlayerUuid
).length;
});
// Calculate player-specific metrics
const selectedPlayer = props.filteredData.players.find((p: any) => p.uuid === props.selectedPlayerUuid);
if (selectedPlayer) {
let totalP1Scores = 0;
let totalP2Scores = 0;
let p1Count = 0;
let p2Count = 0;
let playersWithShame = selectedPlayer.shameTokens > 0 ? 1 : 0;
if (selectedPlayer.roomScoreHistory) {
selectedPlayer.roomScoreHistory.forEach((roomScore: any) => {
roomScore.scores.forEach((score: any) => {
if (score.role === 'P1') {
totalP1Scores += score.score;
p1Count++;
} else if (score.role === 'P2') {
totalP2Scores += score.score;
p2Count++;
}
});
});
}
playerCounts.players_seated = 1;
playerCounts.score_p1 = p1Count > 0 ? Math.round((totalP1Scores / p1Count) * 10) / 10 : 0;
playerCounts.score_p2 = p2Count > 0 ? Math.round((totalP2Scores / p2Count) * 10) / 10 : 0;
playerCounts.players_with_shame = playersWithShame;
playerCounts.players_without_shame = 1 - playersWithShame;
}
return playerCounts;
});
const playerBarGradient = computed(() => '#8b5cf6');
// Group totals computation
const groupTotals = computed(() => {
const counts = globalEventCounts.value;
return {
offers: (counts.p1_propose || 0) + (counts.p1_no_offer || 0),
responses: (counts.p2_accept || 0) + (counts.p2_reject || 0) + (counts.p2_snatch || 0),
force: (counts.p2_force || 0) + (counts.p2_no_force || 0),
shame: (counts.p1_shame || 0) + (counts.p1_no_shame || 0),
report: (counts.p1_report || 0) + (counts.p1_no_report || 0),
averageScore: calculateAverageScore(),
totalPlayers: counts.players_seated || 0
};
});
function calculateAverageScore(): number {
if (!props.filteredData?.players.length) return 0;
let totalScores = 0;
let totalScoreCount = 0;
props.filteredData.players.forEach((player: any) => {
if (player.roomScoreHistory) {
player.roomScoreHistory.forEach((roomScore: any) => {
roomScore.scores.forEach((score: any) => {
totalScores += score.score;
totalScoreCount++;
});
});
}
});
return totalScoreCount > 0 ? Math.round((totalScores / totalScoreCount) * 10) / 10 : 0;
}
// Define ratio groups for superposed view // Define ratio groups for superposed view
const ratioGroups = computed(() => [ const ratioGroups = computed(() => [
{ {
name: 'Ofertas', name: 'Ofertas',
actions: ['p1_propose', 'p1_no_offer'], actions: ['p1_propose', 'p1_no_offer'],
labels: ['Ofrecer', 'No Ofrecer'], labels: ['Ofrecer', 'No Ofrecer'],
total: props.groupTotals?.offers || 0 total: groupTotals.value.offers
}, },
{ {
name: 'Respuestas', name: 'Respuestas',
actions: ['p2_accept', 'p2_reject', 'p2_snatch'], actions: ['p2_accept', 'p2_reject', 'p2_snatch'],
labels: ['Aceptar', 'Rechazar', 'Robar'], labels: ['Aceptar', 'Rechazar', 'Robar'],
total: props.groupTotals?.responses || 0 total: groupTotals.value.responses
}, },
{ {
name: 'Forzar', name: 'Forzar',
actions: ['p2_force', 'p2_no_force'], actions: ['p2_force', 'p2_no_force'],
labels: ['Forzar', 'No Forzar'], labels: ['Forzar', 'No Forzar'],
total: props.groupTotals?.force || 0 total: groupTotals.value.force
}, },
{ {
name: 'Avergonzar', name: 'Avergonzar',
actions: ['p1_shame', 'p1_no_shame'], actions: ['p1_shame', 'p1_no_shame'],
labels: ['Asignar', 'No Asignar'], labels: ['Asignar', 'No Asignar'],
total: props.groupTotals?.shame || 0 total: groupTotals.value.shame
}, },
{ {
name: 'Denunciar', name: 'Denunciar',
actions: ['p1_report', 'p1_no_report'], actions: ['p1_report', 'p1_no_report'],
labels: ['Denunciar', 'No Denunciar'], labels: ['Denunciar', 'No Denunciar'],
total: props.groupTotals?.report || 0 total: groupTotals.value.report
}, },
{ {
name: 'Puntaje Promedio', name: 'Puntaje Promedio',
actions: ['score_p1', 'score_p2'], actions: ['score_p1', 'score_p2'],
labels: ['P1', 'P2'], labels: ['P1', 'P2'],
total: props.groupTotals?.averageScore ? props.groupTotals.averageScore.toFixed(1) : '0.0' total: groupTotals.value.averageScore.toFixed(1)
}, },
{ {
name: 'Total Jugadores', name: 'Total Jugadores',
actions: ['players_with_shame', 'players_without_shame'], actions: ['players_with_shame', 'players_without_shame'],
labels: ['Con vergüenza', 'Sin vergüenza'], labels: ['Con vergüenza', 'Sin vergüenza'],
total: props.groupTotals?.totalPlayers || 0, total: groupTotals.value.totalPlayers,
isCustomRatio: true // Special handling needed isCustomRatio: true // Special handling needed
} }
]); ]);
@@ -209,8 +325,8 @@ const ratioGroups = computed(() => [
const ratioData = computed(() => { const ratioData = computed(() => {
return ratioGroups.value.map(group => { return ratioGroups.value.map(group => {
const counts = props.selectedPlayerUuid const counts = props.selectedPlayerUuid
? props.playerEventCounts ? playerEventCounts.value
: props.globalEventCounts; : globalEventCounts.value;
let values = group.actions.map(action => counts[action] || 0); let values = group.actions.map(action => counts[action] || 0);
@@ -234,55 +350,55 @@ const ratioData = computed(() => {
// Global calculations // Global calculations
const globalMax = computed(() => { const globalMax = computed(() => {
const vals = props.eventTypes.map(k => props.globalEventCounts[k] || 0); const vals = eventTypes.value.map(k => globalEventCounts.value[k] || 0);
const m = Math.max(0, ...vals); const m = Math.max(0, ...vals);
return m || 1; return m || 1;
}); });
const globalTotal = computed(() => const globalTotal = computed(() =>
props.eventTypes.reduce((acc, k) => acc + (props.globalEventCounts[k] || 0), 0) || 1 eventTypes.value.reduce((acc, k) => acc + (globalEventCounts.value[k] || 0), 0) || 1
); );
function globalBarWidth(eventType: string) { function globalBarWidth(eventType: string) {
const v = props.globalEventCounts[eventType] || 0; const v = globalEventCounts.value[eventType] || 0;
return Math.round((v / (props.viewMode === 'percent' ? globalTotal.value : globalMax.value)) * 100); return Math.round((v / (props.viewMode === 'percent' ? globalTotal.value : globalMax.value)) * 100);
} }
function globalValueLabel(eventType: string) { function globalValueLabel(eventType: string) {
const v = props.globalEventCounts[eventType] || 0; const v = globalEventCounts.value[eventType] || 0;
return props.viewMode === 'percent' ? `${Math.round((v / globalTotal.value) * 100)}%` : String(v); return props.viewMode === 'percent' ? `${Math.round((v / globalTotal.value) * 100)}%` : String(v);
} }
// Player calculations // Player calculations
const playerMax = computed(() => { const playerMax = computed(() => {
const vals = props.eventTypes.map(k => props.playerEventCounts[k] || 0); const vals = eventTypes.value.map(k => playerEventCounts.value[k] || 0);
const m = Math.max(0, ...vals); const m = Math.max(0, ...vals);
return m || 1; return m || 1;
}); });
const playerTotal = computed(() => const playerTotal = computed(() =>
props.eventTypes.reduce((acc, k) => acc + (props.playerEventCounts[k] || 0), 0) || 1 eventTypes.value.reduce((acc, k) => acc + (playerEventCounts.value[k] || 0), 0) || 1
); );
function playerBarWidth(eventType: string) { function playerBarWidth(eventType: string) {
const v = props.playerEventCounts[eventType] || 0; const v = playerEventCounts.value[eventType] || 0;
return Math.round((v / (props.viewMode === 'percent' ? playerTotal.value : playerMax.value)) * 100); return Math.round((v / (props.viewMode === 'percent' ? playerTotal.value : playerMax.value)) * 100);
} }
function playerValueLabel(eventType: string) { function playerValueLabel(eventType: string) {
const v = props.playerEventCounts[eventType] || 0; const v = playerEventCounts.value[eventType] || 0;
return props.viewMode === 'percent' ? `${Math.round((v / playerTotal.value) * 100)}%` : String(v); return props.viewMode === 'percent' ? `${Math.round((v / playerTotal.value) * 100)}%` : String(v);
} }
// Styling helpers // Styling helpers
function getEventChipBg(eventType: string): string { function getEventChipBg(eventType: string): string {
const style = props.eventStyles[eventType]; const style = EVENT_STYLES[eventType];
if (!style) return 'rgba(255,255,255,0.82)'; if (!style) return 'rgba(255,255,255,0.82)';
return `linear-gradient(135deg, ${style.color}15 0%, rgba(255,255,255,0.9) 100%)`; return `linear-gradient(135deg, ${style.color}15 0%, rgba(255,255,255,0.9) 100%)`;
} }
function getEventBorderColor(eventType: string): string { function getEventBorderColor(eventType: string): string {
const style = props.eventStyles[eventType]; const style = EVENT_STYLES[eventType];
if (!style) return 'rgba(229,231,235,0.9)'; if (!style) return 'rgba(229,231,235,0.9)';
return `${style.color}40`; return `${style.color}40`;
} }

View File

@@ -64,25 +64,12 @@
<EventChart <EventChart
:event-types="ALL_CHART_TYPES" :filtered-data="filteredData"
:event-styles="EVENT_STYLES"
:global-event-counts="combinedGlobalCounts"
:player-event-counts="combinedPlayerCounts"
:selected-player-uuid="filterState.playerUuids.length ? filterState.playerUuids[0] : ''" :selected-player-uuid="filterState.playerUuids.length ? filterState.playerUuids[0] : ''"
:player-bar-gradient="playerBarGradient"
view-mode="ratio" view-mode="ratio"
:loading="loading" :loading="loading"
:filters-collapsed="filtersCollapsed" :filters-collapsed="filtersCollapsed"
:active-filters="activeFilters" :active-filters="activeFilters"
:group-totals="{
offers: offersTotal,
responses: responsesTotal,
force: forceTotal,
shame: shameTotal,
report: reportTotal,
averageScore: averageScoreTotal,
totalPlayers: totalPlayersCount
}"
/> />
<!-- Filter Data Viewer Component --> <!-- Filter Data Viewer Component -->
@@ -104,12 +91,10 @@ import EventFilters from '../components/EventFilters.vue';
import FilterDataViewer from '../components/FilterDataViewer.vue'; import FilterDataViewer from '../components/FilterDataViewer.vue';
import GameLogo from '../components/GameLogo.vue'; import GameLogo from '../components/GameLogo.vue';
import AppCredits from '../components/AppCredits.vue'; import AppCredits from '../components/AppCredits.vue';
import { useEventFilters } from '../composables/useEventFilters';
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const eventFilters = useEventFilters();
const filtersCollapsed = ref(false); const filtersCollapsed = ref(false);
// Filter state using new EventFilters approach // Filter state using new EventFilters approach
@@ -135,263 +120,15 @@ function clearTimeFilter() {
// Handle filtered data from EventFilters component // Handle filtered data from EventFilters component
function onFiltered(data: any) { function onFiltered(data: any) {
filteredData.value = data; filteredData.value = data;
if (data) {
// Update global counts from filtered data
eventFilters.globalEventCounts.value = data.aggregatedCounts || {};
// Update metrics
additionalMetrics.value = data.metrics || {};
// Recompute selected player metrics
computeSelectedPlayersMetrics();
}
} }
const EVENTS = [
'p1_propose', 'p1_no_offer',
'p2_snatch', 'p2_accept', 'p2_force', 'p2_no_force', 'p2_reject',
'p1_shame', 'p1_no_shame', 'p1_report', 'p1_no_report'
];
// New metric types for additional charts
const METRICS = [
'players_seated', 'score_p1', 'score_p2', 'players_with_shame', 'players_without_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%)' },
'p2_reject': { icon: '✕', color: '#f59e0b', gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)' },
'p2_snatch': { icon: '👹', color: '#ef4444', gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' },
'p2_force': { icon: '⚡', color: '#667eea', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
'p2_no_force': { icon: '🚫', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
'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%)' },
// 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%)' },
'players_without_shame': { icon: '👥', color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)' }
};
// Additional metrics computation
const additionalMetrics = ref<Record<string, number>>({
players_seated: 0,
score_p1: 0,
score_p2: 0,
players_with_shame: 0,
players_without_shame: 0
});
const selectedPlayerMetrics = ref<Record<string, number>>({
players_seated: 0,
score_p1: 0,
score_p2: 0,
players_with_shame: 0,
players_without_shame: 0
});
// Store room score history from players // Store room score history from players
const allPlayersWithScores = ref<any[]>([]); const allPlayersWithScores = ref<any[]>([]);
// Function to check if a score passes the current filters
function scorePassesFilters(score: any, roomId: string) {
// Room filter (array): empty means all
const rf = filterState.value.rooms;
if (rf.length && !rf.includes(String(roomId))) return false;
// Round filter (array)
const rds = filterState.value.rounds;
if (rds.length && !rds.includes(Number(score.round))) return false;
// Game variant filter (array)
const gfs = filterState.value.games;
if (gfs.length && !gfs.includes(String(score.variant))) return false;
return true;
}
// Function to compute additional metrics from players' score history
function computeMetricsFromScores() {
if (!allPlayersWithScores.value.length) return;
let totalP1Scores = 0;
let totalP2Scores = 0;
let p1Count = 0;
let p2Count = 0;
let playersWithShame = 0;
let totalPlayersWithNames = 0;
// Get score data from players with room score history
allPlayersWithScores.value.forEach((player: any) => {
if (player.name) {
totalPlayersWithNames++;
// Count players with shame tokens
if (player.shameTokens && player.shameTokens > 0) {
playersWithShame++;
}
// Extract scores from roomScoreHistory if available
if (player.roomScoreHistory) {
player.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++;
}
});
});
}
}
});
const avgP1Score = p1Count > 0 ? totalP1Scores / p1Count : 0;
const avgP2Score = p2Count > 0 ? totalP2Scores / p2Count : 0;
additionalMetrics.value = {
players_seated: totalPlayersWithNames,
score_p1: Math.round(avgP1Score * 10) / 10, // Round to 1 decimal
score_p2: Math.round(avgP2Score * 10) / 10,
players_with_shame: playersWithShame,
players_without_shame: totalPlayersWithNames - playersWithShame
};
}
// Function to compute metrics for selected players (multi-select)
function computeSelectedPlayersMetrics() {
const uuids = filterState.value.playerUuids;
if (!uuids.length) {
selectedPlayerMetrics.value = {
players_seated: 0,
score_p1: 0,
score_p2: 0,
players_with_shame: 0,
players_without_shame: 0
};
return;
}
let totalP1Scores = 0;
let totalP2Scores = 0;
let p1Count = 0;
let p2Count = 0;
let playersWithShame = 0;
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: 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: playersWithShame,
players_without_shame: uuids.length - playersWithShame
};
}
// Combined counts for charts (events + metrics)
const combinedGlobalCounts = computed(() => ({
...eventFilters.globalEventCounts.value,
...additionalMetrics.value
}));
const combinedPlayerCounts = computed(() => ({
...playerEventCounts.value,
...selectedPlayerMetrics.value
}));
// Chart group totals
const offersTotal = computed(() => {
const propose = eventFilters.globalEventCounts.value.p1_propose || 0;
const noOffer = eventFilters.globalEventCounts.value.p1_no_offer || 0;
return propose + noOffer;
});
const responsesTotal = computed(() => {
const accept = eventFilters.globalEventCounts.value.p2_accept || 0;
const reject = eventFilters.globalEventCounts.value.p2_reject || 0;
const snatch = eventFilters.globalEventCounts.value.p2_snatch || 0;
return accept + reject + snatch;
});
const forceTotal = computed(() => {
const force = eventFilters.globalEventCounts.value.p2_force || 0;
const noForce = eventFilters.globalEventCounts.value.p2_no_force || 0;
return force + noForce;
});
const shameTotal = computed(() => {
const shame = eventFilters.globalEventCounts.value.p1_shame || 0;
const noShame = eventFilters.globalEventCounts.value.p1_no_shame || 0;
return shame + noShame;
});
const reportTotal = computed(() => {
const report = eventFilters.globalEventCounts.value.p1_report || 0;
const noReport = eventFilters.globalEventCounts.value.p1_no_report || 0;
return report + noReport;
});
const averageScoreTotal = computed(() => {
if (!allPlayersWithScores.value.length) return 0;
let totalScores = 0;
let totalScoreCount = 0;
// Sum all individual scores from all players regardless of role, applying filters
allPlayersWithScores.value.forEach((player: any) => {
if (player.roomScoreHistory) {
player.roomScoreHistory.forEach((roomScore: any) => {
roomScore.scores.forEach((score: any) => {
// Apply filters to scores
if (!scorePassesFilters(score, roomScore.roomId)) {
return;
}
totalScores += score.score;
totalScoreCount++;
});
});
}
});
return totalScoreCount > 0 ? Math.round((totalScores / totalScoreCount) * 10) / 10 : 0;
});
const totalPlayersCount = computed(() => {
return additionalMetrics.value.players_seated || 0;
});
// Active filters object // Active filters object
const activeFilters = computed(() => ({ const activeFilters = computed(() => ({
@@ -403,15 +140,8 @@ const activeFilters = computed(() => ({
selectedRoom: filterState.value.rooms.length ? `${filterState.value.rooms.length} salas` : undefined selectedRoom: filterState.value.rooms.length ? `${filterState.value.rooms.length} salas` : undefined
})); }));
// Watch for changes in filter state to update selected player metrics
watch(filterState, () => {
computeSelectedPlayersMetrics();
}, { deep: true });
const availableRooms = ref<{ roomId: string; name: string; playerCount?: number }[]>([]); const availableRooms = ref<{ roomId: string; name: string; playerCount?: number }[]>([]);
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>>>({});
@@ -421,8 +151,6 @@ function goHome() {
} }
// Dynamic per-player overlay bar gradient and label color
const playerBarGradient = computed(() => '#8b5cf6');
@@ -472,17 +200,6 @@ function setupStreams() {
}); });
// Mapear counts por jugador
const byUuid: Record<string, Record<string, number>> = {};
list.forEach((p: any) => {
const uuid = String(p?.uuid || '');
if (!uuid) return;
const src = p?.counts || {};
const normalized: Record<string, number> = Object.fromEntries(EVENTS.map(k => [k, 0])) as any;
EVENTS.forEach(k => { normalized[k] = Number(src[k] || 0); });
byUuid[uuid] = normalized;
});
playersActionsByUuid.value = byUuid;
// Store aggregated events for the EventFilters component // Store aggregated events for the EventFilters component
const aggEvents = Array.isArray(data?.aggregated?.detailedEvents) ? data.aggregated.detailedEvents : []; const aggEvents = Array.isArray(data?.aggregated?.detailedEvents) ? data.aggregated.detailedEvents : [];
@@ -518,11 +235,6 @@ function setupStreams() {
})).sort((a: any, b: any) => (a._lastSeen - b._lastSeen)).map(({ _lastSeen, ...rest }: any) => rest); })).sort((a: any, b: any) => (a._lastSeen - b._lastSeen)).map(({ _lastSeen, ...rest }: any) => rest);
} }
// Recalculate metrics from score history
computeMetricsFromScores();
// Actualizar conteos del/los jugador(es) seleccionado(s)
updateSelectedPlayersCounts();
} finally { } finally {
loading.value = false; loading.value = false;
@@ -535,23 +247,10 @@ function setupStreams() {
function updateSelectedPlayersCounts() {
playerLoading.value = true;
const next: Record<string, number> = Object.fromEntries(EVENTS.map(k => [k, 0])) as any;
const byUuid = playersActionsByUuid.value;
filterState.value.playerUuids.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;
}
onMounted(() => { onMounted(() => {
setupStreams(); setupStreams();
// Initialize with aggregated data as default
eventFilters.globalEventCounts.value = { ...eventFilters.globalEventCountsAggregated.value };
// Initialize default time range for filter state // Initialize default time range for filter state
const now = new Date(); const now = new Date();