top winners + by puzzle
This commit is contained in:
parent
4ba6a48246
commit
9e5ab8539a
@ -6,6 +6,7 @@ import AdminPanel from "@/components/AdminPanel.vue";
|
|||||||
import Results from "@/components/Results.vue";
|
import Results from "@/components/Results.vue";
|
||||||
import Winners from "@/components/Winners.vue";
|
import Winners from "@/components/Winners.vue";
|
||||||
import PuzzleResults from "@/components/PuzzleResults.vue";
|
import PuzzleResults from "@/components/PuzzleResults.vue";
|
||||||
|
import TopUsersLeaderboard from "@/components/TopUsersLeaderboard.vue";
|
||||||
import { apiService, errorHelpers } from "@/services/apiService";
|
import { apiService, errorHelpers } from "@/services/apiService";
|
||||||
import { usePuzzlesStore } from "@/stores/puzzles";
|
import { usePuzzlesStore } from "@/stores/puzzles";
|
||||||
import { useSubmissionsStore } from "@/stores/submissions";
|
import { useSubmissionsStore } from "@/stores/submissions";
|
||||||
@ -191,10 +192,11 @@ const goHome = () => {
|
|||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div v-else class="space-y-8">
|
<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">
|
<div v-if="isTournamentClosed" class="space-y-8">
|
||||||
<Winners />
|
<TopUsersLeaderboard />
|
||||||
<PuzzleResults />
|
<PuzzleResults />
|
||||||
|
<Winners />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
96
polylan_submitter/src/components/TopUsersLeaderboard.vue
Normal file
96
polylan_submitter/src/components/TopUsersLeaderboard.vue
Normal file
@ -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>
|
||||||
@ -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) => {
|
const openImageModal = (fileUrl: string, fileName: string) => {
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
import{k as t,l as a,p as n,v as s}from"./style-BKSucaDP.js";const c={key:0,class:"flex justify-center"},k={key:0,class:"badge badge-warning badge-lg"},d={key:1,class:"badge badge-lg"},l={key:2,class:"badge badge-lg"},o={key:3,class:"badge badge-lg"},g={key:1,class:"text-2xl text-base-content/50"},y=t({__name:"RankBadge",props:{rank:{}},setup(e){return(i,r)=>e.rank!==null?(n(),a("div",c,[e.rank===1?(n(),a("span",k," 🏆 #"+s(e.rank),1)):e.rank===2?(n(),a("span",d," 🥈 #"+s(e.rank),1)):e.rank===3?(n(),a("span",l," 🥉 #"+s(e.rank),1)):(n(),a("span",o," #"+s(e.rank),1))])):(n(),a("div",g," No rank yet "))}});export{y as _};
|
import{k as t,l as a,p as n,v as s}from"./style-C9QoPxDN.js";const c={key:0,class:"flex justify-center"},k={key:0,class:"badge badge-warning badge-lg"},d={key:1,class:"badge badge-lg"},l={key:2,class:"badge badge-lg"},o={key:3,class:"badge badge-lg"},g={key:1,class:"text-2xl text-base-content/50"},y=t({__name:"RankBadge",props:{rank:{}},setup(e){return(i,r)=>e.rank!==null?(n(),a("div",c,[e.rank===1?(n(),a("span",k," 🏆 #"+s(e.rank),1)):e.rank===2?(n(),a("span",d," 🥈 #"+s(e.rank),1)):e.rank===3?(n(),a("span",l," 🥉 #"+s(e.rank),1)):(n(),a("span",o," #"+s(e.rank),1))])):(n(),a("div",g," No rank yet "))}});export{y as _};
|
||||||
@ -1 +1 @@
|
|||||||
import{k as b,c as v,r as g,l as a,p as n,s as t,F as h,y as x,v as i,x as f,O as _}from"./style-BKSucaDP.js";const y={class:"min-h-screen bg-base-300 flex items-center justify-center px-4"},w={class:"w-full max-w-6xl"},k={class:"grid grid-cols-1 md:grid-cols-2 gap-8"},S=["onClick"],I={class:"relative h-60 bg-base-300 overflow-hidden"},j=["src","alt","onError"],E={key:1,class:"w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"},N={class:"card-body"},C={class:"card-title text-2xl"},B={class:"text-base-content/70"},O=b({__name:"Home",setup(A){const c=v(()=>[{id:"opus-magnum",title:"Opus Magnum",description:"Submit your best Opus Magnum puzzle solutions",appId:558990,path:"/opus-magnum"},{id:"noita",title:"Noita",description:"Submit your greatest Noita achievements",appId:881100,path:"/noita"}]),r=g(new Set),d=o=>`https://cdn.akamai.steamstatic.com/steam/apps/${o}/header.jpg`,u=o=>{r.value.add(o)},p=o=>{window.location.href=o};return(o,e)=>(n(),a("div",y,[t("div",w,[e[3]||(e[3]=t("div",{class:"text-center mb-12"},[t("h1",{class:"text-5xl font-bold mb-4"},"PolyLAN Submitter"),t("p",{class:"text-xl text-base-content/70"}," Choose a game and submit your best solutions ")],-1)),t("div",k,[(n(!0),a(h,null,x(c.value,s=>(n(),a("div",{key:s.id,onClick:m=>p(s.path),class:"card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden"},[t("figure",I,[r.value.has(s.appId)?(n(),a("div",E,[...e[0]||(e[0]=[t("i",{class:"mdi mdi-gamepad-variant text-5xl"},null,-1)])])):(n(),a("img",{key:0,src:d(s.appId),alt:s.title,onError:m=>u(s.appId),class:"w-full h-full object-cover"},null,40,j)),e[1]||(e[1]=t("div",{class:"absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"},null,-1))]),t("div",N,[t("h2",C,i(s.title),1),t("p",B,i(s.description),1),e[2]||(e[2]=t("div",{class:"card-actions justify-end mt-4"},[t("button",{class:"btn btn-primary"},[t("i",{class:"mdi mdi-arrow-right mr-2"}),f(" Submit results ")])],-1))])],8,S))),128))]),e[4]||(e[4]=t("div",{class:"text-center mt-12 text-base-content/50"},[t("p",null,"Select a game above to begin submitting")],-1))])]))}}),l="#app",$=document.querySelector(l),z=_(O,{...$?.dataset});z.mount(l);
|
import{k as b,c as v,r as g,l as a,p as n,s as t,F as h,y as x,v as i,x as f,O as _}from"./style-C9QoPxDN.js";const y={class:"min-h-screen bg-base-300 flex items-center justify-center px-4"},w={class:"w-full max-w-6xl"},k={class:"grid grid-cols-1 md:grid-cols-2 gap-8"},S=["onClick"],I={class:"relative h-60 bg-base-300 overflow-hidden"},j=["src","alt","onError"],E={key:1,class:"w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"},N={class:"card-body"},C={class:"card-title text-2xl"},B={class:"text-base-content/70"},O=b({__name:"Home",setup(A){const c=v(()=>[{id:"opus-magnum",title:"Opus Magnum",description:"Submit your best Opus Magnum puzzle solutions",appId:558990,path:"/opus-magnum"},{id:"noita",title:"Noita",description:"Submit your greatest Noita achievements",appId:881100,path:"/noita"}]),r=g(new Set),d=o=>`https://cdn.akamai.steamstatic.com/steam/apps/${o}/header.jpg`,u=o=>{r.value.add(o)},p=o=>{window.location.href=o};return(o,e)=>(n(),a("div",y,[t("div",w,[e[3]||(e[3]=t("div",{class:"text-center mb-12"},[t("h1",{class:"text-5xl font-bold mb-4"},"PolyLAN Submitter"),t("p",{class:"text-xl text-base-content/70"}," Choose a game and submit your best solutions ")],-1)),t("div",k,[(n(!0),a(h,null,x(c.value,s=>(n(),a("div",{key:s.id,onClick:m=>p(s.path),class:"card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden"},[t("figure",I,[r.value.has(s.appId)?(n(),a("div",E,[...e[0]||(e[0]=[t("i",{class:"mdi mdi-gamepad-variant text-5xl"},null,-1)])])):(n(),a("img",{key:0,src:d(s.appId),alt:s.title,onError:m=>u(s.appId),class:"w-full h-full object-cover"},null,40,j)),e[1]||(e[1]=t("div",{class:"absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"},null,-1))]),t("div",N,[t("h2",C,i(s.title),1),t("p",B,i(s.description),1),e[2]||(e[2]=t("div",{class:"card-actions justify-end mt-4"},[t("button",{class:"btn btn-primary"},[t("i",{class:"mdi mdi-arrow-right mr-2"}),f(" Submit results ")])],-1))])],8,S))),128))]),e[4]||(e[4]=t("div",{class:"text-center mt-12 text-base-content/50"},[t("p",null,"Select a game above to begin submitting")],-1))])]))}}),l="#app",$=document.querySelector(l),z=_(O,{...$?.dataset});z.mount(l);
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,20 +1,16 @@
|
|||||||
{
|
{
|
||||||
"_RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js": {
|
"_RankBadge.vue_vue_type_script_setup_true_lang-CiUtgtLU.js": {
|
||||||
"file": "assets/RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js",
|
"file": "assets/RankBadge.vue_vue_type_script_setup_true_lang-CiUtgtLU.js",
|
||||||
"name": "RankBadge.vue_vue_type_script_setup_true_lang",
|
"name": "RankBadge.vue_vue_type_script_setup_true_lang",
|
||||||
"imports": [
|
"imports": [
|
||||||
"_style-BKSucaDP.js"
|
"_style-C9QoPxDN.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_style-8zNjBvNQ.css": {
|
"_style-C9QoPxDN.js": {
|
||||||
"file": "assets/style-8zNjBvNQ.css",
|
"file": "assets/style-C9QoPxDN.js",
|
||||||
"src": "_style-8zNjBvNQ.css"
|
|
||||||
},
|
|
||||||
"_style-BKSucaDP.js": {
|
|
||||||
"file": "assets/style-BKSucaDP.js",
|
|
||||||
"name": "style",
|
"name": "style",
|
||||||
"css": [
|
"css": [
|
||||||
"assets/style-8zNjBvNQ.css"
|
"assets/style-Cs9btLod.css"
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
"assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
"assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
||||||
@ -23,6 +19,10 @@
|
|||||||
"assets/materialdesignicons-webfont-B7mPwVP_.ttf"
|
"assets/materialdesignicons-webfont-B7mPwVP_.ttf"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"_style-Cs9btLod.css": {
|
||||||
|
"file": "assets/style-Cs9btLod.css",
|
||||||
|
"src": "_style-Cs9btLod.css"
|
||||||
|
},
|
||||||
"node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot": {
|
"node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot": {
|
||||||
"file": "assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
"file": "assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
||||||
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot"
|
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot"
|
||||||
@ -40,32 +40,32 @@
|
|||||||
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff2"
|
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff2"
|
||||||
},
|
},
|
||||||
"src/home.ts": {
|
"src/home.ts": {
|
||||||
"file": "assets/home-YbwRQP1Y.js",
|
"file": "assets/home-Cpe9mjX7.js",
|
||||||
"name": "home",
|
"name": "home",
|
||||||
"src": "src/home.ts",
|
"src": "src/home.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_style-BKSucaDP.js"
|
"_style-C9QoPxDN.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/noita.ts": {
|
"src/noita.ts": {
|
||||||
"file": "assets/noita-Su4dRkwf.js",
|
"file": "assets/noita-C5wjrj1v.js",
|
||||||
"name": "noita",
|
"name": "noita",
|
||||||
"src": "src/noita.ts",
|
"src": "src/noita.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_style-BKSucaDP.js",
|
"_style-C9QoPxDN.js",
|
||||||
"_RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js"
|
"_RankBadge.vue_vue_type_script_setup_true_lang-CiUtgtLU.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/opus-magnum.ts": {
|
"src/opus-magnum.ts": {
|
||||||
"file": "assets/opus_magnum-BftkLCBu.js",
|
"file": "assets/opus_magnum-BwlGd4nK.js",
|
||||||
"name": "opus_magnum",
|
"name": "opus_magnum",
|
||||||
"src": "src/opus-magnum.ts",
|
"src": "src/opus-magnum.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_style-BKSucaDP.js",
|
"_style-C9QoPxDN.js",
|
||||||
"_RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js"
|
"_RankBadge.vue_vue_type_script_setup_true_lang-CiUtgtLU.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1 +1 @@
|
|||||||
{"root":["./src/home.ts","./src/noita.ts","./src/opus-magnum.ts","./src/services/apiService.ts","./src/services/ocrService.ts","./src/stores/index.ts","./src/stores/puzzles.ts","./src/stores/submissions.ts","./src/stores/uploads.ts","./src/types/index.ts","./src/Home.vue","./src/Noita.vue","./src/OpusMagnum.vue","./src/components/AdminPanel.vue","./src/components/FileUpload.vue","./src/components/PuzzleCard.vue","./src/components/PuzzleResults.vue","./src/components/RankBadge.vue","./src/components/Results.vue","./src/components/SubmissionForm.vue","./src/components/Winners.vue"],"version":"5.9.3"}
|
{"root":["./src/home.ts","./src/noita.ts","./src/opus-magnum.ts","./src/services/apiService.ts","./src/services/ocrService.ts","./src/stores/index.ts","./src/stores/puzzles.ts","./src/stores/submissions.ts","./src/stores/uploads.ts","./src/types/index.ts","./src/Home.vue","./src/Noita.vue","./src/OpusMagnum.vue","./src/components/AdminPanel.vue","./src/components/FileUpload.vue","./src/components/PuzzleCard.vue","./src/components/PuzzleResults.vue","./src/components/RankBadge.vue","./src/components/Results.vue","./src/components/SubmissionForm.vue","./src/components/TopUsersLeaderboard.vue","./src/components/Winners.vue"],"version":"5.9.3"}
|
||||||
Loading…
Reference in New Issue
Block a user