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}
- )}