mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
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:
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]);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user