mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
feat(glucose): read glucose in one unit across series, CSV, and detail page
Glucose is stored canonical mg/dL but the series DTO, CSV export, and detail page emitted raw mg/dL while FHIR already converted to the user preference. Convert at serialization through the single convertGlucose helper: - series route sets unit from the user's glucoseUnit and converts each point - CSV export converts BLOOD_GLUCOSE rows and emits the unit in the unit column - the detail page + stat strip honour the preference via the chart value scale - declare the trailing-90-day window in the glucose median label across locales mmol/L-preference users now see the blood-glucose page, stat strip, iOS series, and CSV in mmol/L; mg/dL users are unchanged.
This commit is contained in:
1 parent
a2260d5bb8
commit
25944ed57a
17 files changed
+398
-26
No files matched your search
@@ -70,6 +70,37 @@ describe("<MetricStatStrip>", () => {
|
||||
expect(html).toBe("");
|
||||
});
|
||||
|
||||
it("renders the default 'Median' label when no override is given", () => {
|
||||
const html = render(<MetricStatStrip summary={populated} unit="bpm" />);
|
||||
expect(html).toContain("Median");
|
||||
});
|
||||
|
||||
// v1.16.16 — glucose declares the trailing-90-day p50 window in the label
|
||||
// so the median is not read as an all-time central value.
|
||||
it("renders an overridden median label when medianLabel is provided", () => {
|
||||
const html = render(
|
||||
<MetricStatStrip
|
||||
summary={populated}
|
||||
unit="mg/dL"
|
||||
medianLabel="Median glucose (90 days)"
|
||||
/>,
|
||||
);
|
||||
expect(html).toContain("Median glucose (90 days)");
|
||||
});
|
||||
|
||||
it("honours fractionDigits for integer-unit metrics (0 decimals)", () => {
|
||||
const html = render(
|
||||
<MetricStatStrip
|
||||
summary={{ ...populated, min: 100, max: 180, median: 126 }}
|
||||
unit="mg/dL"
|
||||
fractionDigits={0}
|
||||
/>,
|
||||
);
|
||||
// No "100.0" — integer precision.
|
||||
expect(html).toContain("100 mg/dL");
|
||||
expect(html).not.toContain("100.0");
|
||||
});
|
||||
|
||||
it("falls back to mean when median is absent (older payload)", () => {
|
||||
const noMedian: DataSummary = { ...populated, median: null };
|
||||
const html = render(<MetricStatStrip summary={noMedian} unit="bpm" />);
|
||||
|
||||
@@ -142,6 +142,18 @@ export interface HealthKitMetricPageProps {
|
||||
* "no trend to project yet" state rather than a line.
|
||||
*/
|
||||
forecast?: boolean;
|
||||
/**
|
||||
* v1.16.16 — decimal precision for the stat-strip values. Defaults to the
|
||||
* strip's own default (1). Blood glucose passes 0 for mg/dL (integer
|
||||
* readings) and 1 for mmol/L so the numbers and the unit agree.
|
||||
*/
|
||||
statFractionDigits?: number;
|
||||
/**
|
||||
* v1.16.16 — optional override for the stat strip's "Median" label. Blood
|
||||
* glucose passes a window- + context-declaring string so the trailing
|
||||
* 90-day p50 is not read as an all-time central value.
|
||||
*/
|
||||
statMedianLabel?: string;
|
||||
}
|
||||
|
||||
export function HealthKitMetricPage({
|
||||
@@ -162,6 +174,8 @@ export function HealthKitMetricPage({
|
||||
statusMetric,
|
||||
statIcon,
|
||||
forecast = false,
|
||||
statFractionDigits,
|
||||
statMedianLabel,
|
||||
}: HealthKitMetricPageProps) {
|
||||
const { user, isAuthenticated } = useAuth();
|
||||
const { t } = useTranslations();
|
||||
@@ -289,9 +303,11 @@ export function HealthKitMetricPage({
|
||||
<MetricStatStrip
|
||||
summary={summary}
|
||||
unit={yAxisUnit ?? unit}
|
||||
fractionDigits={statFractionDigits}
|
||||
seriesLabel={title}
|
||||
icon={statIcon}
|
||||
windowStats={statsByType?.[measurementType] ?? null}
|
||||
medianLabel={statMedianLabel}
|
||||
/>
|
||||
}
|
||||
diversityNudge={
|
||||
|
||||
@@ -92,6 +92,15 @@ export interface MetricStatSeries {
|
||||
* single-series mode omits it.
|
||||
*/
|
||||
dataKey?: string;
|
||||
/**
|
||||
* v1.16.16 — optional override for the "Median" cell label. The strip's
|
||||
* median is a trailing-90-day p50 (see `DataSummary.median` /
|
||||
* `summaries-slice.ts`); the default generic "Median" label leaves that
|
||||
* window undeclared. Blood glucose passes a window- + context-declaring
|
||||
* string (e.g. "Median glucose (90 days)") so the number is not read as an
|
||||
* all-time central value. Omit it to keep the generic label.
|
||||
*/
|
||||
medianLabel?: string;
|
||||
}
|
||||
|
||||
type MetricStatStripProps = Partial<MetricStatSeries> & {
|
||||
@@ -129,6 +138,7 @@ function SeriesBlock({
|
||||
icon,
|
||||
windowStats,
|
||||
dataKey,
|
||||
medianLabel,
|
||||
}: MetricStatSeries) {
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
@@ -154,7 +164,7 @@ function SeriesBlock({
|
||||
{ key: "max", label: t("insights.subPage.stats.max"), value: source.max },
|
||||
{
|
||||
key: "median",
|
||||
label: t("insights.subPage.stats.median"),
|
||||
label: medianLabel ?? t("insights.subPage.stats.median"),
|
||||
value: source.median,
|
||||
},
|
||||
{
|
||||
@@ -203,6 +213,7 @@ export function MetricStatStrip({
|
||||
icon,
|
||||
windowStats,
|
||||
dataKey,
|
||||
medianLabel,
|
||||
series,
|
||||
groupLabel,
|
||||
}: MetricStatStripProps) {
|
||||
@@ -264,6 +275,7 @@ export function MetricStatStrip({
|
||||
icon={icon}
|
||||
windowStats={windowStats}
|
||||
dataKey={dataKey}
|
||||
medianLabel={medianLabel}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in new issue
Block a user