mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
feat(labs): add biomarker filtering and sorting
This commit is contained in:
1 parent
22b85ff97d
commit
b4ac67ee7c
36 files changed
+3290
-172
No files matched your search
@@ -109,6 +109,17 @@ function FilterRail({ syncToUrl = true }: { syncToUrl?: boolean }) {
|
||||
options={SOURCES}
|
||||
/>
|
||||
</FilterBar>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
patchFilters(
|
||||
{ type: "PULSE" },
|
||||
{ mode: "replace", afterPaint: true },
|
||||
)
|
||||
}
|
||||
>
|
||||
Defer replace type
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -193,6 +204,25 @@ describe("useUrlFilterSync with FilterBar", () => {
|
||||
expect(window.history.length).toBe(entries);
|
||||
});
|
||||
|
||||
it("defers writing until after paint and replaces current entry without adding history entries", async () => {
|
||||
const { screen, history } = await mount("?type=WEIGHT");
|
||||
const entries = window.history.length;
|
||||
(
|
||||
screen.getByRole("button", { name: "Defer replace type" }).element() as HTMLButtonElement
|
||||
).click();
|
||||
|
||||
// Immediately after trigger, the write is deferred until after paint:
|
||||
// search remains unchanged and no history write has executed yet.
|
||||
expect(history.writes).toEqual([]);
|
||||
expect(currentSearch()).toBe("?type=WEIGHT");
|
||||
|
||||
// Once painted frames flush, the replace lands without extra history entry:
|
||||
await expectSearch("?type=PULSE");
|
||||
expect(history.pushes()).toEqual([]);
|
||||
expect(history.replaces()).toEqual(["?type=PULSE"]);
|
||||
expect(window.history.length).toBe(entries);
|
||||
});
|
||||
|
||||
it("drops one facet with its clear button and keeps the others", async () => {
|
||||
const { screen, pill } = await mount("?type=WEIGHT&source=MANUAL");
|
||||
await screen.getByRole("button", { name: "Clear filter: Type" }).click();
|
||||
|
||||
@@ -32,6 +32,13 @@ import { setAllParams, useUrlSearch, writeUrlState } from "@/lib/url-state";
|
||||
* for a list embedded under a URL that belongs to another surface (the
|
||||
* type-locked insights readings subpage).
|
||||
*/
|
||||
export interface UrlFilterSyncOptions {
|
||||
/** A new history entry (`push`) or the current one rewritten (`replace`). */
|
||||
mode?: "push" | "replace";
|
||||
/** Wait two painted frames before writing (a write made while mounting). */
|
||||
afterPaint?: boolean;
|
||||
}
|
||||
|
||||
export function useUrlFilterSync<T>({
|
||||
parse,
|
||||
serialise,
|
||||
@@ -44,10 +51,13 @@ export function useUrlFilterSync<T>({
|
||||
filters: T;
|
||||
applyFilters: (
|
||||
next: T | ((current: T) => T),
|
||||
mode?: "push" | "replace",
|
||||
modeOrOptions?: "push" | "replace" | UrlFilterSyncOptions,
|
||||
) => void;
|
||||
/** Merge `patch` into the live filters (the common facet change). */
|
||||
patchFilters: (patch: Partial<T>, mode?: "push" | "replace") => void;
|
||||
patchFilters: (
|
||||
patch: Partial<T>,
|
||||
modeOrOptions?: "push" | "replace" | UrlFilterSyncOptions,
|
||||
) => void;
|
||||
} {
|
||||
const params = useUrlSearch();
|
||||
|
||||
@@ -60,7 +70,14 @@ export function useUrlFilterSync<T>({
|
||||
);
|
||||
|
||||
const applyFilters = useCallback(
|
||||
(next: T | ((current: T) => T), mode: "push" | "replace" = "push") => {
|
||||
(
|
||||
next: T | ((current: T) => T),
|
||||
modeOrOptions: "push" | "replace" | UrlFilterSyncOptions = "push",
|
||||
) => {
|
||||
const options: UrlFilterSyncOptions =
|
||||
typeof modeOrOptions === "string"
|
||||
? { mode: modeOrOptions }
|
||||
: modeOrOptions;
|
||||
const resolve = (current: T): T =>
|
||||
typeof next === "function" ? (next as (c: T) => T)(current) : next;
|
||||
if (!syncToUrl) {
|
||||
@@ -72,15 +89,17 @@ export function useUrlFilterSync<T>({
|
||||
const search = serialise(resolve(parse(live)));
|
||||
setAllParams(search)(live);
|
||||
},
|
||||
{ mode },
|
||||
{ mode: options.mode ?? "push", afterPaint: options.afterPaint },
|
||||
);
|
||||
},
|
||||
[syncToUrl, serialise, parse],
|
||||
);
|
||||
|
||||
const patchFilters = useCallback(
|
||||
(patch: Partial<T>, mode: "push" | "replace" = "push") =>
|
||||
applyFilters((current) => ({ ...current, ...patch }), mode),
|
||||
(
|
||||
patch: Partial<T>,
|
||||
modeOrOptions: "push" | "replace" | UrlFilterSyncOptions = "push",
|
||||
) => applyFilters((current) => ({ ...current, ...patch }), modeOrOptions),
|
||||
[applyFilters],
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user