mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
fix(report): cut the visit offer's end day on the record's calendar
The offer took its start from the server's preparation window, cut on the record's calendar, and its end from the appointment's day in the browser's zone. In a browser in another zone a late-evening or early-morning visit moved the end a day against the start. Both ends now use the record's zone.
This commit is contained in:
1 parent
03cacc996c
commit
88200a9273
2 files changed
+37
-12
No files matched your search
@@ -155,10 +155,26 @@ describe("<ReportVisitOffer>", () => {
|
||||
occurredAt: at.toISOString(),
|
||||
preparation: { from: "2026-03-02", basis: "default", since: null },
|
||||
}),
|
||||
"Europe/Berlin",
|
||||
);
|
||||
expect(window.from).toBe("2026-03-02");
|
||||
expect(window.to).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
||||
// Without a window from the server, only the end moves.
|
||||
expect(visitReportWindow(visit({ id: "plain" })).from).toBeNull();
|
||||
expect(
|
||||
visitReportWindow(visit({ id: "plain" }), "Europe/Berlin").from,
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("cuts the appointment's day on the record's calendar, like the start", () => {
|
||||
// 04:30 on the 12th in Berlin is still the evening of the 11th in New
|
||||
// York. The start comes from the server on the record's calendar; the
|
||||
// end is cut in the zone it is handed, never the browser's own.
|
||||
const early = visit({
|
||||
id: "early",
|
||||
occurredAt: "2026-11-12T03:30:00.000Z",
|
||||
preparation: { from: "2026-08-12", basis: "default", since: null },
|
||||
});
|
||||
expect(visitReportWindow(early, "Europe/Berlin").to).toBe("2026-11-12");
|
||||
expect(visitReportWindow(early, "America/New_York").to).toBe("2026-11-11");
|
||||
});
|
||||
});
|
||||
@@ -28,9 +28,13 @@ import { encounterKindText } from "@/components/encounters/encounter-labels";
|
||||
import { preparationText } from "@/components/encounters/preparation-text";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useEncounters, type Encounter } from "@/hooks/use-encounters";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import {
|
||||
useDisplayTimezone,
|
||||
useFormatters,
|
||||
useTranslations,
|
||||
} from "@/lib/i18n/context";
|
||||
import type { EncounterKind } from "@/generated/prisma/client";
|
||||
import { detectBrowserTimezone, userDayKey } from "@/lib/tz/format";
|
||||
import { userDayKey } from "@/lib/tz/format";
|
||||
|
||||
/**
|
||||
* How far ahead a booked visit is treated as the reason for this report.
|
||||
@@ -40,11 +44,6 @@ import { detectBrowserTimezone, userDayKey } from "@/lib/tz/format";
|
||||
*/
|
||||
export const REPORT_VISIT_HORIZON_DAYS = 14;
|
||||
|
||||
/** The local `yyyy-MM-dd` a `DateField` takes, in the reader's own day. */
|
||||
function localDay(iso: string): string {
|
||||
return userDayKey(new Date(iso), detectBrowserTimezone());
|
||||
}
|
||||
|
||||
/** The report window an appointment offers, as `yyyy-MM-dd` days. */
|
||||
export interface VisitReportWindow {
|
||||
/** The server's start of the window before the visit; null if it has none. */
|
||||
@@ -53,11 +52,20 @@ export interface VisitReportWindow {
|
||||
to: string;
|
||||
}
|
||||
|
||||
/** What pressing the offer for `visit` applies. */
|
||||
export function visitReportWindow(visit: Encounter): VisitReportWindow {
|
||||
/**
|
||||
* What pressing the offer for `visit` applies. Both ends are days of the
|
||||
* record's own calendar (`timeZone`, the profile zone): the server cuts
|
||||
* `preparation.from` there, and the appointment's day has to be cut in the
|
||||
* same zone, or a browser in another zone moved the end a day against the
|
||||
* start for a late-evening or early-morning visit.
|
||||
*/
|
||||
export function visitReportWindow(
|
||||
visit: Encounter,
|
||||
timeZone: string,
|
||||
): VisitReportWindow {
|
||||
return {
|
||||
from: visit.preparation?.from ?? null,
|
||||
to: localDay(visit.occurredAt),
|
||||
to: userDayKey(new Date(visit.occurredAt), timeZone),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,6 +77,7 @@ export function ReportVisitOffer({
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const format = useFormatters();
|
||||
const timeZone = useDisplayTimezone();
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const visits = useEncounters();
|
||||
|
||||
@@ -131,7 +140,7 @@ export function ReportVisitOffer({
|
||||
size="sm"
|
||||
className="ml-auto min-h-11 sm:ml-0 sm:min-h-9"
|
||||
data-slot="report-visit-offer-apply"
|
||||
onClick={() => onUseVisitWindow(visitReportWindow(next))}
|
||||
onClick={() => onUseVisitWindow(visitReportWindow(next, timeZone))}
|
||||
>
|
||||
{t("settings.healthRecord.visitOfferApplyWindow")}
|
||||
</Button>
|
||||
|
||||
Reference in new issue
Block a user