small ui fixes

This commit is contained in:
2025-11-23 12:03:07 +01:00
parent 596731a8a7
commit 012b72527b
15 changed files with 712 additions and 578 deletions
+122 -112
View File
@@ -1,151 +1,154 @@
<script setup lang="ts">
import { ref, onMounted, computed, defineProps } from 'vue'
import PuzzleCard from '@/components/PuzzleCard.vue'
import SubmissionForm from '@/components/SubmissionForm.vue'
import AdminPanel from '@/components/AdminPanel.vue'
import { apiService, errorHelpers } from '@/services/apiService'
import { usePuzzlesStore } from '@/stores/puzzles'
import { useSubmissionsStore } from '@/stores/submissions'
import type { SteamCollection, PuzzleResponse, UserInfo } from '@/types'
import { useCountdown } from '@vueuse/core'
import { ref, onMounted, computed, defineProps } from "vue";
import PuzzleCard from "@/components/PuzzleCard.vue";
import SubmissionForm from "@/components/SubmissionForm.vue";
import AdminPanel from "@/components/AdminPanel.vue";
import { apiService, errorHelpers } from "@/services/apiService";
import { usePuzzlesStore } from "@/stores/puzzles";
import { useSubmissionsStore } from "@/stores/submissions";
import type { PuzzleResponse, UserInfo } from "@/types";
import { useCountdown } from "@vueuse/core";
const props = defineProps<{collectionTitle: string, collectionUrl: string, collectionDescription: string}>()
const props = defineProps<{
collectionTitle: string;
collectionUrl: string;
collectionDescription: string;
}>();
// Pinia stores
const puzzlesStore = usePuzzlesStore()
const submissionsStore = useSubmissionsStore()
const puzzlesStore = usePuzzlesStore();
const submissionsStore = useSubmissionsStore();
// Local state
const userInfo = ref<UserInfo | null>(null)
const isLoading = ref(true)
const error = ref<string>('')
const userInfo = ref<UserInfo | null>(null);
const isLoading = ref(true);
const error = ref<string>("");
// Computed properties
const isSuperuser = computed(() => {
return userInfo.value?.is_superuser || false
})
return userInfo.value?.is_superuser || false;
});
// Computed property to get responses grouped by puzzle
const responsesByPuzzle = computed(() => {
const grouped: Record<number, PuzzleResponse[]> = {}
submissionsStore.submissions.forEach(submission => {
submission.responses.forEach(response => {
const grouped: Record<number, PuzzleResponse[]> = {};
submissionsStore.submissions.forEach((submission) => {
submission.responses.forEach((response) => {
// Handle both number and object types for puzzle field
const puzzleId = typeof response.puzzle === 'number' ? response.puzzle : response.puzzle.id
if (!grouped[puzzleId]) {
grouped[puzzleId] = []
if (!grouped[response.puzzle]) {
grouped[response.puzzle] = [];
}
grouped[puzzleId].push(response)
})
})
return grouped
})
grouped[response.puzzle].push(response);
});
});
return grouped;
});
async function initialize() {
try {
isLoading.value = true
error.value = ''
console.log('Starting data load...')
isLoading.value = true;
error.value = "";
console.log("Starting data load...");
// Load user info
console.log('Loading user info...')
const userResponse = await apiService.getUserInfo()
console.log("Loading user info...");
const userResponse = await apiService.getUserInfo();
if (userResponse.data) {
userInfo.value = userResponse.data
console.log('User info loaded:', userResponse.data)
userInfo.value = userResponse.data;
console.log("User info loaded:", userResponse.data);
} else if (userResponse.error) {
console.warn('User info error:', userResponse.error)
console.warn("User info error:", userResponse.error);
}
// Load puzzles from API using store
console.log('Loading puzzles...')
await puzzlesStore.loadPuzzles()
console.log('Puzzles loaded:', puzzlesStore.puzzles.length)
console.log("Loading puzzles...");
await puzzlesStore.loadPuzzles();
console.log("Puzzles loaded:", puzzlesStore.puzzles.length);
// Load existing submissions using store
console.log('Loading submissions...')
await submissionsStore.loadSubmissions()
console.log('Submissions loaded:', submissionsStore.submissions.length)
console.log('Data load complete!')
console.log("Loading submissions...");
await submissionsStore.loadSubmissions();
console.log("Submissions loaded:", submissionsStore.submissions.length);
console.log("Data load complete!");
} catch (err) {
error.value = errorHelpers.getErrorMessage(err)
console.error('Failed to load data:', err)
error.value = errorHelpers.getErrorMessage(err);
console.error("Failed to load data:", err);
} finally {
isLoading.value = false
console.log('Loading state set to false')
isLoading.value = false;
console.log("Loading state set to false");
}
if (userInfo.value.is_superuser) {
start()
if (userInfo.value?.is_superuser) {
start();
}
}
const { remaining, start } = useCountdown(60, {
onComplete() {
initialize()
}
})
initialize();
},
});
onMounted(async () => {
await initialize()
})
await initialize();
});
const handleSubmission = async (submissionData: {
files: any[],
notes?: string,
manualValidationRequested?: boolean
const handleSubmission = async (submissionData: {
files: any[];
notes?: string;
manualValidationRequested?: boolean;
}) => {
try {
isLoading.value = true
error.value = ''
isLoading.value = true;
error.value = "";
// Create submission via store
const submission = await submissionsStore.createSubmission(
submissionData.files,
submissionData.notes,
submissionData.manualValidationRequested
)
submissionData.manualValidationRequested,
);
// Show success message
if (submission) {
const puzzleNames = submission.responses.map(r => r.puzzle_name).join(', ')
alert(`Solutions submitted successfully for puzzles: ${puzzleNames}`)
const puzzleNames = submission.responses
.map((r) => r.puzzle_name)
.join(", ");
alert(`Solutions submitted successfully for puzzles: ${puzzleNames}`);
} else {
alert('Submission created successfully!')
alert("Submission created successfully!");
}
// Close modal
submissionsStore.closeSubmissionModal()
submissionsStore.closeSubmissionModal();
} catch (err) {
const errorMessage = errorHelpers.getErrorMessage(err)
error.value = errorMessage
alert(`Submission failed: ${errorMessage}`)
console.error('Submission error:', err)
const errorMessage = errorHelpers.getErrorMessage(err);
error.value = errorMessage;
alert(`Submission failed: ${errorMessage}`);
console.error("Submission error:", err);
} finally {
isLoading.value = false
isLoading.value = false;
}
}
};
const openSubmissionModal = () => {
submissionsStore.openSubmissionModal()
}
submissionsStore.openSubmissionModal();
};
const closeSubmissionModal = () => {
submissionsStore.closeSubmissionModal()
}
submissionsStore.closeSubmissionModal();
};
// Function to match puzzle name from OCR to actual puzzle
const findPuzzleByName = (ocrPuzzleName: string) => {
return puzzlesStore.findPuzzleByName(ocrPuzzleName)
}
return puzzlesStore.findPuzzleByName(ocrPuzzleName);
};
const reloadPage = () => {
window.location.reload()
}
window.location.reload();
};
</script>
<template>
@@ -157,19 +160,22 @@ const reloadPage = () => {
<h1 class="text-xl font-bold">Opus Magnum Puzzle Submitter</h1>
</div>
<div class="flex items-start justify-between">
<div v-if="userInfo?.is_authenticated" class="flex items-center gap-2">
<div
v-if="userInfo?.is_authenticated"
class="flex items-center gap-2"
>
<div class="text-sm">
<span class="font-medium">{{ userInfo.username }}</span>
<span v-if="userInfo.is_superuser" class="badge badge-warning badge-xs ml-1">Admin</span>
<span
v-if="userInfo.is_superuser"
class="badge badge-warning badge-xs ml-1"
>Admin</span
>
</div>
</div>
<div v-else class="text-sm text-base-content/70">
Not logged in
</div>
<div v-else class="text-sm text-base-content/70">Not logged in</div>
<div class="flex flex-col items-end gap-2">
<a href="/admin" class="btn btn-xs btn-warning">
Admin django
</a>
<a href="/admin" class="btn btn-xs btn-warning"> Admin panel </a>
</div>
</div>
</div>
@@ -187,13 +193,16 @@ const reloadPage = () => {
</div>
</div>
<div v-if="isLoading" class="flex justify-center items-center min-h-[400px]">
<div
v-if="isLoading"
class="flex justify-center items-center min-h-[400px]"
>
<div class="text-center">
<span class="loading loading-spinner loading-lg"></span>
<p class="mt-4 text-base-content/70">Loading puzzles...</p>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="alert alert-error max-w-2xl mx-auto">
<i class="mdi mdi-alert-circle text-xl"></i>
@@ -214,14 +223,13 @@ const reloadPage = () => {
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="card-title text-2xl">{{ props.collectionTitle }}</h2>
<p class="text-base-content/70">{{ props.collectionDescription }}</p>
<p class="text-base-content/70">
{{ props.collectionDescription }}
</p>
<div class="flex flex-wrap gap-4 mt-4">
<button
@click="openSubmissionModal"
class="btn btn-primary"
>
<i class="mdi mdi-plus mr-2"></i>
Submit Solution
<button @click="openSubmissionModal" class="btn btn-primary">
<i class="mdi mdi-plus mr-2"></i>
Submit Solution
</button>
</div>
</div>
@@ -247,25 +255,27 @@ const reloadPage = () => {
<div v-if="puzzlesStore.puzzles.length === 0" class="text-center py-12">
<div class="text-6xl mb-4">🧩</div>
<h3 class="text-xl font-bold mb-2">No Puzzles Available</h3>
<p class="text-base-content/70">Check back later for new puzzle collections!</p>
<p class="text-base-content/70">
Check back later for new puzzle collections!
</p>
</div>
</div>
</div>
<!-- Submission Modal -->
<div v-if="submissionsStore.isSubmissionModalOpen" class="modal modal-open">
<div class="modal-box max-w-4xl">
<div class="modal-box max-w-6xl">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-lg">Submit Solution</h3>
<button
<button
@click="closeSubmissionModal"
class="btn btn-sm btn-circle btn-ghost"
>
<i class="mdi mdi-close"></i>
</button>
</div>
<SubmissionForm
<SubmissionForm
:puzzles="puzzlesStore.puzzles"
:find-puzzle-by-name="findPuzzleByName"
@submit="handleSubmission"