diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 98aa07c..aad1e24 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/app/pages/admin/index.vue b/app/pages/admin/index.vue index 19cb824..269de32 100644 --- a/app/pages/admin/index.vue +++ b/app/pages/admin/index.vue @@ -43,6 +43,10 @@ Invitations Waitlist Transmissions + + Bug Reports + + @@ -932,6 +936,145 @@ + + + + + + Bug Reports + Fehlermeldungen von Testern aus der /pm-Seite. + + + + + Laden + + + + + + {{ bugReportError }} + + + + + Bug Reports laden… + + + + + + + + + + + {{ report.status === 'open' ? 'Offen' : 'Erledigt' }} + + {{ formatRelative(report.createdAt) }} + + mdi-image Screenshot + + + {{ report.contact }} + {{ report.user.email }} + {{ report.comment }} + + State: {{ report.pmState.currentStateId }} · Flow: {{ report.pmState.flowSlug || '—' }} + + + + + + Erledigt + + + Wieder öffnen + + + Screenshot + + + In /pm öffnen + + + + + + + + + + + + + + + + Keine Bug Reports vorhanden. + + + + Seite {{ bugReportPagination.page }} von {{ bugReportPagination.pages }} · {{ bugReportPagination.total }} Reports + + + Zurück + Weiter + + + + + + @@ -1280,13 +1423,33 @@ interface CreateInviteResponse { } } +interface BugReportItem { + id: string + comment: string + contact: string + user?: { id: string; email: string; name?: string } + pmState?: { flowSlug?: string; scenarioId?: string; currentStateId?: string } + status: 'open' | 'resolved' + createdAt: string | null + hasScreenshot: boolean +} + +interface BugReportDetail extends BugReportItem { + screenshot: string | null +} + +interface BugReportsResponse { + items: BugReportItem[] + pagination: { total: number; page: number; pageSize: number; pages: number } +} + definePageMeta({ middleware: 'require-admin' }) useHead({ title: 'Admin • OpenSquawk' }) const auth = useAuthStore() const api = useApi() -const activeTab = ref<'overview' | 'users' | 'invitations' | 'waitlist' | 'logs'>('overview') +const activeTab = ref<'overview' | 'users' | 'invitations' | 'waitlist' | 'logs' | 'bug-reports'>('overview') const refreshing = ref(false) const overview = ref(null) @@ -1890,11 +2053,104 @@ watch(activeTab, (tab) => { fetchWaitlist(true) } else if (tab === 'logs' && !sessionsLoaded.value) { fetchSessions(true) + } else if (tab === 'bug-reports' && !bugReportsLoaded.value) { + fetchBugReports(true) } }) +// ── Bug Reports ────────────────────────────────────────────────────────────── +const bugReports = ref([]) +const bugReportPagination = reactive({ total: 0, page: 1, pages: 1, pageSize: 20 }) +const bugReportLoading = ref(false) +const bugReportError = ref('') +const bugReportsLoaded = ref(false) +const bugReportStatusFilter = ref<'open' | 'resolved'>('open') +const bugReportOpenCount = ref(0) +const bugReportResolving = ref(null) +const bugReportDetailId = ref(null) +const bugReportDetail = ref(null) +const bugReportDetailLoading = ref(false) + +async function fetchBugReports(resetPage = false) { + if (resetPage) bugReportPagination.page = 1 + bugReportLoading.value = true + bugReportError.value = '' + try { + const response = await api.get('/api/admin/bug-reports', { + query: { status: bugReportStatusFilter.value, page: bugReportPagination.page }, + }) + bugReports.value = response.items + Object.assign(bugReportPagination, response.pagination) + bugReportsLoaded.value = true + if (bugReportStatusFilter.value === 'open') bugReportOpenCount.value = response.pagination.total + } catch (error) { + bugReportError.value = extractErrorMessage(error, 'Bug Reports konnten nicht geladen werden.') + } finally { + bugReportLoading.value = false + } +} + +async function loadBugReportDetail(id: string) { + if (bugReportDetailId.value === id) { + bugReportDetailId.value = null + bugReportDetail.value = null + return + } + bugReportDetailId.value = id + bugReportDetail.value = null + bugReportDetailLoading.value = true + try { + bugReportDetail.value = await api.get(`/api/admin/bug-reports/${id}`) + } catch { + bugReportDetailId.value = null + } finally { + bugReportDetailLoading.value = false + } +} + +async function resolveBugReport(id: string) { + bugReportResolving.value = id + try { + await api.request(`/api/admin/bug-reports/${id}`, { method: 'PATCH', body: { status: 'resolved' } }) + const report = bugReports.value.find((r) => r.id === id) + if (report) { report.status = 'resolved'; bugReportOpenCount.value = Math.max(0, bugReportOpenCount.value - 1) } + if (bugReportStatusFilter.value === 'open') bugReports.value = bugReports.value.filter((r) => r.id !== id) + } catch (error) { + bugReportError.value = extractErrorMessage(error, 'Status konnte nicht geändert werden.') + } finally { + bugReportResolving.value = null + } +} + +async function reopenBugReport(id: string) { + bugReportResolving.value = id + try { + await api.request(`/api/admin/bug-reports/${id}`, { method: 'PATCH', body: { status: 'open' } }) + const report = bugReports.value.find((r) => r.id === id) + if (report) { report.status = 'open'; bugReportOpenCount.value++ } + if (bugReportStatusFilter.value === 'resolved') bugReports.value = bugReports.value.filter((r) => r.id !== id) + } catch (error) { + bugReportError.value = extractErrorMessage(error, 'Status konnte nicht geändert werden.') + } finally { + bugReportResolving.value = null + } +} + +function changeBugReportPage(page: number) { + if (page < 1 || page > bugReportPagination.pages) return + bugReportPagination.page = page + fetchBugReports() +} + +watch(bugReportStatusFilter, () => fetchBugReports(true)) +// ──────────────────────────────────────────────────────────────────────────── + onMounted(() => { loadOverview(true) + // Load open bug report count for the badge + api.get('/api/admin/bug-reports', { query: { status: 'open', page: 1 } }) + .then((r) => { bugReportOpenCount.value = r.pagination.total }) + .catch(() => {}) })
Fehlermeldungen von Testern aus der /pm-Seite.
Bug Reports laden…
{{ report.contact }}
{{ report.user.email }}
{{ report.comment }}
Keine Bug Reports vorhanden.