diff --git a/apps/admin-web/src/api/adminApiClient.ts b/apps/admin-web/src/api/adminApiClient.ts index 52491b61c..5155a2470 100644 --- a/apps/admin-web/src/api/adminApiClient.ts +++ b/apps/admin-web/src/api/adminApiClient.ts @@ -23,6 +23,8 @@ import type { AdminEditorShowcaseListQuery, AdminEditorShowcaseListResponse, AdminEditorShowcaseReviewRequest, + AdminErrorReportDetail, + AdminErrorReportListResponse, AdminFeatureGateConfigResponse, AdminLoginResponse, AdminMeResponse, @@ -280,14 +282,14 @@ export function listAdminErrorReports( limit?: number; } = {}, ) { - return request( + return request( `/admin/api/error-reports${buildErrorReportQueryString(query)}`, { token }, ); } export function getAdminErrorReport(token: string, batchId: string) { - return request( + return request( `/admin/api/error-reports/${encodeURIComponent(batchId)}`, { token }, ); @@ -298,7 +300,7 @@ export function updateAdminErrorReport( batchId: string, payload: { status: string; note?: string }, ) { - return request( + return request( `/admin/api/error-reports/${encodeURIComponent(batchId)}`, { method: 'PATCH', token, body: payload }, ); diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index 59efb2b56..c2f9b3b52 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -112,7 +112,6 @@ export interface AdminErrorReportEntry { ossObjectKey?: string; archiveSha256?: string; uploadStatus?: 'uploading' | 'ready' | 'failed'; - reviewStatus?: 'new' | 'in-progress' | 'resolved'; } export interface AdminErrorReportListResponse { diff --git a/apps/admin-web/src/pages/AdminErrorReportsPage.tsx b/apps/admin-web/src/pages/AdminErrorReportsPage.tsx index 949fa00ae..9c58a5ef7 100644 --- a/apps/admin-web/src/pages/AdminErrorReportsPage.tsx +++ b/apps/admin-web/src/pages/AdminErrorReportsPage.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { downloadAdminErrorReport, @@ -15,14 +15,18 @@ import type { type Props = { token: string; onUnauthorized: (message?: string) => void }; +const ADMIN_ERROR_REPORT_STATUSES = ['new', 'in-progress', 'resolved'] as const; + export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { const [reports, setReports] = useState([]); const [selected, setSelected] = useState(null); const [status, setStatus] = useState(''); const [busy, setBusy] = useState(false); const [filterStatus, setFilterStatus] = useState(''); + const openReportRequestId = useRef(0); const load = useCallback(async () => { + setStatus(''); try { const response = await listAdminErrorReports(token, { status: filterStatus || undefined, @@ -40,9 +44,13 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { }, [load]); async function openReport(batchId: string) { + const requestId = ++openReportRequestId.current; + setStatus(''); try { - setSelected(await getAdminErrorReport(token, batchId)); + const detail = await getAdminErrorReport(token, batchId); + if (requestId === openReportRequestId.current) setSelected(detail); } catch (error) { + if (requestId !== openReportRequestId.current) return; if (isAdminApiError(error) && error.status === 401) return onUnauthorized(); setStatus(formatAdminApiError(error)); @@ -52,6 +60,7 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { async function saveStatus(nextStatus: string) { if (!selected || busy) return; setBusy(true); + setStatus(''); try { setSelected( await updateAdminErrorReport(token, selected.batchId, { @@ -69,13 +78,16 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { } async function download(batchId: string) { + setStatus(''); try { const blob = await downloadAdminErrorReport(token, batchId); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = `${batchId}.zip`; + document.body.appendChild(anchor); anchor.click(); + anchor.remove(); URL.revokeObjectURL(url); } catch (error) { if (isAdminApiError(error) && error.status === 401) onUnauthorized(); @@ -97,13 +109,19 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { onChange={(event) => setFilterStatus(event.target.value)} > - - - + {ADMIN_ERROR_REPORT_STATUSES.map((value) => ( + + ))} - {status ?

{status}

: null} + {status ? ( +

+ {status} +

+ ) : null}
@@ -160,9 +178,11 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { onChange={(event) => void saveStatus(event.target.value)} disabled={busy} > - - - + {ADMIN_ERROR_REPORT_STATUSES.map((value) => ( + + ))}