fix: restore schedule interaction correctness

* fix: restore schedule interaction correctness

* fix: use scheduled stock timing for historical doses
This commit is contained in:
Daniel Volz
2026-03-14 20:49:13 +01:00
committed by GitHub
parent 816888a697
commit 0160ef3ddf
15 changed files with 888 additions and 286 deletions
+19 -8
View File
@@ -3,6 +3,7 @@
// =============================================================================
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
export interface UseDosesReturn {
takenDoses: Set<string>;
@@ -10,8 +11,6 @@ export interface UseDosesReturn {
takenDoseTimestamps: Map<string, number>;
takenDoseSources: Map<string, "manual" | "automatic">;
dismissedDoses: Set<string>;
showClearMissedConfirm: boolean;
setShowClearMissedConfirm: (show: boolean) => void;
clearDosesState: () => void;
getDoseId: (baseDoseId: string, person: string | null) => string;
isDoseTakenAutomatically: (doseId: string) => boolean;
@@ -22,11 +21,11 @@ export interface UseDosesReturn {
}
export function useDoses(): UseDosesReturn {
const { t } = useTranslation();
const [takenDoses, setTakenDoses] = useState<Set<string>>(new Set());
const [takenDoseTimestamps, setTakenDoseTimestamps] = useState<Map<string, number>>(new Map());
const [takenDoseSources, setTakenDoseSources] = useState<Map<string, "manual" | "automatic">>(new Map());
const [dismissedDoses, setDismissedDoses] = useState<Set<string>>(new Set());
const [showClearMissedConfirm, setShowClearMissedConfirm] = useState(false);
// Track in-flight mutations to prevent polling from overwriting optimistic updates
const mutationInFlightRef = useRef(0);
@@ -36,7 +35,6 @@ export function useDoses(): UseDosesReturn {
setTakenDoseTimestamps(new Map());
setTakenDoseSources(new Map());
setDismissedDoses(new Set());
setShowClearMissedConfirm(false);
mutationInFlightRef.current = 0;
}, []);
@@ -118,6 +116,15 @@ export function useDoses(): UseDosesReturn {
[takenDoses, getDoseId]
);
const getErrorCode = useCallback(async (response: Response): Promise<string | null> => {
try {
const data = (await response.json()) as { code?: string };
return typeof data.code === "string" ? data.code : null;
} catch {
return null;
}
}, []);
const markDoseTaken = useCallback(
async (doseId: string) => {
// Optimistic update
@@ -140,12 +147,18 @@ export function useDoses(): UseDosesReturn {
// Send to server
try {
await fetch("/api/doses/taken", {
const response = await fetch("/api/doses/taken", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ doseId }),
});
if (!response.ok) {
if ((await getErrorCode(response)) === "OUT_OF_STOCK") {
alert(t("common.outOfStockTakeBlocked"));
}
throw new Error("Failed to mark dose as taken");
}
} catch {
// Revert on error
setTakenDoses((prev) => {
@@ -169,7 +182,7 @@ export function useDoses(): UseDosesReturn {
loadTakenDoses();
}
},
[loadTakenDoses]
[getErrorCode, loadTakenDoses, t]
);
const undoDoseTaken = useCallback(
@@ -220,8 +233,6 @@ export function useDoses(): UseDosesReturn {
takenDoseTimestamps,
takenDoseSources,
dismissedDoses,
showClearMissedConfirm,
setShowClearMissedConfirm,
clearDosesState,
getDoseId,
isDoseTakenAutomatically,