diff --git a/webapp-next/src/app/admin/page.test.tsx b/webapp-next/src/app/admin/page.test.tsx index 890a797..84eb44c 100644 --- a/webapp-next/src/app/admin/page.test.tsx +++ b/webapp-next/src/app/admin/page.test.tsx @@ -93,13 +93,12 @@ describe("AdminPage", () => { }); }); - it("shows admin title and back link when allowed and data loaded", async () => { + it("shows admin title (month/year) when allowed and data loaded", async () => { mockFetchForAdmin(); render(); await waitFor(() => { expect(screen.getByRole("heading", { name: /admin|админка/i })).toBeInTheDocument(); }); - expect(screen.getByRole("link", { name: /back to calendar|назад к календарю/i })).toBeInTheDocument(); }); it("shows access denied when fetchAdminMe returns is_admin false", async () => { diff --git a/webapp-next/src/app/admin/page.tsx b/webapp-next/src/app/admin/page.tsx index 743bccb..16ad3da 100644 --- a/webapp-next/src/app/admin/page.tsx +++ b/webapp-next/src/app/admin/page.tsx @@ -6,10 +6,8 @@ "use client"; -import Link from "next/link"; import { useAdminPage, AdminDutyList, ReassignSheet } from "@/components/admin"; import { useTranslation } from "@/i18n/use-translation"; -import { Button } from "@/components/ui/button"; import { AccessDeniedScreen } from "@/components/states/AccessDeniedScreen"; import { LoadingState } from "@/components/states/LoadingState"; import { ErrorState } from "@/components/states/ErrorState"; @@ -47,24 +45,28 @@ export default function AdminPage() {

{admin.adminAccessDeniedDetail ?? t("admin.access_denied")}

- ); } + const month = admin.currentMonth.getMonth(); + const year = admin.currentMonth.getFullYear(); + return (
-
-

- {t("admin.title")} — {monthName(admin.currentMonth.getMonth())}{" "} - {admin.currentMonth.getFullYear()} +
+

+ + {year} + + + {monthName(month)} +

-
{admin.successMessage && ( diff --git a/webapp-next/src/app/page.test.tsx b/webapp-next/src/app/page.test.tsx index 17740ec..453af67 100644 --- a/webapp-next/src/app/page.test.tsx +++ b/webapp-next/src/app/page.test.tsx @@ -10,6 +10,10 @@ import { resetAppStore } from "@/test/test-utils"; import { useAppStore } from "@/store/app-store"; import { useTelegramAuth } from "@/hooks/use-telegram-auth"; +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: vi.fn(), replace: vi.fn(), prefetch: vi.fn() }), +})); + vi.mock("@/hooks/use-telegram-auth", () => ({ useTelegramAuth: vi.fn(), })); diff --git a/webapp-next/src/components/CalendarPage.tsx b/webapp-next/src/components/CalendarPage.tsx index 9856654..a3b79eb 100644 --- a/webapp-next/src/components/CalendarPage.tsx +++ b/webapp-next/src/components/CalendarPage.tsx @@ -6,14 +6,14 @@ "use client"; import { useRef, useState, useEffect, useCallback } from "react"; -import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { settingsButton } from "@telegram-apps/sdk-react"; import { useAppStore } from "@/store/app-store"; import { useShallow } from "zustand/react/shallow"; import { useMonthData } from "@/hooks/use-month-data"; import { useSwipe } from "@/hooks/use-swipe"; import { useStickyScroll } from "@/hooks/use-sticky-scroll"; import { useAutoRefresh } from "@/hooks/use-auto-refresh"; -import { useTranslation } from "@/i18n/use-translation"; import { CalendarHeader } from "@/components/calendar/CalendarHeader"; import { CalendarGrid } from "@/components/calendar/CalendarGrid"; import { DutyList } from "@/components/duty/DutyList"; @@ -80,7 +80,7 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) { })) ); - const { t } = useTranslation(); + const router = useRouter(); const { retry } = useMonthData({ initDataRaw, @@ -138,6 +138,35 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) { } }, [loading, accessDenied, setAppContentReady]); + // Show native Settings button in Telegram context menu for admins; click navigates to /admin. + useEffect(() => { + if (!isAdmin) return; + let offClick: (() => void) | undefined; + try { + if (settingsButton.mount.isAvailable()) { + settingsButton.mount(); + } + if (settingsButton.show.isAvailable()) { + settingsButton.show(); + } + if (settingsButton.onClick.isAvailable()) { + offClick = settingsButton.onClick(() => router.push("/admin")); + } + } catch { + // Non-Telegram environment; SettingsButton not available. + } + return () => { + try { + if (typeof offClick === "function") offClick(); + if (settingsButton.hide.isAvailable()) { + settingsButton.hide(); + } + } catch { + // Ignore cleanup errors in non-Telegram environment. + } + }; + }, [isAdmin, router]); + return (
- {t("admin.link")} - - ) : undefined - } /> void; onNextMonth: () => void; - /** Optional content shown above the nav row (e.g. Admin link). */ - trailingContent?: ReactNode; className?: string; } @@ -31,7 +28,6 @@ export function CalendarHeader({ disabled = false, onPrevMonth, onNextMonth, - trailingContent, className, }: CalendarHeaderProps) { const { t, monthName, weekdayLabels } = useTranslation(); @@ -41,9 +37,6 @@ export function CalendarHeader({ return (
- {trailingContent != null && ( -
{trailingContent}
- )}