From 92911283be71e0a945819e5072dddecb12c81c19 Mon Sep 17 00:00:00 2001
From: haritowa <6107211+haritowa@users.noreply.github.com>
Date: Thu, 22 Oct 2026 16:45:02 +0000
Subject: [PATCH] fix(medications): offer "End" only for a course that is
running
Ending sends today as the end of the medication's latest course. While
the PUT was refused this did nothing; now that it succeeds, pressing End
on a course that had already ended reopened it up to today (doses due
again, the days between counted as missed), and on a course that had not
started yet it became a one-day course today.
LifecycleManageBody now takes the server-resolved courseStatus and shows
End only while the course is CURRENT (a medication with no dates is
CURRENT). Pausing stays available whatever the course.
---
.../detail/medication-detail-tabs.tsx | 1 +
.../destructive-zone-section.test.ts | 40 ------
.../destructive-zone-section.test.tsx | 91 ++++++++++++
.../sections/destructive-zone-section.tsx | 130 ++++++++++--------
4 files changed, 167 insertions(+), 95 deletions(-)
delete mode 100644 src/components/medications/sections/__tests__/destructive-zone-section.test.ts
create mode 100644 src/components/medications/sections/__tests__/destructive-zone-section.test.tsx
diff --git a/src/components/medications/detail/medication-detail-tabs.tsx b/src/components/medications/detail/medication-detail-tabs.tsx
index 39a7a8b7b..ba0e5da5d 100644
--- a/src/components/medications/detail/medication-detail-tabs.tsx
+++ b/src/components/medications/detail/medication-detail-tabs.tsx
@@ -927,6 +927,7 @@ export function MedicationDetailTabs({
medicationId={id}
medicationName={medication.name}
active={medication.active}
+ courseStatus={served.courseStatus}
/>
{/* v1.40 (#1024) — the courses: when it was taken, and a new
diff --git a/src/components/medications/sections/__tests__/destructive-zone-section.test.ts b/src/components/medications/sections/__tests__/destructive-zone-section.test.ts
deleted file mode 100644
index b1702880f..000000000
--- a/src/components/medications/sections/__tests__/destructive-zone-section.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { describe, expect, it } from "vitest";
-
-import { updateMedicationSchema } from "@/lib/validations/medication/create-update";
-
-import { endCourseBody } from "../destructive-zone-section";
-
-/**
- * "End medication" sent `new Date().toISOString()`, a full instant, against an
- * `endsOn` that only takes `YYYY-MM-DD`, so every press answered 422 and the
- * toast said the medication could not be ended. The body now carries the
- * person's own calendar day.
- */
-describe("endCourseBody", () => {
- it("is a date the update schema accepts", () => {
- const body = endCourseBody(
- new Date("2026-10-21T09:30:00.000Z"),
- "Europe/Berlin",
- );
- expect(body).toEqual({ endsOn: "2026-10-21" });
- expect(updateMedicationSchema.safeParse(body).success).toBe(true);
- });
-
- it("is the person's day, not the UTC one", () => {
- // 23:30 UTC is already the next day in Warsaw and still the same day in
- // New York.
- const lateUtc = new Date("2026-10-20T23:30:00.000Z");
- expect(endCourseBody(lateUtc, "Europe/Warsaw").endsOn).toBe("2026-10-21");
- expect(endCourseBody(lateUtc, "America/New_York").endsOn).toBe(
- "2026-10-20",
- );
- });
-
- it("is what the update schema refused before: a full instant is not a date", () => {
- expect(
- updateMedicationSchema.safeParse({
- endsOn: new Date("2026-10-21T09:30:00.000Z").toISOString(),
- }).success,
- ).toBe(false);
- });
-});
diff --git a/src/components/medications/sections/__tests__/destructive-zone-section.test.tsx b/src/components/medications/sections/__tests__/destructive-zone-section.test.tsx
new file mode 100644
index 000000000..3e71ba1da
--- /dev/null
+++ b/src/components/medications/sections/__tests__/destructive-zone-section.test.tsx
@@ -0,0 +1,91 @@
+import { describe, expect, it, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { updateMedicationSchema } from "@/lib/validations/medication/create-update";
+
+import {
+ canEndCourse,
+ endCourseBody,
+ LifecycleManageBody,
+} from "../destructive-zone-section";
+
+vi.mock("@/hooks/use-auth", () => ({
+ useAuth: () => ({
+ user: { id: "test-user", username: "tester", role: "USER" },
+ isAuthenticated: true,
+ isLoading: false,
+ }),
+}));
+
+/**
+ * "End medication" sent `new Date().toISOString()`, a full instant, against an
+ * `endsOn` that only takes `YYYY-MM-DD`, so every press answered 422 and the
+ * toast said the medication could not be ended. The body now carries the
+ * person's own calendar day.
+ */
+describe("endCourseBody", () => {
+ it("is a date the update schema accepts", () => {
+ const body = endCourseBody(
+ new Date("2026-10-21T09:30:00.000Z"),
+ "Europe/Berlin",
+ );
+ expect(body).toEqual({ endsOn: "2026-10-21" });
+ expect(updateMedicationSchema.safeParse(body).success).toBe(true);
+ });
+
+ it("is the person's day, not the UTC one", () => {
+ // 23:30 UTC is already the next day in Warsaw and still the same day in
+ // New York.
+ const lateUtc = new Date("2026-10-20T23:30:00.000Z");
+ expect(endCourseBody(lateUtc, "Europe/Warsaw").endsOn).toBe("2026-10-21");
+ expect(endCourseBody(lateUtc, "America/New_York").endsOn).toBe(
+ "2026-10-20",
+ );
+ });
+
+ it("is what the update schema refused before: a full instant is not a date", () => {
+ expect(
+ updateMedicationSchema.safeParse({
+ endsOn: new Date("2026-10-21T09:30:00.000Z").toISOString(),
+ }).success,
+ ).toBe(false);
+ });
+});
+
+/**
+ * Ending writes today as the end of the latest course. On a course that has
+ * already ended that would reopen it up to today, and on one that has not
+ * started it would become a one-day course today, so End is only offered while
+ * the course runs. A medication with no dates is CURRENT.
+ */
+describe("End is offered only for a running course", () => {
+ function render(courseStatus?: "UPCOMING" | "CURRENT" | "ENDED") {
+ return renderToStaticMarkup(
+
- {t("medications.detail.zone.end.title")} -
-- {t("medications.detail.zone.end.helper")} -
-+ {t("medications.detail.zone.end.title")} +
++ {t("medications.detail.zone.end.helper")} +
+