feat(measurements): value-range filter on the management list

Add an optional numeric value-range filter to the measurement list:
readings whose `value` falls within `[valueMin, valueMax]`, either bound
omittable for an open-ended range. Server-authoritative — both bounds are
parsed on `listMeasurementsSchema` (coerced from the query string), an
inverted `valueMin > valueMax` returns 422, and the bounds thread into the
Prisma `where` as a `value: { gte, lte }` range on the raw per-sample list.

The filter rides a new `FilterBarNumberRange` pill that mirrors the existing
date-range control, hidden for the day-grouped and sleep-night types where
the painted value is a synthesised total rather than a stored reading.
This commit is contained in:
Marc-André Bombeck committed 2026-06-18 14:42:29 +02:00
1 parent 8920a35d38
commit cc89695cbd
14 files changed
+286 -4

No files matched your search

@@ -331,6 +331,68 @@ describe("measurement validation", () => {
});
});
describe("listMeasurementsSchema — v1.18.5 value-range filter", () => {
it("coerces valueMin / valueMax from query strings", () => {
const parsed = listMeasurementsSchema.safeParse({
valueMin: "80",
valueMax: "120",
});
expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data.valueMin).toBe(80);
expect(parsed.data.valueMax).toBe(120);
}
});
it("accepts an open-ended min-only range", () => {
const parsed = listMeasurementsSchema.safeParse({ valueMin: "100" });
expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data.valueMin).toBe(100);
expect(parsed.data.valueMax).toBeUndefined();
}
});
it("accepts an open-ended max-only range", () => {
const parsed = listMeasurementsSchema.safeParse({ valueMax: "90" });
expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data.valueMax).toBe(90);
expect(parsed.data.valueMin).toBeUndefined();
}
});
it("accepts valueMin == valueMax (exact-value band)", () => {
const parsed = listMeasurementsSchema.safeParse({
valueMin: "72",
valueMax: "72",
});
expect(parsed.success).toBe(true);
});
it("rejects an inverted range (valueMin > valueMax) with a 422-shaped issue", () => {
const parsed = listMeasurementsSchema.safeParse({
valueMin: "120",
valueMax: "80",
});
expect(parsed.success).toBe(false);
if (!parsed.success) {
expect(parsed.error.issues.some((i) => i.path[0] === "valueMin")).toBe(
true,
);
}
});
it("leaves both bounds undefined when omitted", () => {
const parsed = listMeasurementsSchema.safeParse({});
expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data.valueMin).toBeUndefined();
expect(parsed.data.valueMax).toBeUndefined();
}
});
});
// v1.4.38 — the per-day drill-down branch always returns at most
// 1000 rows (one pathological phone-only stepCount day). Push the
// cap into the validator so a caller asking for more sees a 422