top winners + by puzzle

This commit is contained in:
2026-05-22 09:32:13 +02:00
parent 4ba6a48246
commit 9e5ab8539a
12 changed files with 129 additions and 31 deletions
+4 -2
View File
@@ -6,6 +6,7 @@ import AdminPanel from "@/components/AdminPanel.vue";
import Results from "@/components/Results.vue";
import Winners from "@/components/Winners.vue";
import PuzzleResults from "@/components/PuzzleResults.vue";
import TopUsersLeaderboard from "@/components/TopUsersLeaderboard.vue";
import { apiService, errorHelpers } from "@/services/apiService";
import { usePuzzlesStore } from "@/stores/puzzles";
import { useSubmissionsStore } from "@/stores/submissions";
@@ -191,10 +192,11 @@ const goHome = () => {
<!-- Main Content -->
<div v-else class="space-y-8">
<!-- Winners Section (only when tournament is closed) -->
<!-- Results Section (only when tournament is closed) -->
<div v-if="isTournamentClosed" class="space-y-8">
<Winners />
<TopUsersLeaderboard />
<PuzzleResults />
<Winners />
</div>
<template v-else>
@@ -0,0 +1,96 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { apiService } from "@/services/apiService";
import type { TournamentPuzzleResults } from "@/types";
const isLoading = ref(true);
const resultsData = ref<TournamentPuzzleResults | null>(null);
const error = ref<string>("");
const fetchResults = async () => {
isLoading.value = true;
error.value = "";
try {
const response = await apiService.getPuzzleResults(100);
if (response.data) {
resultsData.value = response.data;
} else if (response.error) {
error.value = response.error;
console.error("Error fetching results:", response.error);
}
} catch (err) {
error.value = err instanceof Error ? err.message : "Failed to fetch results";
console.error("Error fetching results:", err);
} finally {
isLoading.value = false;
}
};
const getTop10Users = () => {
if (!resultsData.value) return [];
const userScores: Record<string, { username: string; user_id: number; total_points: number }> = {};
resultsData.value.results.forEach((puzzle) => {
puzzle.submissions.forEach((submission) => {
const key = submission.user_id;
if (!userScores[key]) {
userScores[key] = {
username: submission.username,
user_id: submission.user_id,
total_points: 0
};
}
userScores[key].total_points += submission.rank_points || 0;
});
});
return Object.values(userScores)
.sort((a, b) => b.total_points - a.total_points)
.slice(0, 10);
};
onMounted(() => {
fetchResults();
});
</script>
<template>
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="card-title text-2xl flex items-center gap-2">
<i class="mdi mdi-podium text-yellow-500 text-3xl"></i>
Top 10 Users
</h2>
<div v-if="isLoading" class="flex justify-center py-12">
<span class="loading loading-spinner loading-lg"></span>
</div>
<div v-else-if="error" class="alert alert-error">
<i class="mdi mdi-alert-circle text-xl"></i>
<div>{{ error }}</div>
</div>
<div v-else-if="getTop10Users().length === 0" class="text-center py-8">
<p class="text-base-content/70">No results available yet.</p>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-3">
<template v-for="(user, index) in getTop10Users()" :key="user.user_id">
<div class="bg-base-200 p-4 rounded-lg text-center" :class="{
'ring-2 ring-yellow-500': index === 0,
'ring-2 ring-gray-400': index === 1,
'ring-2 ring-orange-400': index === 2,
}">
<div class="text-3xl font-bold mb-2">
{{ index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `#${index + 1}` }}
</div>
<div class="font-semibold text-sm mb-2 truncate">{{ user.username }}</div>
<div class="text-2xl font-bold text-primary">{{ user.total_points }} pts</div>
</div>
</template>
</div>
</div>
</div>
</template>
+1 -1
View File
@@ -65,7 +65,7 @@ const flattenedRows = computed(() => {
});
});
return rows;
return rows.sort((a, b) => (b.total || 0) - (a.total || 0));
});
const openImageModal = (fileUrl: string, fileName: string) => {