From 9b8c76390185ff237b5e0e11750fe4c677a82510 Mon Sep 17 00:00:00 2001 From: haritowa <6107211+haritowa@users.noreply.github.com> Date: Thu, 22 Oct 2026 18:18:07 +0000 Subject: [PATCH] refactor(medications): tidy the End guard after review The course-status prop uses the shared CourseStatus type instead of a spelled-out union, and the End confirmation's open state is dropped when End stops being offered, so a dialog closed by a refetch cannot pop up by itself once a later course offers End again. --- .../sections/destructive-zone-section.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/medications/sections/destructive-zone-section.tsx b/src/components/medications/sections/destructive-zone-section.tsx index e39b51b44..f627a22b4 100644 --- a/src/components/medications/sections/destructive-zone-section.tsx +++ b/src/components/medications/sections/destructive-zone-section.tsx @@ -50,6 +50,7 @@ import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import { invalidateMedicationReads, queryKeys } from "@/lib/query-keys"; import { apiDelete, apiPut } from "@/lib/api/api-fetch"; +import type { CourseStatus } from "@/lib/medications/course-window"; import { DEFAULT_TIMEZONE, userDayKey } from "@/lib/tz/format"; /** @@ -69,9 +70,7 @@ export function endCourseBody(now: Date, timeZone: string): { endsOn: string } { * started it would turn into a one-day course today. Unknown (an older * server) keeps the button. */ -export function canEndCourse( - courseStatus: "UPCOMING" | "CURRENT" | "ENDED" | undefined, -): boolean { +export function canEndCourse(courseStatus: CourseStatus | undefined): boolean { return courseStatus === undefined || courseStatus === "CURRENT"; } @@ -94,19 +93,30 @@ export function LifecycleManageBody({ medicationName: string; active: boolean; /** Where today sits in the course (server-resolved). */ - courseStatus?: "UPCOMING" | "CURRENT" | "ENDED"; + courseStatus?: CourseStatus; onAfterAction?: () => void; }) { const { t } = useTranslations(); const queryClient = useQueryClient(); const { user } = useAuth(); const userTz = user?.timezone || DEFAULT_TIMEZONE; + const canEnd = canEndCourse(courseStatus); const [paused, setPaused] = useState(!active); const [tier1Busy, setTier1Busy] = useState(false); const [endDialogOpen, setEndDialogOpen] = useState(false); const [tier2Busy, setTier2Busy] = useState(false); + // A refetch can stop offering End while its confirmation is open (the + // course ended in another tab). Drop the open state with it, or the dialog + // would pop up by itself once a later course offers End again. Adjusted + // during render, React's pattern for state that follows a prop. + const [offeredEnd, setOfferedEnd] = useState(canEnd); + if (offeredEnd !== canEnd) { + setOfferedEnd(canEnd); + if (!canEnd) setEndDialogOpen(false); + } + async function tier1Toggle(pauseNext: boolean) { if (tier1Busy) return; setTier1Busy(true); @@ -184,7 +194,7 @@ export function LifecycleManageBody({ /> - {canEndCourse(courseStatus) ? ( + {canEnd ? ( <>