mejoras de codigo 2
This commit is contained in:
@@ -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`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user