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:
Marc-André Bombeck committed 2026-06-12 18:10:21 +02:00
1 parent d142a6ed31
commit e21dfd040e
31 files changed
+3382 -65

No files matched your search

+11
View File
@@ -246,6 +246,16 @@ export function invalidateUserInsightsLayout(userId: string): void {
caches.insightsLayout.deleteByPrefix(userId);
}
/**
* v1.16.10 — invalidate the medications list-presentation cache for a
* single user. Called from the medications-layout PUT / DELETE
* endpoints when the user toggles the view or saves a manual order so
* the next /medications mount paints the new presentation.
*/
export function invalidateUserMedicationListLayout(userId: string): void {
caches.medicationListLayout.deleteByPrefix(userId);
}
/**
* v1.16.9 — full per-user cache eviction for the wipe / restore paths.
* A data wipe or a backup restore replaces (or removes) every row the
@@ -265,6 +275,7 @@ export function invalidateUserData(userId: string): void {
caches.workouts.deleteByPrefix(`${userId}|`);
caches.dashboardWidgets.deleteByPrefix(userId);
caches.insightsLayout.deleteByPrefix(userId);
caches.medicationListLayout.deleteByPrefix(userId);
}
/**
+12
View File
@@ -610,6 +610,18 @@ export const caches = {
maxEntries: 500,
ttlMs: 300_000,
}),
/**
* v1.16.10 — per-user medications list presentation (view choice +
* manual order). Mirrors `insightsLayout` (same 5-minute TTL, same
* per-user bucket size). Read on every /medications mount; changes
* only on a view toggle / order save, which invalidates via
* `invalidateUserMedicationListLayout()`.
*/
medicationListLayout: new ServerCache<unknown>({
name: "medicationListLayout",
maxEntries: 500,
ttlMs: 300_000,
}),
} as const;
/**