- Updated Mini App design guidelines to include detailed instructions on UI changes, accessibility rules, and verification processes. - Refactored multiple components to utilize `MiniAppScreen` and `MiniAppScreenContent` for consistent layout structure across the application. - Improved error handling in `GlobalError` and `NotFound` components by integrating new layout components for better user experience. - Introduced new hooks for admin functionality, streamlining access checks and data loading processes. - Enhanced documentation to reflect changes in design policies and component usage, ensuring clarity for future development.
132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
/**
|
|
* Admin page: list duties for the month and reassign duty to another user.
|
|
* Visible only to admins (link shown on calendar when GET /api/admin/me returns is_admin).
|
|
* Logic and heavy UI live in components/admin (useAdminPage, AdminDutyList, ReassignSheet).
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import { useAdminPage, AdminDutyList, ReassignSheet } from "@/components/admin";
|
|
import { useTranslation } from "@/i18n/use-translation";
|
|
import { MonthNavHeader } from "@/components/calendar/MonthNavHeader";
|
|
import { AccessDeniedScreen } from "@/components/states/AccessDeniedScreen";
|
|
import { LoadingState } from "@/components/states/LoadingState";
|
|
import { ErrorState } from "@/components/states/ErrorState";
|
|
import { MiniAppScreen, MiniAppScreenContent, MiniAppStickyHeader } from "@/components/layout/MiniAppScreen";
|
|
import { useScreenReady } from "@/hooks/use-screen-ready";
|
|
|
|
export default function AdminPage() {
|
|
const { t, monthName } = useTranslation();
|
|
const admin = useAdminPage();
|
|
useScreenReady(true);
|
|
|
|
if (!admin.isAllowed) {
|
|
return (
|
|
<MiniAppScreen>
|
|
<MiniAppScreenContent>
|
|
<AccessDeniedScreen primaryAction="reload" />
|
|
</MiniAppScreenContent>
|
|
</MiniAppScreen>
|
|
);
|
|
}
|
|
|
|
if (admin.adminCheckComplete === null) {
|
|
return (
|
|
<MiniAppScreen>
|
|
<MiniAppScreenContent>
|
|
<div className="py-4 flex flex-col items-center gap-2">
|
|
<LoadingState />
|
|
<p className="text-sm text-muted-foreground">{t("admin.loading_users")}</p>
|
|
</div>
|
|
</MiniAppScreenContent>
|
|
</MiniAppScreen>
|
|
);
|
|
}
|
|
|
|
if (admin.adminAccessDenied) {
|
|
return (
|
|
<MiniAppScreen>
|
|
<MiniAppScreenContent>
|
|
<div className="flex flex-col gap-4 py-6">
|
|
<p className="text-muted-foreground">
|
|
{admin.adminAccessDeniedDetail ?? t("admin.access_denied")}
|
|
</p>
|
|
</div>
|
|
</MiniAppScreenContent>
|
|
</MiniAppScreen>
|
|
);
|
|
}
|
|
|
|
const month = admin.adminMonth.getMonth();
|
|
const year = admin.adminMonth.getFullYear();
|
|
|
|
return (
|
|
<MiniAppScreen>
|
|
<MiniAppScreenContent>
|
|
<MiniAppStickyHeader className="flex flex-col items-center border-b border-border py-3">
|
|
<MonthNavHeader
|
|
month={admin.adminMonth}
|
|
disabled={admin.loading}
|
|
onPrevMonth={admin.onPrevMonth}
|
|
onNextMonth={admin.onNextMonth}
|
|
titleAriaLabel={`${t("admin.title")}, ${monthName(month)} ${year}`}
|
|
className="w-full px-1"
|
|
/>
|
|
</MiniAppStickyHeader>
|
|
|
|
{admin.successMessage && (
|
|
<p className="mt-3 text-sm text-[var(--duty)]" role="status" aria-live="polite">
|
|
{admin.successMessage}
|
|
</p>
|
|
)}
|
|
|
|
{admin.loading && (
|
|
<div className="py-4 flex flex-col items-center gap-2">
|
|
<LoadingState />
|
|
<p className="text-sm text-muted-foreground">{t("admin.loading_users")}</p>
|
|
</div>
|
|
)}
|
|
|
|
{admin.error && !admin.loading && (
|
|
<ErrorState
|
|
message={admin.error}
|
|
onRetry={() => window.location.reload()}
|
|
className="my-3"
|
|
/>
|
|
)}
|
|
|
|
{!admin.loading && !admin.error && (
|
|
<div className="mt-3 flex flex-col gap-2">
|
|
{admin.visibleGroups.length > 0 && (
|
|
<p className="text-sm text-muted-foreground">
|
|
{t("admin.reassign_duty")}: {t("admin.select_user")}
|
|
</p>
|
|
)}
|
|
<AdminDutyList
|
|
groups={admin.visibleGroups}
|
|
hasMore={admin.hasMore}
|
|
sentinelRef={admin.sentinelRef}
|
|
onSelectDuty={admin.openReassign}
|
|
t={t}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<ReassignSheet
|
|
open={!admin.sheetExiting && admin.selectedDuty !== null}
|
|
selectedDuty={admin.selectedDuty}
|
|
selectedUserId={admin.selectedUserId}
|
|
setSelectedUserId={admin.setSelectedUserId}
|
|
users={admin.usersForSelect}
|
|
saving={admin.saving}
|
|
reassignErrorKey={admin.reassignErrorKey}
|
|
onReassign={admin.handleReassign}
|
|
onRequestClose={admin.requestCloseSheet}
|
|
onCloseAnimationEnd={admin.closeReassign}
|
|
t={t}
|
|
/>
|
|
</MiniAppScreenContent>
|
|
</MiniAppScreen>
|
|
);
|
|
}
|