diff --git a/webapp-next/src/components/current-duty/CurrentDutyView.test.tsx b/webapp-next/src/components/current-duty/CurrentDutyView.test.tsx index ee49c7f..aab8314 100644 --- a/webapp-next/src/components/current-duty/CurrentDutyView.test.tsx +++ b/webapp-next/src/components/current-duty/CurrentDutyView.test.tsx @@ -57,6 +57,18 @@ describe("CurrentDutyView", () => { expect(screen.queryByText(/Back to calendar|Назад к календарю/i)).not.toBeInTheDocument(); }); + it("shows Open calendar button in no-duty view and it calls onBack", async () => { + const onBack = vi.fn(); + render(); + await screen.findByText(/No one is on duty|Сейчас никто не дежурит/i, {}, { timeout: 3000 }); + const openCalendarBtn = screen.getByRole("button", { + name: /Open calendar|Открыть календарь/i, + }); + expect(openCalendarBtn).toBeInTheDocument(); + fireEvent.click(openCalendarBtn); + expect(onBack).toHaveBeenCalled(); + }); + it("shows contact info not set when duty has no phone or username", async () => { const { fetchDuties } = await import("@/lib/api"); const now = new Date(); @@ -81,4 +93,38 @@ describe("CurrentDutyView", () => { ).toBeInTheDocument(); vi.mocked(fetchDuties).mockResolvedValue([]); }); + + it("shows ends_at line when duty is active", async () => { + const { fetchDuties } = await import("@/lib/api"); + const now = new Date(); + const start = new Date(now.getTime() - 60 * 60 * 1000); + const end = new Date(now.getTime() + 2 * 60 * 60 * 1000); + const duty = { + id: 1, + user_id: 1, + start_at: start.toISOString(), + end_at: end.toISOString(), + event_type: "duty" as const, + full_name: "Test User", + phone: null, + username: null, + }; + vi.mocked(fetchDuties).mockResolvedValue([duty]); + render(); + await screen.findByText("Test User", {}, { timeout: 3000 }); + expect( + screen.getByText(/Until end of shift at|До конца смены в/i) + ).toBeInTheDocument(); + vi.mocked(fetchDuties).mockResolvedValue([]); + }); + + it("error state shows Retry as first button", async () => { + const { fetchDuties } = await import("@/lib/api"); + vi.mocked(fetchDuties).mockRejectedValue(new Error("Network error")); + render(); + await screen.findByText(/Could not load|Не удалось загрузить/i, {}, { timeout: 3000 }); + const buttons = screen.getAllByRole("button"); + expect(buttons[0]).toHaveAccessibleName(/Retry|Повторить/i); + vi.mocked(fetchDuties).mockResolvedValue([]); + }); }); diff --git a/webapp-next/src/components/current-duty/CurrentDutyView.tsx b/webapp-next/src/components/current-duty/CurrentDutyView.tsx index f043eb6..08ed618 100644 --- a/webapp-next/src/components/current-duty/CurrentDutyView.tsx +++ b/webapp-next/src/components/current-duty/CurrentDutyView.tsx @@ -166,15 +166,14 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi aria-label={t("loading")} > - +
- - + +
-
-
- + +
@@ -246,10 +245,17 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi {t("current_duty.no_duty")}

- + +
@@ -287,41 +293,45 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi role="article" aria-labelledby="current-duty-title" > - - - - {t("current_duty.title")} - + + {t("current_duty.title")} - + + + + {t("duty.now_on_duty")} +

{duty.full_name}

- {shiftLabel} {shiftStr} + {shiftLabel} +

+

+ {shiftStr}

{t("current_duty.remaining_label")} - + {remainingValueStr} + + {t("current_duty.ends_at", { time: formatHHMM(duty.end_at) })} +
{hasContacts ? ( diff --git a/webapp-next/src/i18n/messages.ts b/webapp-next/src/i18n/messages.ts index 57f97e8..427da3a 100644 --- a/webapp-next/src/i18n/messages.ts +++ b/webapp-next/src/i18n/messages.ts @@ -72,6 +72,7 @@ export const MESSAGES: Record> = { "current_duty.ends_at": "Until end of shift at {time}", "current_duty.back": "Back to calendar", "current_duty.close": "Close", + "current_duty.open_calendar": "Open calendar", "current_duty.contact_info_not_set": "Contact info not set", "error_boundary.message": "Something went wrong.", "error_boundary.description": "An unexpected error occurred. Try reloading the app.", @@ -147,6 +148,7 @@ export const MESSAGES: Record> = { "current_duty.ends_at": "До конца смены в {time}", "current_duty.back": "Назад к календарю", "current_duty.close": "Закрыть", + "current_duty.open_calendar": "Открыть календарь", "current_duty.contact_info_not_set": "Контактные данные не указаны", "error_boundary.message": "Что-то пошло не так.", "error_boundary.description": "Произошла непредвиденная ошибка. Попробуйте перезагрузить приложение.",