mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(vaccinations): date a dose by its calendar day on every surface
A dose date is stored at UTC midnight. The health record PDF, the share page, the related-records list, the document link chips and both dose pickers formatted it as an instant in the reader's zone, so across the Americas each of them showed the day before the vaccination list did. The booster anchor added the interval to the stored instant, which put the first due day a day early there too. The report and share page print dose dates through the calendar-date formatter, the link surfaces through `useCalendarDate()`, and the booster counts its interval from the dose's date to the start of that day in the person's zone.
This commit is contained in:
1 parent
9afcfd4351
commit
9d7c99e60c
9 files changed
+147
-19
No files matched your search
@@ -519,6 +519,28 @@ describe("<ClinicianView>", () => {
|
||||
expect(html).toContain("05/14/1960");
|
||||
expect(html).not.toContain("05/13/1960");
|
||||
});
|
||||
|
||||
it("dates a vaccination on its own day west of UTC", () => {
|
||||
const html = render(
|
||||
makeReport({
|
||||
immunizations: [
|
||||
{
|
||||
// A dose date at UTC midnight: the evening before in New York.
|
||||
occurredAt: "2026-04-11T00:00:00.000Z",
|
||||
antigenSlug: null,
|
||||
vaccineName: "Influenza",
|
||||
lotNumber: null,
|
||||
site: null,
|
||||
practitionerName: null,
|
||||
series: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ timezone: "America/New_York", locale: "en" },
|
||||
);
|
||||
expect(html).toContain("04/11/2026");
|
||||
expect(html).not.toContain("04/10/2026");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -303,7 +303,7 @@ export function ClinicianView({
|
||||
t={t}
|
||||
report={report}
|
||||
scope={scope}
|
||||
fmtDate={fmtDate}
|
||||
fmtCalendarDate={fmtCalendarDate}
|
||||
/>
|
||||
<AllergiesSection t={t} report={report} scope={scope} />
|
||||
|
||||
|
||||
@@ -302,12 +302,13 @@ export function ImmunizationsSection({
|
||||
t,
|
||||
report,
|
||||
scope,
|
||||
fmtDate,
|
||||
fmtCalendarDate,
|
||||
}: {
|
||||
t: Translate;
|
||||
report: DoctorReportData;
|
||||
scope: LeafScope;
|
||||
fmtDate: (iso: string) => string;
|
||||
/** A dose date is a calendar date at UTC midnight, never an instant. */
|
||||
fmtCalendarDate: (iso: string) => string;
|
||||
}) {
|
||||
const doses = report.immunizations ?? [];
|
||||
|
||||
@@ -342,7 +343,7 @@ export function ImmunizationsSection({
|
||||
: (dose.vaccineName ?? "—")
|
||||
}
|
||||
value={compose([
|
||||
fmtDate(dose.occurredAt),
|
||||
fmtCalendarDate(dose.occurredAt),
|
||||
doseDisplay(dose.series),
|
||||
dose.lotNumber
|
||||
? `${t("doctorReport.immunizationsColLot")} ${dose.lotNumber}`
|
||||
|
||||
@@ -46,6 +46,7 @@ import { useVaccinations } from "@/components/vaccinations/use-vaccinations";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import { useCalendarDate } from "@/hooks/use-calendar-date";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import type { EncounterDTO, EncounterListDTO } from "@/lib/encounters/dto";
|
||||
import type { EncounterKind } from "@/generated/prisma/client";
|
||||
@@ -161,6 +162,7 @@ export function DocumentRecordLinks({
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const format = useFormatters();
|
||||
const calendarDate = useCalendarDate();
|
||||
const { user } = useAuth();
|
||||
const vaccinationsOn = user?.modules?.vaccinations !== false;
|
||||
const anchor = doc.documentDate ?? doc.reportDate;
|
||||
@@ -205,6 +207,7 @@ export function DocumentRecordLinks({
|
||||
anchor,
|
||||
labels,
|
||||
);
|
||||
// A dose date is a calendar date at UTC midnight, never an instant.
|
||||
const doseOptions = recordOptions(
|
||||
(vaccinations.data?.vaccinations ?? []).map((dose) => ({
|
||||
id: dose.id,
|
||||
@@ -213,7 +216,7 @@ export function DocumentRecordLinks({
|
||||
href: doseHref(dose.id),
|
||||
})),
|
||||
anchor,
|
||||
labels,
|
||||
{ ...labels, date: calendarDate },
|
||||
);
|
||||
|
||||
const showDoses = vaccinationsOn && doc.vaccinationLinks !== null;
|
||||
@@ -324,7 +327,7 @@ export function DocumentRecordLinks({
|
||||
</span>
|
||||
{link.occurredAt ? (
|
||||
<span className="text-muted-foreground shrink-0">
|
||||
{format.date(link.occurredAt)}
|
||||
{calendarDate(link.occurredAt)}
|
||||
</span>
|
||||
) : null}
|
||||
</Link>
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
type EntityLinkOption,
|
||||
} from "@/components/links/entity-link-picker";
|
||||
import { useVaultDocumentOptions } from "@/components/links/vault-document-options";
|
||||
import { useCalendarDate } from "@/hooks/use-calendar-date";
|
||||
import {
|
||||
pickerPivot,
|
||||
useLinkPickerPages,
|
||||
@@ -133,6 +134,7 @@ export function RelatedRecordsList({
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const format = useFormatters();
|
||||
const calendarDate = useCalendarDate();
|
||||
const rows = (related ?? []).filter((row) => !omitKinds.includes(row.kind));
|
||||
if (rows.length === 0) return null;
|
||||
return (
|
||||
@@ -142,7 +144,12 @@ export function RelatedRecordsList({
|
||||
const via = viaText(t, row.via);
|
||||
const meta = [
|
||||
kindText(t, row.kind),
|
||||
row.date ? format.date(row.date) : null,
|
||||
row.date
|
||||
? // A dose date is a calendar date at UTC midnight.
|
||||
row.kind === "vaccination"
|
||||
? calendarDate(row.date)
|
||||
: format.date(row.date)
|
||||
: null,
|
||||
via,
|
||||
]
|
||||
.filter(Boolean)
|
||||
@@ -381,7 +388,7 @@ function MedicationPicker({ selected, onChange, known }: PickerProps) {
|
||||
|
||||
function DosePicker({ selected, onChange, anchor, known }: PickerProps) {
|
||||
const { t } = useTranslations();
|
||||
const format = useFormatters();
|
||||
const calendarDate = useCalendarDate();
|
||||
const doses = useVaccinations(null, true);
|
||||
const options = recordOptions(
|
||||
(doses.data?.vaccinations ?? []).map((dose) => ({
|
||||
@@ -393,10 +400,7 @@ function DosePicker({ selected, onChange, anchor, known }: PickerProps) {
|
||||
href: `/vaccinations?dose=${encodeURIComponent(dose.id)}`,
|
||||
})),
|
||||
anchor,
|
||||
{
|
||||
suggested: t("links.picker.suggested"),
|
||||
date: (iso: string) => format.date(iso),
|
||||
},
|
||||
{ suggested: t("links.picker.suggested"), date: calendarDate },
|
||||
);
|
||||
return (
|
||||
<EntityLinkPicker
|
||||
|
||||
Reference in new issue
Block a user