Files
service-finder/frontend_admin/.nuxt/dist/server/_nuxt/competitions-1WDTeHdG.js.map.json
2026-06-30 08:56:55 +00:00

1 line
25 KiB
JSON

{"file":"competitions-1WDTeHdG.js","mappings":"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","names":["_unref","_ssrRenderAttr","_ssrLooseContain","_ssrLooseEqual","_ssrRenderList","_ssrInterpolate","_ssrIncludeBooleanAttr"],"sources":["../../../../pages/gamification/competitions.vue"],"sourcesContent":["<template>\n <div>\n <!-- Page Header -->\n <div class=\"mb-8 flex items-center justify-between\">\n <div>\n <h1 class=\"text-2xl font-bold text-white\">{{ t('gamification.competitions.title') }}</h1>\n <p class=\"text-slate-400 mt-1\">{{ t('gamification.competitions.subtitle') }}</p>\n </div>\n <button\n class=\"flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-lg transition\"\n @click=\"openCreateModal\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n {{ t('gamification.competitions.create') }}\n </button>\n </div>\n\n <!-- Season Filter -->\n <div class=\"mb-6\">\n <div class=\"flex items-center gap-3 flex-wrap\">\n <label class=\"text-sm text-slate-400\">{{ t('gamification.competitions.season_filter') }}</label>\n <select\n v-model=\"selectedSeasonId\"\n class=\"px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n @change=\"fetchCompetitions\"\n >\n <option :value=\"null\">{{ t('gamification.competitions.all_seasons') }}</option>\n <option v-for=\"s in seasons\" :key=\"s.id\" :value=\"s.id\">\n {{ s.name }} {{ s.is_active ? t('gamification.competitions.active_badge') : '' }}\n </option>\n </select>\n </div>\n </div>\n\n <!-- Loading State -->\n <div v-if=\"loading\" class=\"flex items-center justify-center py-20\">\n <div class=\"text-slate-400 flex items-center gap-3\">\n <svg class=\"w-5 h-5 animate-spin\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n <span>{{ t('gamification.competitions.loading') }}</span>\n </div>\n </div>\n\n <!-- Error State -->\n <div v-else-if=\"error\" class=\"bg-rose-500/10 border border-rose-500/30 rounded-xl p-6 mb-8\">\n <p class=\"text-rose-400\">{{ t('gamification.competitions.error') }}</p>\n <button class=\"mt-2 text-sm text-rose-300 hover:text-rose-200 underline\" @click=\"fetchCompetitions\">{{ t('gamification.competitions.retry') }}</button>\n </div>\n\n <!-- Competitions List -->\n <template v-else>\n <!-- Empty State -->\n <div v-if=\"competitions.length === 0\" class=\"bg-slate-800 rounded-xl border border-slate-700 p-12 text-center\">\n <svg class=\"w-12 h-12 mx-auto text-slate-600 mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 100 4 2 2 0 000-4z\" />\n </svg>\n <p class=\"text-slate-400\">{{ t('gamification.competitions.no_items') }}</p>\n <button\n class=\"mt-4 px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-lg transition\"\n @click=\"openCreateModal\"\n >\n {{ t('gamification.competitions.create_first') }}\n </button>\n </div>\n\n <!-- Competition Cards -->\n <div v-else class=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n <div\n v-for=\"comp in competitions\"\n :key=\"comp.id\"\n class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-slate-600 transition\"\n >\n <div class=\"flex items-start justify-between mb-3\">\n <div>\n <h3 class=\"text-lg font-semibold text-white\">{{ comp.name }}</h3>\n <p v-if=\"comp.description\" class=\"text-sm text-slate-400 mt-1 line-clamp-2\">{{ comp.description }}</p>\n </div>\n <span\n class=\"px-2.5 py-0.5 rounded-full text-xs font-medium flex-shrink-0 ml-2\"\n :class=\"getStatusClass(comp.status)\"\n >\n {{ getStatusLabel(comp.status) }}\n </span>\n </div>\n\n <div class=\"space-y-2 text-sm text-slate-400 mb-4\">\n <div class=\"flex items-center gap-2\">\n <svg class=\"w-4 h-4 flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\" />\n </svg>\n <span>{{ formatDate(comp.start_date) }} → {{ formatDate(comp.end_date) }}</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <svg class=\"w-4 h-4 flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z\" />\n </svg>\n <span>{{ t('gamification.competitions.season_id_label') }} {{ comp.season_id }}</span>\n </div>\n </div>\n\n <!-- Rules Preview -->\n <div v-if=\"comp.rules && Object.keys(comp.rules).length > 0\" class=\"mb-4\">\n <details class=\"text-sm\">\n <summary class=\"text-indigo-400 cursor-pointer hover:text-indigo-300\">{{ t('gamification.competitions.view_rules') }}</summary>\n <pre class=\"mt-2 p-3 bg-slate-900 rounded-lg text-xs text-slate-300 overflow-x-auto\">{{ JSON.stringify(comp.rules, null, 2) }}</pre>\n </details>\n </div>\n\n <!-- Actions -->\n <div class=\"flex items-center gap-2 pt-2 border-t border-slate-700/50\">\n <button\n class=\"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm text-indigo-400 hover:bg-indigo-500/10 transition\"\n @click=\"openEditModal(comp)\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n {{ t('gamification.competitions.edit') }}\n </button>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Create/Edit Modal -->\n <div\n v-if=\"showModal\"\n class=\"fixed inset-0 z-50 flex items-center justify-center p-4\"\n >\n <div class=\"fixed inset-0 bg-black/60\" @click=\"closeModal\" />\n <div class=\"relative bg-slate-800 rounded-xl border border-slate-700 w-full max-w-lg p-6 shadow-2xl max-h-[90vh] overflow-y-auto\">\n <h2 class=\"text-lg font-semibold text-white mb-6\">\n {{ editingCompetition ? t('gamification.competitions.edit_title') : t('gamification.competitions.create_title') }}\n </h2>\n\n <form @submit.prevent=\"saveCompetition\" class=\"space-y-4\">\n <!-- Name -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.name') }}</label>\n <input\n v-model=\"form.name\"\n type=\"text\"\n required\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"t('gamification.competitions.name_placeholder')\"\n />\n </div>\n\n <!-- Description -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.description') }}</label>\n <textarea\n v-model=\"form.description\"\n rows=\"3\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"t('gamification.competitions.desc_placeholder')\"\n />\n </div>\n\n <!-- Season -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.season') }}</label>\n <select\n v-model=\"form.season_id\"\n required\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n >\n <option value=\"\" disabled>{{ t('gamification.competitions.select_season') }}</option>\n <option v-for=\"s in seasons\" :key=\"s.id\" :value=\"s.id\">\n {{ s.name }} {{ s.is_active ? t('gamification.competitions.active_badge') : '' }}\n </option>\n </select>\n </div>\n\n <!-- Start Date -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.start_date') }}</label>\n <input\n v-model=\"form.start_date\"\n type=\"date\"\n required\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n\n <!-- End Date -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.end_date') }}</label>\n <input\n v-model=\"form.end_date\"\n type=\"date\"\n required\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n\n <!-- Status -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.status') }}</label>\n <select\n v-model=\"form.status\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n >\n <option value=\"draft\">{{ t('gamification.competitions.status_draft') }}</option>\n <option value=\"active\">{{ t('gamification.competitions.status_active') }}</option>\n <option value=\"completed\">{{ t('gamification.competitions.status_completed') }}</option>\n <option value=\"cancelled\">{{ t('gamification.competitions.status_cancelled') }}</option>\n </select>\n </div>\n\n <!-- Rules (JSON) -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.competitions.rules') }}</label>\n <textarea\n v-model=\"form.rulesJson\"\n rows=\"5\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 font-mono text-xs focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"t('gamification.competitions.rules_placeholder')\"\n />\n <p v-if=\"rulesParseError\" class=\"text-xs text-rose-400 mt-1\">{{ t('gamification.competitions.invalid_json') }}</p>\n </div>\n\n <!-- Error -->\n <div v-if=\"formError\" class=\"bg-rose-500/10 border border-rose-500/30 rounded-lg p-3\">\n <p class=\"text-sm text-rose-400\">{{ formError }}</p>\n </div>\n\n <!-- Buttons -->\n <div class=\"flex items-center justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n class=\"px-4 py-2 text-sm text-slate-400 hover:text-white transition\"\n @click=\"closeModal\"\n >\n {{ t('gamification.competitions.cancel') }}\n </button>\n <button\n type=\"submit\"\n :disabled=\"saving || rulesParseError\"\n class=\"px-4 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-600/50 text-white text-sm font-medium rounded-lg transition flex items-center gap-2\"\n >\n <svg v-if=\"saving\" class=\"w-4 h-4 animate-spin\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n {{ editingCompetition ? t('gamification.competitions.save') : t('gamification.competitions.create_btn') }}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\ndefinePageMeta({\n middleware: 'auth',\n})\n\nconst { t } = useI18n()\n\ninterface Season {\n id: number\n name: string\n start_date: string\n end_date: string\n is_active: boolean\n created_at: string | null\n}\n\ninterface Competition {\n id: number\n name: string\n description: string | null\n season_id: number\n start_date: string\n end_date: string\n rules: Record<string, any> | null\n status: string\n created_at: string | null\n updated_at: string | null\n}\n\ninterface CompetitionForm {\n name: string\n description: string\n season_id: number | ''\n start_date: string\n end_date: string\n status: string\n rulesJson: string\n}\n\nconst loading = ref(true)\nconst error = ref(false)\nconst saving = ref(false)\nconst showModal = ref(false)\nconst seasons = ref<Season[]>([])\nconst competitions = ref<Competition[]>([])\nconst editingCompetition = ref<Competition | null>(null)\nconst selectedSeasonId = ref<number | null>(null)\nconst formError = ref('')\nconst rulesParseError = ref(false)\n\nconst form = reactive<CompetitionForm>({\n name: '',\n description: '',\n season_id: '',\n start_date: '',\n end_date: '',\n status: 'draft',\n rulesJson: '',\n})\n\nfunction getAuthHeaders() {\n const tokenCookie = useCookie('access_token')\n const token = tokenCookie.value\n return token ? { Authorization: `Bearer ${token}` } : {}\n}\n\nfunction formatDate(dateStr: string): string {\n if (!dateStr) return ''\n const d = new Date(dateStr)\n return d.toLocaleDateString('hu-HU', { year: 'numeric', month: 'long', day: 'numeric' })\n}\n\nfunction getStatusClass(status: string): string {\n switch (status) {\n case 'active': return 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/30'\n case 'draft': return 'bg-slate-700 text-slate-400 border border-slate-600'\n case 'completed': return 'bg-blue-500/10 text-blue-400 border border-blue-500/30'\n case 'cancelled': return 'bg-rose-500/10 text-rose-400 border border-rose-500/30'\n default: return 'bg-slate-700 text-slate-400 border border-slate-600'\n }\n}\n\nfunction getStatusLabel(status: string): string {\n switch (status) {\n case 'active': return t('gamification.competitions.status_active')\n case 'draft': return t('gamification.competitions.status_draft')\n case 'completed': return t('gamification.competitions.status_completed')\n case 'cancelled': return t('gamification.competitions.status_cancelled')\n default: return status\n }\n}\n\nfunction resetForm() {\n form.name = ''\n form.description = ''\n form.season_id = ''\n form.start_date = ''\n form.end_date = ''\n form.status = 'draft'\n form.rulesJson = ''\n formError.value = ''\n rulesParseError.value = false\n}\n\nfunction openCreateModal() {\n editingCompetition.value = null\n resetForm()\n showModal.value = true\n}\n\nfunction openEditModal(comp: Competition) {\n editingCompetition.value = comp\n form.name = comp.name\n form.description = comp.description || ''\n form.season_id = comp.season_id\n form.start_date = comp.start_date\n form.end_date = comp.end_date\n form.status = comp.status\n form.rulesJson = comp.rules ? JSON.stringify(comp.rules, null, 2) : ''\n formError.value = ''\n rulesParseError.value = false\n showModal.value = true\n}\n\nfunction closeModal() {\n showModal.value = false\n editingCompetition.value = null\n resetForm()\n}\n\nfunction parseRules(): Record<string, any> | null {\n if (!form.rulesJson.trim()) return null\n try {\n const parsed = JSON.parse(form.rulesJson)\n rulesParseError.value = false\n return parsed\n } catch {\n rulesParseError.value = true\n return null\n }\n}\n\nasync function fetchSeasons() {\n try {\n const data = await $fetch('/api/v1/admin/gamification/seasons', {\n headers: getAuthHeaders(),\n })\n seasons.value = data as Season[]\n } catch (e) {\n console.error('Failed to fetch seasons:', e)\n }\n}\n\nasync function fetchCompetitions() {\n loading.value = true\n error.value = false\n try {\n const params: Record<string, any> = {}\n if (selectedSeasonId.value) {\n params.season_id = selectedSeasonId.value\n }\n const query = new URLSearchParams(params).toString()\n const url = `/api/v1/admin/gamification/competitions${query ? `?${query}` : ''}`\n const data = await $fetch(url, {\n headers: getAuthHeaders(),\n })\n competitions.value = data as Competition[]\n } catch (e) {\n console.error('Failed to fetch competitions:', e)\n error.value = true\n } finally {\n loading.value = false\n }\n}\n\nasync function saveCompetition() {\n saving.value = true\n formError.value = ''\n\n // Validate rules JSON if provided\n let rules = null\n if (form.rulesJson.trim()) {\n rules = parseRules()\n if (rulesParseError.value) {\n saving.value = false\n return\n }\n }\n\n try {\n if (editingCompetition.value) {\n // Update existing competition\n const payload: Record<string, any> = {}\n if (form.name !== editingCompetition.value.name) payload.name = form.name\n if (form.description !== (editingCompetition.value.description || '')) payload.description = form.description || null\n if (form.season_id !== editingCompetition.value.season_id) payload.season_id = form.season_id\n if (form.start_date !== editingCompetition.value.start_date) payload.start_date = form.start_date\n if (form.end_date !== editingCompetition.value.end_date) payload.end_date = form.end_date\n if (form.status !== editingCompetition.value.status) payload.status = form.status\n if (rules !== null) payload.rules = rules\n\n if (Object.keys(payload).length > 0) {\n await $fetch(`/api/v1/admin/gamification/competitions/${editingCompetition.value.id}`, {\n method: 'PUT',\n headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },\n body: payload,\n })\n }\n } else {\n // Create new competition\n await $fetch('/api/v1/admin/gamification/competitions', {\n method: 'POST',\n headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },\n body: {\n name: form.name,\n description: form.description || null,\n season_id: form.season_id,\n start_date: form.start_date,\n end_date: form.end_date,\n status: form.status,\n rules: rules,\n },\n })\n }\n\n closeModal()\n await fetchCompetitions()\n } catch (e: any) {\n console.error('Failed to save competition:', e)\n formError.value = e?.data?.detail || e?.message || t('gamification.competitions.save_error')\n } finally {\n saving.value = false\n }\n}\n\nonMounted(async () => {\n await fetchSeasons()\n await fetchCompetitions()\n})\n</script>\n"],"version":3}