mejoras de UI

This commit is contained in:
2025-08-28 01:25:22 -06:00
parent acb3edde3b
commit 91f0e93a28
3 changed files with 254 additions and 123 deletions

View File

@@ -11,9 +11,6 @@
<button class="btn" @click="downloadCSV" :disabled="loading" title="Descargar datos actuales como CSV">
📊 CSV
</button>
<button class="btn toggle" @click="cycleViewMode" :disabled="loading">
{{ viewModeLabel }}
</button>
</div>
</div>
@@ -107,16 +104,18 @@
:player-event-counts="combinedPlayerCounts"
:selected-player-uuid="selectedUuid"
:player-bar-gradient="playerBarGradient"
:view-mode="viewMode"
view-mode="ratio"
:loading="loading"
:filters-collapsed="filtersCollapsed"
:active-filters="{
dataSource: eventFilters.dataSource.value,
round: eventFilters.roundFilter.value.toString(),
game: eventFilters.gameFilter.value,
hasFilters: eventFilters.hasActiveFilters.value || !!selectedRoomId,
selectedPlayer: selectedUuid ? players.find(p => p.uuid === selectedUuid)?.name || 'Jugador' : undefined,
selectedRoom: selectedRoomId ? availableRooms.find(r => r.roomId === selectedRoomId)?.name || 'Sala' : undefined
:active-filters="activeFilters"
:group-totals="{
offers: offersTotal,
responses: responsesTotal,
force: forceTotal,
shame: shameTotal,
report: reportTotal,
averageScore: averageScoreTotal,
totalPlayers: totalPlayersCount
}"
/>
</div>
@@ -136,25 +135,6 @@ const loading = ref(false);
const eventFilters = useEventFilters();
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 = [
'p1_propose', 'p1_no_offer',
@@ -164,7 +144,7 @@ const EVENTS = [
// New metric types for additional charts
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];
@@ -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%)' },
'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_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
const additionalMetrics = ref<Record<string, number>>({
players_seated: 0,
score_p1: 0,
score_p2: 0,
players_with_shame: 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_with_shame: 0,
players_without_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;
// Store room score history from players
const allPlayersWithScores = ref<any[]>([]);
// Function to compute additional metrics from players' score history
function computeMetricsFromScores() {
if (!allPlayersWithScores.value.length) return;
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++;
}
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++;
// Extract scores from roomScoreHistory if available
if (player.roomScoreHistory) {
player.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++;
}
});
});
}
});
}
});
const avgP1Score = p1Count > 0 ? totalP1Scores / p1Count : 0;
const avgP2Score = p2Count > 0 ? totalP2Scores / p2Count : 0;
additionalMetrics.value = {
players_seated: playersSeated,
score_p1: totalP1Score,
score_p2: totalP2Score,
players_with_shame: playersWithShame
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
};
// 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
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++;
}
});
});
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
players_seated: 1,
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: 0, // Would need shame data
players_without_shame: 0
};
}
@@ -287,6 +289,74 @@ const combinedPlayerCounts = computed(() => ({
...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([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter, eventFilters.roomFilter], () => {
eventFilters.applyFilters(EVENTS);
@@ -386,9 +456,6 @@ function initials(name: string): string {
return chars || '🙂';
}
function shortRoomId(roomId: string): string {
return roomId.slice(0, 8);
}
function selectPlayer(uuid: string) {
if (selectedUuid.value === uuid) return;
@@ -487,8 +554,8 @@ function setupStreams() {
eventFilters.updateActiveRoomsData(detailedEvents, counts);
// Compute additional metrics
computeMetrics(details);
// Compute additional metrics from active rooms (for current UI compatibility)
// computeMetrics(details); // Removed since we now use score 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 : [];
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
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
}));
// Compute metrics from score history
computeMetricsFromScores();
// Apply filters and update display if viewing aggregated data
if (eventFilters.dataSource.value === 'aggregated') {
eventFilters.applyFilters(EVENTS);
@@ -1062,4 +1135,5 @@ function downloadCSV() {
min-width: 30px;
}
}
</style>