feat: enhance admin page functionality with new components and hooks
- Added `AdminDutyList` and `ReassignSheet` components for improved duty management in the admin panel. - Introduced `useAdminPage` hook to encapsulate admin-related logic, including user and duty loading, and reassign functionality. - Updated `frontend.mdc` documentation to reflect new admin components and their usage. - Improved error handling for API responses, particularly for access denied scenarios. - Refactored admin page to utilize new components, streamlining the UI and enhancing maintainability.
This commit is contained in:
@@ -128,6 +128,27 @@ function buildFetchOptions(
|
||||
return { headers, signal: controller.signal, cleanup };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse 403 response body for user-facing detail. Returns default i18n message if body is invalid.
|
||||
*/
|
||||
async function handle403Response(
|
||||
res: Response,
|
||||
acceptLang: ApiLang,
|
||||
defaultI18nKey: string
|
||||
): Promise<string> {
|
||||
let detail = translate(acceptLang, defaultI18nKey);
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body && (body as { detail?: string }).detail !== undefined) {
|
||||
const d = (body as { detail: string | { msg?: string } }).detail;
|
||||
detail = typeof d === "string" ? d : (d.msg ?? JSON.stringify(d));
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch duties for date range. Throws AccessDeniedError on 403.
|
||||
* Rethrows AbortError when the request is cancelled (e.g. stale load).
|
||||
@@ -148,17 +169,7 @@ export async function fetchDuties(
|
||||
const res = await fetch(url, { headers: opts.headers, signal: opts.signal });
|
||||
if (res.status === 403) {
|
||||
logger.warn("Access denied", from, to);
|
||||
let detail = translate(acceptLang, "access_denied");
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body && (body as { detail?: string }).detail !== undefined) {
|
||||
const d = (body as { detail: string | { msg?: string } }).detail;
|
||||
detail =
|
||||
typeof d === "string" ? d : (d.msg ?? JSON.stringify(d));
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const detail = await handle403Response(res, acceptLang, "access_denied");
|
||||
throw new AccessDeniedError(API_ACCESS_DENIED, detail);
|
||||
}
|
||||
if (!res.ok) {
|
||||
@@ -197,17 +208,7 @@ export async function fetchCalendarEvents(
|
||||
const res = await fetch(url, { headers: opts.headers, signal: opts.signal });
|
||||
if (res.status === 403) {
|
||||
logger.warn("Access denied", from, to, "calendar-events");
|
||||
let detail = translate(acceptLang, "access_denied");
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body && (body as { detail?: string }).detail !== undefined) {
|
||||
const d = (body as { detail: string | { msg?: string } }).detail;
|
||||
detail =
|
||||
typeof d === "string" ? d : (d.msg ?? JSON.stringify(d));
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const detail = await handle403Response(res, acceptLang, "access_denied");
|
||||
throw new AccessDeniedError(API_ACCESS_DENIED, detail);
|
||||
}
|
||||
if (!res.ok) return [];
|
||||
@@ -273,17 +274,7 @@ export async function fetchAdminUsers(
|
||||
logger.debug("API request", "/api/admin/users");
|
||||
const res = await fetch(url, { headers: opts.headers, signal: opts.signal });
|
||||
if (res.status === 403) {
|
||||
let detail = translate(acceptLang, "admin.access_denied");
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body && (body as { detail?: string }).detail !== undefined) {
|
||||
const d = (body as { detail: string | { msg?: string } }).detail;
|
||||
detail =
|
||||
typeof d === "string" ? d : (d.msg ?? JSON.stringify(d));
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const detail = await handle403Response(res, acceptLang, "admin.access_denied");
|
||||
throw new AccessDeniedError(API_ACCESS_DENIED, detail);
|
||||
}
|
||||
if (!res.ok) {
|
||||
@@ -338,17 +329,7 @@ export async function patchAdminDuty(
|
||||
signal: opts.signal,
|
||||
});
|
||||
if (res.status === 403) {
|
||||
let detail = translate(acceptLang, "admin.access_denied");
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body && (body as { detail?: string }).detail !== undefined) {
|
||||
const d = (body as { detail: string | { msg?: string } }).detail;
|
||||
detail =
|
||||
typeof d === "string" ? d : (d.msg ?? JSON.stringify(d));
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const detail = await handle403Response(res, acceptLang, "admin.access_denied");
|
||||
throw new AccessDeniedError(API_ACCESS_DENIED, detail);
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
Reference in New Issue
Block a user