diff --git a/duty_teller/api/app.py b/duty_teller/api/app.py index c96b092..87bc7bb 100644 --- a/duty_teller/api/app.py +++ b/duty_teller/api/app.py @@ -169,7 +169,8 @@ def app_config_js() -> Response: tz = _safe_tz_string(config.DUTY_DISPLAY_TZ) tz_js = f'\nwindow.__DT_TZ = "{tz}";' if tz else "\nwindow.__DT_TZ = undefined;" body = ( - f'window.__DT_LANG = "{lang}";\nwindow.__DT_LOG_LEVEL = "{log_level}";{tz_js}' + f'window.__DT_LANG = "{lang}";\nwindow.__DT_LOG_LEVEL = "{log_level}";{tz_js}\n' + 'if (typeof window !== "undefined") window.dispatchEvent(new Event("dt-config-loaded"));' ) return Response( content=body, diff --git a/webapp-next/src/app/admin/page.test.tsx b/webapp-next/src/app/admin/page.test.tsx index 84eb44c..c9ed174 100644 --- a/webapp-next/src/app/admin/page.test.tsx +++ b/webapp-next/src/app/admin/page.test.tsx @@ -55,7 +55,7 @@ function mockFetchForAdmin( const adminMe = options?.adminMe ?? { is_admin: true }; vi.stubGlobal( "fetch", - vi.fn((url: string, init?: RequestInit) => { + vi.fn((url: string, _init?: RequestInit) => { if (url.includes("/api/admin/me")) { return Promise.resolve({ ok: true, @@ -163,7 +163,7 @@ describe("AdminPage", () => { fireEvent.click(dutyButton); await waitFor(() => { expect( - screen.getByLabelText(/select user|выберите пользователя/i) + screen.getByRole("radiogroup", { name: /select user|выберите пользователя/i }) ).toBeInTheDocument(); }); expect(screen.getByRole("button", { name: /save|сохранить/i })).toBeInTheDocument(); @@ -231,10 +231,9 @@ describe("AdminPage", () => { }); fireEvent.click(screen.getByRole("button", { name: /Alice/ })); await waitFor(() => { - expect(screen.getByLabelText(/select user|выберите пользователя/i)).toBeInTheDocument(); + expect(screen.getByRole("radiogroup", { name: /select user|выберите пользователя/i })).toBeInTheDocument(); }); - const select = screen.getByLabelText(/select user|выберите пользователя/i); - fireEvent.change(select, { target: { value: "2" } }); + fireEvent.click(screen.getByRole("radio", { name: /Bob/ })); fireEvent.click(screen.getByRole("button", { name: /save|сохранить/i })); await waitFor(() => { expect( diff --git a/webapp-next/src/app/admin/page.tsx b/webapp-next/src/app/admin/page.tsx index 16ad3da..bd10418 100644 --- a/webapp-next/src/app/admin/page.tsx +++ b/webapp-next/src/app/admin/page.tsx @@ -11,6 +11,8 @@ import { useTranslation } from "@/i18n/use-translation"; import { AccessDeniedScreen } from "@/components/states/AccessDeniedScreen"; import { LoadingState } from "@/components/states/LoadingState"; import { ErrorState } from "@/components/states/ErrorState"; +import { Button } from "@/components/ui/button"; +import { ChevronLeft as ChevronLeftIcon, ChevronRight as ChevronRightIcon } from "lucide-react"; const PAGE_WRAPPER_CLASS = "content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6"; @@ -50,27 +52,54 @@ export default function AdminPage() { ); } - const month = admin.currentMonth.getMonth(); - const year = admin.currentMonth.getFullYear(); + const month = admin.adminMonth.getMonth(); + const year = admin.adminMonth.getFullYear(); return (
+ {admin.loading ? "…" : t("admin.duties_count", { count: String(admin.dutyOnly.length) })} +
+
{admin.successMessage}
)} @@ -96,7 +125,7 @@ export default function AdminPage() { {t("admin.reassign_duty")}: {t("admin.select_user")}+ {t("duty.none_this_month_hint")} +
+ +{t("admin.no_duties")}
; + if (groups.length === 0) { + return