mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
fix(visits): reach every lab result, condition, document and visit from the link pickers
The visit form's lab picker offered the newest 200 results, the condition picker the newest 100 conditions, the document pickers the newest thousand documents and the document sheet's visit picker the newest 100 visits. On a long record everything older could not be linked. Each picker now opens on the records around the date of the thing being linked, pages older and newer from there, and searches through the list route itself. Records already linked always show, from the labels the record's own read carries and from what was picked in the sheet. The lab list takes a q filter over analyte and panel, the condition list from/to/q/offset/sortDir, and the visit list an offset.
This commit is contained in:
1 parent
dad9a7778e
commit
73c4dda224
35 files changed
+1714
-251
No files matched your search
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* One key, one fetch. The visit form's link pickers read labs and illness
|
||||
* episodes with their own page size (200 results, 100 episodes), the Labs
|
||||
* list and the illness hook with theirs (500, the route's default 50). Under
|
||||
* a shared key whichever read landed first served the other, so the picker
|
||||
* could offer 50 episodes or the Labs list could render a 200-row page as
|
||||
* the whole list. The picker reads carry their own keys, still under the
|
||||
* domain roots so a lab or episode write evicts them.
|
||||
* episodes in their own windows and pages, the Labs list and the illness hook
|
||||
* with theirs. Under a shared key whichever read landed first served the
|
||||
* other, so the picker could offer 50 episodes or the Labs list could render
|
||||
* a picker page as the whole list. The picker reads carry their own keys,
|
||||
* still under the domain roots so a lab or episode write evicts them, and
|
||||
* each direction, split and term is its own key.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -22,20 +22,36 @@ const LIST_PARAMS = {
|
||||
|
||||
describe("the link pickers' reads have keys of their own", () => {
|
||||
it("labs: differs from the Labs list and sits under the lab root", () => {
|
||||
const key = queryKeys.labResultsPicker(200);
|
||||
const key = queryKeys.labResultsPicker("older", null, "");
|
||||
expect(key).not.toEqual(queryKeys.labResultsList(LIST_PARAMS));
|
||||
expect(key.slice(0, 1)).toEqual(queryKeys.labResults());
|
||||
});
|
||||
|
||||
it("episodes: differs from the illness list and sits under the illness root", () => {
|
||||
const key = queryKeys.illnessEpisodesPicker(100);
|
||||
const key = queryKeys.illnessEpisodesPicker("older", null, "");
|
||||
expect(key).not.toEqual(queryKeys.illnessEpisodes(true));
|
||||
expect(key.slice(0, 1)).toEqual(queryKeys.illness());
|
||||
});
|
||||
|
||||
it("a different page size is a different key", () => {
|
||||
expect(queryKeys.labResultsPicker(200)).not.toEqual(
|
||||
queryKeys.labResultsPicker(500),
|
||||
it("a different direction, split or term is a different key", () => {
|
||||
const base = queryKeys.labResultsPicker("older", "2019-05-17", "");
|
||||
expect(base).not.toEqual(
|
||||
queryKeys.labResultsPicker("newer", "2019-05-17", ""),
|
||||
);
|
||||
expect(base).not.toEqual(
|
||||
queryKeys.labResultsPicker("older", "2020-01-01", ""),
|
||||
);
|
||||
expect(queryKeys.labResultsPicker("search", null, "a")).not.toEqual(
|
||||
queryKeys.labResultsPicker("search", null, "b"),
|
||||
);
|
||||
});
|
||||
|
||||
it("documents and visits: under their roots", () => {
|
||||
expect(
|
||||
queryKeys.inboundDocumentVaultPicker("older", null, "").slice(0, 1),
|
||||
).toEqual(["documents"]);
|
||||
expect(queryKeys.encounterPicker("older", null, "").slice(0, 1)).toEqual(
|
||||
queryKeys.encounters(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -61,12 +61,24 @@ export const documentKeys = {
|
||||
inboundDocumentPicker: (q: string) =>
|
||||
["documents", "inbound", "picker", q] as const,
|
||||
/**
|
||||
* The whole vault as a link-picker option list (every page, walked at the
|
||||
* list route's ceiling), shared by the visit and vaccination forms. Own
|
||||
* segment because the shape is a flat document array, not a page.
|
||||
* The vault as a link-picker option list, shared by the visit and
|
||||
* vaccination forms: one paged direction from the record's date or a
|
||||
* search. Own segment because the page shape is the picker's, not the
|
||||
* library's.
|
||||
*/
|
||||
inboundDocumentVaultPicker: () =>
|
||||
["documents", "inbound", "vault-picker"] as const,
|
||||
inboundDocumentVaultPicker: (
|
||||
direction: string,
|
||||
pivot: string | null,
|
||||
term: string,
|
||||
) =>
|
||||
[
|
||||
"documents",
|
||||
"inbound",
|
||||
"vault-picker",
|
||||
direction,
|
||||
pivot ?? "",
|
||||
term,
|
||||
] as const,
|
||||
/**
|
||||
* Storage usage + effective limits (`GET /api/documents/inbound/usage`).
|
||||
* Invalidated through the `["documents"]` prefix after every upload /
|
||||
|
||||
@@ -45,6 +45,13 @@ export const encounterKeys = {
|
||||
status ?? null,
|
||||
episodeId ?? null,
|
||||
] as const,
|
||||
/**
|
||||
* The document sheet's visit picker: one paged direction from the
|
||||
* document's date. Its own key because its page shape (offset steps inside
|
||||
* a window) is not the list's.
|
||||
*/
|
||||
encounterPicker: (direction: string, pivot: string | null, term: string) =>
|
||||
["encounters", "picker", direction, pivot ?? "", term] as const,
|
||||
encounter: (id: string) => ["encounters", "detail", id] as const,
|
||||
/**
|
||||
* The procedure history for one filter. Under the `["encounters"]` root, so
|
||||
|
||||
@@ -15,10 +15,14 @@ export const illnessKeys = {
|
||||
illness: () => ["illness"] as const,
|
||||
illnessEpisodes: (includeResolved: boolean) =>
|
||||
["illness", "episodes", includeResolved] as const,
|
||||
// The visit form's episode picker, with its own page size (the illness
|
||||
// pages read the route's default); a shared key would serve one to the other.
|
||||
illnessEpisodesPicker: (limit: number) =>
|
||||
["illness", "episodes-picker", limit] as const,
|
||||
// The visit form's episode picker: one paged direction from the visit's
|
||||
// date or a search, with its own page shape; a shared key would serve one
|
||||
// read to the other.
|
||||
illnessEpisodesPicker: (
|
||||
direction: string,
|
||||
pivot: string | null,
|
||||
term: string,
|
||||
) => ["illness", "episodes-picker", direction, pivot ?? "", term] as const,
|
||||
illnessEpisode: (id: string) => ["illness", "episode", id] as const,
|
||||
illnessDayLog: (episodeId: string, date: string) =>
|
||||
["illness", "day-log", episodeId, date] as const,
|
||||
|
||||
@@ -36,11 +36,12 @@ export const labKeys = {
|
||||
labAnalyteLink: (analyte: string) =>
|
||||
["lab-results", "analyte-link", analyte] as const,
|
||||
|
||||
// The visit form's lab picker: newest results, its own page size. Its own
|
||||
// key because the Labs list reads a different page size, and a shared key
|
||||
// would serve one read's page to the other.
|
||||
labResultsPicker: (limit: number) =>
|
||||
["lab-results", "picker", limit] as const,
|
||||
// The visit form's lab picker: one paged direction from the visit's date
|
||||
// (`older` / `newer`) or a search across the whole history. Its own key
|
||||
// because the Labs list reads other pages, and a shared key would serve
|
||||
// one read's page to the other.
|
||||
labResultsPicker: (direction: string, pivot: string | null, term: string) =>
|
||||
["lab-results", "picker", direction, pivot ?? "", term] as const,
|
||||
|
||||
// v1.25 — paginated (offset) reading feed for the biomarker detail page.
|
||||
// Distinct from `labResultsList` (single-page key) so the infinite-query
|
||||
|
||||
@@ -164,6 +164,11 @@ export const encounterListQuerySchema = z
|
||||
*/
|
||||
episodeId: id.optional(),
|
||||
limit: z.coerce.number().int().min(1).max(200).optional(),
|
||||
/**
|
||||
* Rows to skip in each arm, for a picker that pages a long history in
|
||||
* `limit`-sized steps inside a date window.
|
||||
*/
|
||||
offset: z.coerce.number().int().min(0).max(100_000).optional(),
|
||||
})
|
||||
.strict()
|
||||
.refine((q) => !(q.from && q.to) || q.from <= q.to, {
|
||||
|
||||
@@ -136,6 +136,17 @@ export type IllnessEpisodeResolve = z.infer<typeof illnessEpisodeResolveSchema>;
|
||||
export const illnessEpisodeListQuerySchema = z.object({
|
||||
limit: z.coerce.number().int().min(1).max(100).optional(),
|
||||
includeResolved: z.union([z.literal("true"), z.literal("false")]).optional(),
|
||||
/**
|
||||
* Paging and a date window over the onset, for a picker that opens on the
|
||||
* episodes around a visit and pages older or newer from there.
|
||||
*/
|
||||
offset: z.coerce.number().int().min(0).max(100_000).optional(),
|
||||
from: z.iso.datetime({ offset: true }).optional(),
|
||||
to: z.iso.datetime({ offset: true }).optional(),
|
||||
/** Onset order; newest first by default. */
|
||||
sortDir: z.enum(["asc", "desc"]).optional(),
|
||||
/** Case-insensitive substring over the episode's label. */
|
||||
q: z.string().trim().min(1).max(120).optional(),
|
||||
});
|
||||
|
||||
/* ── day-log capture (one row per day of an episode) ─────────────── */
|
||||
|
||||
@@ -284,6 +284,11 @@ export const listLabResultsSchema = z.object({
|
||||
biomarkerId: z.string().trim().min(1).max(64).optional(),
|
||||
analyte: z.string().trim().min(1).max(120).optional(),
|
||||
panel: z.string().trim().min(1).max(120).optional(),
|
||||
/**
|
||||
* Case-insensitive substring over the analyte and the panel name, so a
|
||||
* picker can find one result in a long history without paging to it.
|
||||
*/
|
||||
q: z.string().trim().min(1).max(120).optional(),
|
||||
from: z
|
||||
.string()
|
||||
.datetime()
|
||||
|
||||
Reference in new issue
Block a user