fix(charts): draw the BMI of the record on screen

Inside a shared or managed record the BMI chart drew nothing: it only
knew the viewer's height, and dividing someone else's weights by it
would have given a plausible wrong figure, so it divided by nothing.
The chart now takes the record's height from /api/insights/targets,
the cell the BMI page and the reference panel already read, through a
small useBmiDivisor hook; in the viewer's own record it keeps using the
account's height.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 21:43:07 +01:00
1 parent b85989f3df
commit 24cca975da
3 files changed
+97 -10

No files matched your search

@@ -0,0 +1,59 @@
/**
* The BMI chart divides the weights on screen by the height of the record on
* screen. It used to know only the viewer's height, so inside another
* person's record it drew nothing at all.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
const h = vi.hoisted(() => ({
inSharedRecord: false,
recordHeightCm: 120 as number | null,
enabled: [] as boolean[],
}));
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({ isAuthenticated: true, user: { heightCm: 180 } }),
}));
vi.mock("@/hooks/use-record-capabilities", () => ({
useRecordCapabilities: () => ({ inSharedRecord: h.inSharedRecord }),
}));
vi.mock("@tanstack/react-query", () => ({
useQuery: (options: { enabled: boolean }) => {
h.enabled.push(options.enabled);
return options.enabled
? { data: { profile: { heightCm: h.recordHeightCm } } }
: { data: undefined };
},
}));
import { useBmiDivisor } from "../health-chart/use-bmi-divisor";
describe("useBmiDivisor", () => {
beforeEach(() => {
h.inSharedRecord = false;
h.recordHeightCm = 120;
h.enabled.length = 0;
});
it("uses the viewer's own height in their own record", () => {
expect(useBmiDivisor("bmi")).toBeCloseTo(3.24);
expect(h.enabled).toEqual([false]);
});
it("uses the record's height inside somebody else's record", () => {
h.inSharedRecord = true;
expect(useBmiDivisor("bmi")).toBeCloseTo(1.44);
});
it("draws no BMI when the record has no height", () => {
h.inSharedRecord = true;
h.recordHeightCm = null;
expect(useBmiDivisor("bmi")).toBeNull();
});
it("asks for nothing outside the BMI view", () => {
h.inSharedRecord = true;
expect(useBmiDivisor("raw")).toBeNull();
expect(h.enabled).toEqual([false]);
});
});
+2 -10
View File
@@ -19,7 +19,6 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { useAuth } from "@/hooks/use-auth";
import { useChartOverlayPrefs } from "@/hooks/use-chart-overlay-prefs";
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
import { useViewportWidth } from "@/hooks/use-viewport-width";
import { axisTickUnitSuffix } from "@/lib/charts/axis-unit";
import { makeBucketLabelFormatters } from "@/lib/charts/bucket-label";
@@ -61,6 +60,7 @@ import {
useHealthChartLabels,
useHealthChartOverlays,
} from "./health-chart/use-health-chart-overlays";
import { useBmiDivisor } from "./health-chart/use-bmi-divisor";
import { useHealthChartSeries } from "./health-chart/use-health-chart-series";
import { useHealthChartView } from "./health-chart/use-health-chart-view";
import { resolveMiniRangePoints } from "./mini-window";
@@ -157,15 +157,7 @@ export function HealthChart({
// Viewport-aware tick density: 4 to 10 labels, whatever the point count.
const viewportWidth = useViewportWidth();
// `useAuth()` is the person at the keyboard, and under a switch that is the
// delegate: dividing the owner's weights by the delegate's height squared
// gives a plausible BMI that is somebody else's. Inside a shared record the
// chart draws no BMI rather than a wrong one.
const { inSharedRecord } = useRecordCapabilities();
const bmiDivisor =
valueMode === "bmi" && !inSharedRecord && user?.heightCm
? (user.heightCm / 100) ** 2
: null;
const bmiDivisor = useBmiDivisor(valueMode);
const { data, isLoading, isError, error, isPlaceholderData, refetch } =
useHealthChartSeries({
@@ -0,0 +1,36 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
import { apiGet } from "@/lib/api/api-fetch";
import { queryKeys } from "@/lib/query-keys";
/**
* The BMI view's divisor: the height of the record on screen, in metres,
* squared. `useAuth()` is the person at the keyboard, and under a switch that
* is the delegate, so inside somebody else's record the height comes from
* `/api/insights/targets`, the route that knows which record is on screen
* (the BMI page and the reference panel read the same cell). No height, no
* BMI.
*/
export function useBmiDivisor(
valueMode: "raw" | "bmi" | undefined,
): number | null {
const { isAuthenticated, user } = useAuth();
const { inSharedRecord } = useRecordCapabilities();
const { data: targets } = useQuery({
queryKey: queryKeys.insightsTargets(),
queryFn: () =>
apiGet<{ profile?: { heightCm: number | null } }>(
"/api/insights/targets",
),
enabled: isAuthenticated && valueMode === "bmi" && inSharedRecord,
});
if (valueMode !== "bmi") return null;
const heightCm = inSharedRecord
? (targets?.profile?.heightCm ?? null)
: (user?.heightCm ?? null);
return heightCm ? (heightCm / 100) ** 2 : null;
}