feat(submissions): scoped submissions per user/matchs, sync from polylan

This commit is contained in:
2026-09-16 13:12:22 +02:00
parent 85e67599af
commit fabf06e6d4
35 changed files with 1008 additions and 92 deletions
+23 -29
View File
@@ -8,15 +8,18 @@ import { provideEventPath } from "@/composables/useEventPath";
interface Props {
eventPath: string;
gamePath?: string;
scopeId: string;
scopeName?: string;
gameName?: string;
gamePath?: string;
gameImage?: string;
gameSteamId?: string;
}
const props = withDefaults(defineProps<Props>(), {
gamePath: "",
scopeName: "Match",
gameName: "Game",
gamePath: "",
gameImage: "",
gameSteamId: "",
});
@@ -403,7 +406,7 @@ const submitSubmission = async () => {
try {
await submissionsApiCreateSubmission({
path: { event_path: props.eventPath },
query: { game_path: props.gamePath },
query: { scope_id: props.scopeId },
body: {
files: uploadedFiles.value,
notes: notes.value || null,
@@ -423,7 +426,7 @@ const submitSubmission = async () => {
};
const goHome = () => {
window.location.href = `/${props.eventPath}/`;
window.location.href = `/${props.eventPath}/submissions/${props.gamePath}`;
};
const formatFileSize = (bytes: number) => {
@@ -454,13 +457,11 @@ const getOCRStatusColor = (status: string) => {
};
const loadUserSubmissions = async () => {
if (!props.gamePath) return;
isLoadingSubmissions.value = true;
try {
const { data } = await submissionsApiListUserSubmissions({
path: { event_path: props.eventPath },
query: { game_path: props.gamePath },
query: { scope_id: props.scopeId },
});
if (data && Array.isArray(data)) {
userSubmissions.value = data;
@@ -522,7 +523,7 @@ onUnmounted(() => {
<i class="mdi mdi-arrow-left"></i>
Back
</button>
<h1 class="text-xl font-bold">{{ gameName }} Submissions</h1>
<h1 class="text-xl font-bold">{{ gameName }} Submissions &mdash; {{ scopeName }}</h1>
<div class="flex-1"></div>
<!-- WebSocket Status Indicator -->
@@ -555,7 +556,8 @@ onUnmounted(() => {
</figure>
<div class="card-body">
<h2 class="card-title text-2xl">{{ gameName }}</h2>
<p class="text-base-content/70">Submit your best results for this game</p>
<p class="text-base-content/70">Submitting for match: <span class="font-semibold">{{ scopeName }}</span>
</p>
</div>
</div>
@@ -568,10 +570,11 @@ onUnmounted(() => {
</h2>
<!-- Upload Area -->
<div @dragover="handleDragover" @dragleave="handleDragleave" @drop="handleDrop" @mouseenter="isUploadAreaHovered = true" @mouseleave="isUploadAreaHovered = false" :class="[
'border-2 border-dashed rounded-lg p-8 text-center transition-colors cursor-pointer bg-base-200/50 mb-6',
isDragover ? 'border-primary bg-primary/10' : 'border-base-300 hover:border-primary'
]">
<div @dragover="handleDragover" @dragleave="handleDragleave" @drop="handleDrop"
@mouseenter="isUploadAreaHovered = true" @mouseleave="isUploadAreaHovered = false" :class="[
'border-2 border-dashed rounded-lg p-8 text-center transition-colors cursor-pointer bg-base-200/50 mb-6',
isDragover ? 'border-primary bg-primary/10' : 'border-base-300 hover:border-primary'
]">
<input type="file" multiple @change="handleFileUpload" class="hidden" id="file-upload" />
<label for="file-upload" class="cursor-pointer flex flex-col items-center gap-3">
<i :class="[
@@ -625,7 +628,7 @@ onUnmounted(() => {
<span class="label-text-alt">0/500</span>
</label>
<textarea class="flex textarea textarea-bordered h-24 w-full resize-none"
<textarea v-model="notes" class="flex textarea textarea-bordered h-24 w-full resize-none"
placeholder="Add any notes special for this upload" maxlength="500"></textarea>
</div>
</div>
@@ -699,15 +702,6 @@ onUnmounted(() => {
processed.
</p>
</div>
<div class="divider"></div>
<div class="bg-base-200 p-4 rounded-lg">
<p class="text-base-content/70 text-sm">
<i class="mdi mdi-lightbulb-on mr-2 text-warning"></i>
Pro tip: Include clear screenshots or documentation with your submission for faster verification.
</p>
</div>
</div>
</div>
</div>
@@ -925,8 +919,7 @@ onUnmounted(() => {
<p class="text-base-content/70">OCR processing failed</p>
</div>
</div>
<div v-else-if="!selectedSubmissionFile?.ocr_raw_data"
class="flex items-center justify-center py-8">
<div v-else-if="!selectedSubmissionFile?.ocr_raw_data" class="flex items-center justify-center py-8">
<div class="text-center">
<i class="mdi mdi-clock-outline text-4xl text-base-content/30 mb-2"></i>
<p class="text-base-content/70">Waiting for OCR processing...</p>
@@ -934,9 +927,9 @@ onUnmounted(() => {
</div>
<!-- Crops OCR Data -->
<div v-if="selectedSubmissionFile?.crops?.length"
class="space-y-3 pt-4 border-t border-base-300">
<p class="text-xs font-semibold text-base-content/70">Crop OCR Results ({{ selectedSubmissionFile.crops.length }}):</p>
<div v-if="selectedSubmissionFile?.crops?.length" class="space-y-3 pt-4 border-t border-base-300">
<p class="text-xs font-semibold text-base-content/70">Crop OCR Results ({{
selectedSubmissionFile.crops.length }}):</p>
<div v-for="crop in selectedSubmissionFile.crops" :key="crop.id" class="space-y-2">
<div class="flex items-center justify-between">
<p class="text-xs font-medium">{{ crop.label || `Crop (${crop.x1}, ${crop.y1})` }}</p>
@@ -946,7 +939,8 @@ onUnmounted(() => {
class="bg-base-100 p-2 rounded text-xs whitespace-pre-wrap break-words font-mono max-h-32 overflow-y-auto">
{{ crop.ocr_raw_data }}
</div>
<div v-else-if="crop.ocr_status === 'processing'" class="text-xs text-base-content/70 text-center py-2">
<div v-else-if="crop.ocr_status === 'processing'"
class="text-xs text-base-content/70 text-center py-2">
<span class="loading loading-spinner loading-sm inline-block"></span>
Processing...
</div>