mejoras de UI
This commit is contained in:
BIN
Untitled.png
Normal file
BIN
Untitled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 347 KiB |
@@ -4,13 +4,13 @@
|
|||||||
<h2 class="panel-title">Eventos y comparación</h2>
|
<h2 class="panel-title">Eventos y comparación</h2>
|
||||||
<div v-if="filtersCollapsed && (activeFilters?.hasFilters || selectedPlayerUuid)" class="active-filters-summary">
|
<div v-if="filtersCollapsed && (activeFilters?.hasFilters || selectedPlayerUuid)" class="active-filters-summary">
|
||||||
<span class="filter-tag" v-if="activeFilters?.dataSource !== 'aggregated'">
|
<span class="filter-tag" v-if="activeFilters?.dataSource !== 'aggregated'">
|
||||||
{{ activeFilters.dataSource === 'active-rooms' ? '🔴 Tiempo Real' : '📁 Agregado' }}
|
{{ activeFilters?.dataSource === 'active-rooms' ? '🔴 Tiempo Real' : '📁 Agregado' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="filter-tag" v-if="activeFilters?.round !== 'all'">
|
<span class="filter-tag" v-if="activeFilters?.round !== 'all'">
|
||||||
Round {{ activeFilters.round }}
|
Round {{ activeFilters?.round }}
|
||||||
</span>
|
</span>
|
||||||
<span class="filter-tag" v-if="activeFilters?.game !== 'all'">
|
<span class="filter-tag" v-if="activeFilters?.game !== 'all'">
|
||||||
{{ activeFilters.game }}
|
{{ activeFilters?.game }}
|
||||||
</span>
|
</span>
|
||||||
<span class="filter-tag player-tag" v-if="selectedPlayerUuid && activeFilters?.selectedPlayer">
|
<span class="filter-tag player-tag" v-if="selectedPlayerUuid && activeFilters?.selectedPlayer">
|
||||||
👤 {{ activeFilters.selectedPlayer }}
|
👤 {{ activeFilters.selectedPlayer }}
|
||||||
@@ -68,13 +68,20 @@
|
|||||||
<!-- Ratio bars view -->
|
<!-- Ratio bars view -->
|
||||||
<div v-else class="ratio-bars">
|
<div v-else class="ratio-bars">
|
||||||
<div
|
<div
|
||||||
v-for="(group, index) in ratioData"
|
v-for="group in ratioData"
|
||||||
:key="group.name"
|
:key="group.name"
|
||||||
class="ratio-group"
|
class="ratio-group"
|
||||||
:class="{ highlight: highlighted === group.name }"
|
:class="{ highlight: highlighted === group.name }"
|
||||||
@mouseenter="highlighted = group.name"
|
@mouseenter="highlighted = group.name"
|
||||||
@mouseleave="highlighted = ''"
|
@mouseleave="highlighted = ''"
|
||||||
>
|
>
|
||||||
|
<!-- Group Header -->
|
||||||
|
<div class="ratio-group-header">
|
||||||
|
<h3 class="group-title">{{ group.name }}</h3>
|
||||||
|
<span class="group-total">{{ group.total }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ratio Bar -->
|
||||||
<div class="ratio-bar">
|
<div class="ratio-bar">
|
||||||
<div
|
<div
|
||||||
v-for="(action, actionIndex) in group.actions"
|
v-for="(action, actionIndex) in group.actions"
|
||||||
@@ -129,6 +136,15 @@ 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>(), {
|
||||||
@@ -139,48 +155,55 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
const highlighted = ref('');
|
const highlighted = ref('');
|
||||||
|
|
||||||
// Define ratio groups for superposed view
|
// Define ratio groups for superposed view
|
||||||
const ratioGroups = [
|
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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fuerzas',
|
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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Vergüenzas',
|
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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Denuncias',
|
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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Puntuaciones',
|
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'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Jugadores',
|
name: 'Total Jugadores',
|
||||||
actions: ['players_with_shame', 'players_seated'],
|
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,
|
||||||
isCustomRatio: true // Special handling needed
|
isCustomRatio: true // Special handling needed
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
|
||||||
// Compute ratio data for each group
|
// Compute ratio data for each group
|
||||||
const ratioData = computed(() => {
|
const ratioData = computed(() => {
|
||||||
return ratioGroups.map(group => {
|
return ratioGroups.value.map(group => {
|
||||||
const counts = props.selectedPlayerUuid
|
const counts = props.selectedPlayerUuid
|
||||||
? props.playerEventCounts
|
? props.playerEventCounts
|
||||||
: props.globalEventCounts;
|
: props.globalEventCounts;
|
||||||
@@ -188,10 +211,9 @@ const ratioData = computed(() => {
|
|||||||
let values = group.actions.map(action => counts[action] || 0);
|
let values = group.actions.map(action => counts[action] || 0);
|
||||||
|
|
||||||
// Special handling for players ratio (shame vs no shame)
|
// Special handling for players ratio (shame vs no shame)
|
||||||
if (group.isCustomRatio && group.name === 'Jugadores') {
|
if (group.isCustomRatio && group.name === 'Total Jugadores') {
|
||||||
const playersWithShame = counts['players_with_shame'] || 0;
|
const playersWithShame = counts['players_with_shame'] || 0;
|
||||||
const totalPlayersSeated = counts['players_seated'] || 0;
|
const playersWithoutShame = counts['players_without_shame'] || 0;
|
||||||
const playersWithoutShame = Math.max(0, totalPlayersSeated - playersWithShame);
|
|
||||||
values = [playersWithShame, playersWithoutShame];
|
values = [playersWithShame, playersWithoutShame];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,15 +506,14 @@ function friendlyEventName(eventType: string): string {
|
|||||||
.ratio-bars {
|
.ratio-bars {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 8px;
|
||||||
flex: 1 1 auto;
|
|
||||||
min-height: 0;
|
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ratio-group {
|
.ratio-group {
|
||||||
flex: 1 1 0;
|
flex: 0 0 auto;
|
||||||
min-height: 60px;
|
min-height: 120px;
|
||||||
|
margin-bottom: 8px;
|
||||||
transition: transform .18s ease;
|
transition: transform .18s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,9 +521,45 @@ function friendlyEventName(eventType: string): string {
|
|||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Group header styles */
|
||||||
|
.ratio-group-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
border: 1px solid rgba(229, 231, 235, 0.4);
|
||||||
|
border-radius: 10px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #1e293b;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-total {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #667eea;
|
||||||
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(102, 126, 234, 0.08) 100%);
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.25);
|
||||||
|
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15);
|
||||||
|
min-width: 48px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.ratio-bar {
|
.ratio-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 60px;
|
||||||
background: linear-gradient(135deg, rgba(238,242,255,0.4) 0%, rgba(199,210,254,0.2) 100%);
|
background: linear-gradient(135deg, rgba(238,242,255,0.4) 0%, rgba(199,210,254,0.2) 100%);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
<button class="btn" @click="downloadCSV" :disabled="loading" title="Descargar datos actuales como CSV">
|
<button class="btn" @click="downloadCSV" :disabled="loading" title="Descargar datos actuales como CSV">
|
||||||
📊 CSV
|
📊 CSV
|
||||||
</button>
|
</button>
|
||||||
<button class="btn toggle" @click="cycleViewMode" :disabled="loading">
|
|
||||||
{{ viewModeLabel }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,16 +104,18 @@
|
|||||||
:player-event-counts="combinedPlayerCounts"
|
:player-event-counts="combinedPlayerCounts"
|
||||||
:selected-player-uuid="selectedUuid"
|
:selected-player-uuid="selectedUuid"
|
||||||
:player-bar-gradient="playerBarGradient"
|
:player-bar-gradient="playerBarGradient"
|
||||||
:view-mode="viewMode"
|
view-mode="ratio"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:filters-collapsed="filtersCollapsed"
|
:filters-collapsed="filtersCollapsed"
|
||||||
:active-filters="{
|
:active-filters="activeFilters"
|
||||||
dataSource: eventFilters.dataSource.value,
|
:group-totals="{
|
||||||
round: eventFilters.roundFilter.value.toString(),
|
offers: offersTotal,
|
||||||
game: eventFilters.gameFilter.value,
|
responses: responsesTotal,
|
||||||
hasFilters: eventFilters.hasActiveFilters.value || !!selectedRoomId,
|
force: forceTotal,
|
||||||
selectedPlayer: selectedUuid ? players.find(p => p.uuid === selectedUuid)?.name || 'Jugador' : undefined,
|
shame: shameTotal,
|
||||||
selectedRoom: selectedRoomId ? availableRooms.find(r => r.roomId === selectedRoomId)?.name || 'Sala' : undefined
|
report: reportTotal,
|
||||||
|
averageScore: averageScoreTotal,
|
||||||
|
totalPlayers: totalPlayersCount
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,25 +135,6 @@ const loading = ref(false);
|
|||||||
const eventFilters = useEventFilters();
|
const eventFilters = useEventFilters();
|
||||||
const filtersCollapsed = ref(false);
|
const filtersCollapsed = ref(false);
|
||||||
|
|
||||||
// View mode cycling
|
|
||||||
type ViewMode = 'count' | 'percent' | 'ratio';
|
|
||||||
const viewMode = ref<ViewMode>('count');
|
|
||||||
|
|
||||||
const viewModeLabel = computed(() => {
|
|
||||||
switch (viewMode.value) {
|
|
||||||
case 'count': return 'Ver conteos';
|
|
||||||
case 'percent': return 'Ver %';
|
|
||||||
case 'ratio': return 'Ver superpuesto';
|
|
||||||
default: return 'Ver conteos';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function cycleViewMode() {
|
|
||||||
const modes: ViewMode[] = ['count', 'percent', 'ratio'];
|
|
||||||
const currentIndex = modes.indexOf(viewMode.value);
|
|
||||||
const nextIndex = (currentIndex + 1) % modes.length;
|
|
||||||
viewMode.value = modes[nextIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
const EVENTS = [
|
const EVENTS = [
|
||||||
'p1_propose', 'p1_no_offer',
|
'p1_propose', 'p1_no_offer',
|
||||||
@@ -164,7 +144,7 @@ const EVENTS = [
|
|||||||
|
|
||||||
// New metric types for additional charts
|
// New metric types for additional charts
|
||||||
const METRICS = [
|
const METRICS = [
|
||||||
'players_seated', 'score_p1', 'score_p2', 'players_with_shame'
|
'players_seated', 'score_p1', 'score_p2', 'players_with_shame', 'players_without_shame'
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALL_CHART_TYPES = [...EVENTS, ...METRICS];
|
const ALL_CHART_TYPES = [...EVENTS, ...METRICS];
|
||||||
@@ -188,91 +168,113 @@ const EVENT_STYLES: Record<string, { icon: string; color: string; gradient: stri
|
|||||||
'players_seated': { icon: '👥', color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 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_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%)' },
|
'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_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%)' }
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
// Additional metrics computation
|
||||||
const additionalMetrics = ref<Record<string, number>>({
|
const additionalMetrics = ref<Record<string, number>>({
|
||||||
players_seated: 0,
|
players_seated: 0,
|
||||||
score_p1: 0,
|
score_p1: 0,
|
||||||
score_p2: 0,
|
score_p2: 0,
|
||||||
players_with_shame: 0
|
players_with_shame: 0,
|
||||||
|
players_without_shame: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedPlayerMetrics = ref<Record<string, number>>({
|
const selectedPlayerMetrics = ref<Record<string, number>>({
|
||||||
players_seated: 0,
|
players_seated: 0,
|
||||||
score_p1: 0,
|
score_p1: 0,
|
||||||
score_p2: 0,
|
score_p2: 0,
|
||||||
players_with_shame: 0
|
players_with_shame: 0,
|
||||||
|
players_without_shame: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to compute additional metrics from room data
|
// Store room score history from players
|
||||||
function computeMetrics(roomDetails: any) {
|
const allPlayersWithScores = ref<any[]>([]);
|
||||||
let playersSeated = 0;
|
|
||||||
let totalP1Score = 0;
|
// Function to compute additional metrics from players' score history
|
||||||
let totalP2Score = 0;
|
function computeMetricsFromScores() {
|
||||||
|
if (!allPlayersWithScores.value.length) return;
|
||||||
|
|
||||||
|
let totalP1Scores = 0;
|
||||||
|
let totalP2Scores = 0;
|
||||||
|
let p1Count = 0;
|
||||||
|
let p2Count = 0;
|
||||||
let playersWithShame = 0;
|
let playersWithShame = 0;
|
||||||
|
let totalPlayersWithNames = 0;
|
||||||
|
|
||||||
Object.values(roomDetails || {}).forEach((room: any) => {
|
// Get score data from players with room score history
|
||||||
const roomPlayers = room?.players || [];
|
allPlayersWithScores.value.forEach((player: any) => {
|
||||||
roomPlayers.forEach((player: any) => {
|
if (player.name) {
|
||||||
// Count seated players (have a name)
|
totalPlayersWithNames++;
|
||||||
if (player?.name && player.name.trim()) {
|
|
||||||
playersSeated++;
|
|
||||||
|
|
||||||
const pavoTokens = player.pavoTokens || 0;
|
// Extract scores from roomScoreHistory if available
|
||||||
const eloteTokens = player.eloteTokens || 0;
|
if (player.roomScoreHistory) {
|
||||||
const shameTokens = player.shameTokens || 0;
|
player.roomScoreHistory.forEach((roomScore: any) => {
|
||||||
const role = player.role;
|
roomScore.scores.forEach((score: any) => {
|
||||||
|
if (score.role === 'P1') {
|
||||||
|
totalP1Scores += score.score;
|
||||||
|
p1Count++;
|
||||||
|
} else if (score.role === 'P2') {
|
||||||
|
totalP2Scores += score.score;
|
||||||
|
p2Count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add scores based on role
|
const avgP1Score = p1Count > 0 ? totalP1Scores / p1Count : 0;
|
||||||
if (role === 'P1') {
|
const avgP2Score = p2Count > 0 ? totalP2Scores / p2Count : 0;
|
||||||
totalP1Score += calculateP1Score(pavoTokens, eloteTokens);
|
|
||||||
} else if (role === 'P2') {
|
|
||||||
totalP2Score += calculateP2Score(pavoTokens, eloteTokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count players with shame
|
additionalMetrics.value = {
|
||||||
if (shameTokens > 0) {
|
players_seated: totalPlayersWithNames,
|
||||||
playersWithShame++;
|
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 a selected player
|
||||||
|
function computeSelectedPlayerMetrics(uuid: string) {
|
||||||
|
const playerData = allPlayersWithScores.value.find(p => p.uuid === uuid);
|
||||||
|
if (!playerData?.roomScoreHistory) {
|
||||||
|
selectedPlayerMetrics.value = {
|
||||||
|
players_seated: 1,
|
||||||
|
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;
|
||||||
|
|
||||||
|
playerData.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++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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 = {
|
selectedPlayerMetrics.value = {
|
||||||
players_seated: 1, // If player is selected, they're seated
|
players_seated: 1,
|
||||||
score_p1: 0, // Would need current player data to calculate
|
score_p1: p1Count > 0 ? Math.round((totalP1Scores / p1Count) * 10) / 10 : 0,
|
||||||
score_p2: 0, // Would need current player data to calculate
|
score_p2: p2Count > 0 ? Math.round((totalP2Scores / p2Count) * 10) / 10 : 0,
|
||||||
players_with_shame: 0 // Would need current player data to determine
|
players_with_shame: 0, // Would need shame data
|
||||||
|
players_without_shame: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +289,74 @@ const combinedPlayerCounts = computed(() => ({
|
|||||||
...selectedPlayerMetrics.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
|
||||||
|
allPlayersWithScores.value.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;
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalPlayersCount = computed(() => {
|
||||||
|
return additionalMetrics.value.players_seated || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Active filters object
|
||||||
|
const activeFilters = computed(() => ({
|
||||||
|
dataSource: eventFilters.dataSource.value,
|
||||||
|
round: eventFilters.roundFilter.value.toString(),
|
||||||
|
game: eventFilters.gameFilter.value,
|
||||||
|
hasFilters: eventFilters.hasActiveFilters.value || !!selectedRoomId.value,
|
||||||
|
selectedPlayer: selectedUuid.value ? players.value.find(p => p.uuid === selectedUuid.value)?.name || 'Jugador' : undefined,
|
||||||
|
selectedRoom: selectedRoomId.value ? availableRooms.value.find(r => r.roomId === selectedRoomId.value)?.name || 'Sala' : undefined
|
||||||
|
}));
|
||||||
|
|
||||||
// Watch for changes in filters and data source
|
// Watch for changes in filters and data source
|
||||||
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter, eventFilters.roomFilter], () => {
|
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter, eventFilters.roomFilter], () => {
|
||||||
eventFilters.applyFilters(EVENTS);
|
eventFilters.applyFilters(EVENTS);
|
||||||
@@ -386,9 +456,6 @@ function initials(name: string): string {
|
|||||||
return chars || '🙂';
|
return chars || '🙂';
|
||||||
}
|
}
|
||||||
|
|
||||||
function shortRoomId(roomId: string): string {
|
|
||||||
return roomId.slice(0, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectPlayer(uuid: string) {
|
function selectPlayer(uuid: string) {
|
||||||
if (selectedUuid.value === uuid) return;
|
if (selectedUuid.value === uuid) return;
|
||||||
@@ -487,8 +554,8 @@ function setupStreams() {
|
|||||||
|
|
||||||
eventFilters.updateActiveRoomsData(detailedEvents, counts);
|
eventFilters.updateActiveRoomsData(detailedEvents, counts);
|
||||||
|
|
||||||
// Compute additional metrics
|
// Compute additional metrics from active rooms (for current UI compatibility)
|
||||||
computeMetrics(details);
|
// computeMetrics(details); // Removed since we now use score history
|
||||||
|
|
||||||
// Don't extract rooms from active data - we'll get them from aggregated player history
|
// Don't extract rooms from active data - we'll get them from aggregated player history
|
||||||
|
|
||||||
@@ -542,6 +609,9 @@ function setupStreams() {
|
|||||||
const list = Array.isArray(data?.players) ? data.players : [];
|
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) }));
|
allPlayersActions.value = list.map((p: any) => ({ uuid: String(p.uuid||''), name: String(p.name||''), total: Number(p.total||0) }));
|
||||||
|
|
||||||
|
// Store complete player data with room score history
|
||||||
|
allPlayersWithScores.value = list;
|
||||||
|
|
||||||
|
|
||||||
// Collect all detailed events from all players
|
// Collect all detailed events from all players
|
||||||
const allDetailedEvents: Array<{ kind: string; round?: number; gameVariant?: string; roomId?: string }> = [];
|
const allDetailedEvents: Array<{ kind: string; round?: number; gameVariant?: string; roomId?: string }> = [];
|
||||||
@@ -586,6 +656,9 @@ function setupStreams() {
|
|||||||
playerCount: allDetailedEvents.filter(e => e.roomId === roomId).length
|
playerCount: allDetailedEvents.filter(e => e.roomId === roomId).length
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Compute metrics from score history
|
||||||
|
computeMetricsFromScores();
|
||||||
|
|
||||||
// Apply filters and update display if viewing aggregated data
|
// Apply filters and update display if viewing aggregated data
|
||||||
if (eventFilters.dataSource.value === 'aggregated') {
|
if (eventFilters.dataSource.value === 'aggregated') {
|
||||||
eventFilters.applyFilters(EVENTS);
|
eventFilters.applyFilters(EVENTS);
|
||||||
@@ -1062,4 +1135,5 @@ function downloadCSV() {
|
|||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user