mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(medications): a sortable table view with an order you choose
The medications page gains a compact table beside the card view — one row per medication with the same served-due status pill, next-intake line, cadence-scaled adherence bars, dose-derived stock and the take and skip actions the cards use, all from the payloads the cards already read; the list response aggregates stock in the same round trip. Columns sort tri-state with nulls last, the default order is yours to arrange — the same drag dialog the dashboard and insights use, applied to both views — and the chosen view and order persist per user on their own settings column with field-scoped writes, so toggling the view never touches the order. Inactive medications pin to the end, the first column stays put under horizontal scroll, and the loading skeleton follows the view you left.
This commit is contained in:
1 parent
d142a6ed31
commit
e21dfd040e
31 files changed
+3382
-65
No files matched your search
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* v1.16.10 — medications list presentation blob (resolver + serializer).
|
||||
*
|
||||
* The resolver must never throw on a stored row (malformed / legacy /
|
||||
* partial blobs collapse onto the defaults field-by-field); the
|
||||
* serializer dedupes and caps the manual order so the persisted blob
|
||||
* stays bounded and canonical.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
DEFAULT_MEDICATION_LIST_LAYOUT,
|
||||
MEDICATION_ORDER_MAX_ENTRIES,
|
||||
resolveMedicationListLayout,
|
||||
serializeMedicationListLayout,
|
||||
} from "@/lib/medication-list-layout";
|
||||
|
||||
describe("resolveMedicationListLayout", () => {
|
||||
it("returns the defaults for a null / missing column", () => {
|
||||
expect(resolveMedicationListLayout(null)).toEqual(
|
||||
DEFAULT_MEDICATION_LIST_LAYOUT,
|
||||
);
|
||||
expect(resolveMedicationListLayout(undefined)).toEqual(
|
||||
DEFAULT_MEDICATION_LIST_LAYOUT,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the defaults for a non-object blob", () => {
|
||||
expect(resolveMedicationListLayout("cards")).toEqual(
|
||||
DEFAULT_MEDICATION_LIST_LAYOUT,
|
||||
);
|
||||
expect(resolveMedicationListLayout(42)).toEqual(
|
||||
DEFAULT_MEDICATION_LIST_LAYOUT,
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves a complete stored blob verbatim", () => {
|
||||
expect(
|
||||
resolveMedicationListLayout({
|
||||
version: 1,
|
||||
view: "table",
|
||||
order: ["med-b", "med-a"],
|
||||
}),
|
||||
).toEqual({ version: 1, view: "table", order: ["med-b", "med-a"] });
|
||||
});
|
||||
|
||||
it("falls back per field: unknown view → cards, malformed order → empty", () => {
|
||||
expect(
|
||||
resolveMedicationListLayout({ view: "kanban", order: "med-a" }),
|
||||
).toEqual({ version: 1, view: "cards", order: [] });
|
||||
});
|
||||
|
||||
it("drops non-string and oversized order entries, keeping the rest", () => {
|
||||
const resolved = resolveMedicationListLayout({
|
||||
view: "table",
|
||||
order: ["med-a", 7, null, "x".repeat(65), "med-b"],
|
||||
});
|
||||
expect(resolved.order).toEqual(["med-a", "med-b"]);
|
||||
});
|
||||
|
||||
it("dedupes a stored order (first occurrence wins)", () => {
|
||||
const resolved = resolveMedicationListLayout({
|
||||
view: "cards",
|
||||
order: ["med-a", "med-b", "med-a"],
|
||||
});
|
||||
expect(resolved.order).toEqual(["med-a", "med-b"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("serializeMedicationListLayout", () => {
|
||||
it("emits the canonical v1 blob", () => {
|
||||
expect(
|
||||
serializeMedicationListLayout({ view: "table", order: ["med-a"] }),
|
||||
).toEqual({ version: 1, view: "table", order: ["med-a"] });
|
||||
});
|
||||
|
||||
it("dedupes the order before persisting", () => {
|
||||
expect(
|
||||
serializeMedicationListLayout({
|
||||
view: "cards",
|
||||
order: ["a", "b", "a", "c", "b"],
|
||||
}).order,
|
||||
).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
|
||||
it("caps the order at the documented maximum", () => {
|
||||
const order = Array.from({ length: 500 }, (_, i) => `med-${i}`);
|
||||
expect(
|
||||
serializeMedicationListLayout({ view: "cards", order }).order.length,
|
||||
).toBe(MEDICATION_ORDER_MAX_ENTRIES);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user