mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
Merge remote-tracking branch 'origin/release/v1.47.2' into takeover/1370
# Conflicts: # CHANGELOG.md # src/lib/module-list-prefs.ts
This commit is contained in:
commit
a486c3c8a3
148 files changed
+4668
-347
No files matched your search
@@ -179,6 +179,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -226,6 +227,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -322,6 +324,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -378,6 +381,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
saved: false,
|
||||
},
|
||||
illness: {
|
||||
@@ -386,6 +390,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
saved: false,
|
||||
},
|
||||
vorsorge: {
|
||||
@@ -394,6 +399,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
saved: false,
|
||||
},
|
||||
},
|
||||
@@ -424,6 +430,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "manual",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
saved: true,
|
||||
},
|
||||
illness: {
|
||||
@@ -432,6 +439,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
saved: false,
|
||||
},
|
||||
vorsorge: {
|
||||
@@ -440,6 +448,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
saved: false,
|
||||
},
|
||||
},
|
||||
@@ -496,6 +505,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "category",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -533,6 +543,7 @@ describe("LabList stale category and group header handling", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "category",
|
||||
groupsOpen: "flagged",
|
||||
categoryOrder: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,6 +48,7 @@ const mockModulePrefs: ModuleListPrefs = {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
};
|
||||
|
||||
vi.mock("@/lib/module-list-prefs", async (importOriginal) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ let mockModulePrefs: ModuleListPrefs = {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
};
|
||||
|
||||
const displayZone = vi.hoisted(() => ({ current: "UTC" }));
|
||||
@@ -85,6 +86,7 @@ describe("<LabList>", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -184,6 +186,7 @@ describe("<LabList>", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "category",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
@@ -257,6 +260,7 @@ describe("<LabList>", () => {
|
||||
sortDir: "recentDesc",
|
||||
groupBy: "none",
|
||||
groupsOpen: "expanded",
|
||||
categoryOrder: [],
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
|
||||
@@ -28,6 +28,21 @@ function render(prefs: Partial<ModuleListPrefs>) {
|
||||
biomarkers: [],
|
||||
customPanels: [],
|
||||
});
|
||||
// The server's answer, already in the person's order.
|
||||
queryClient.setQueryData(queryKeys.labResultsOverview("en"), {
|
||||
markers: [],
|
||||
grouped: true,
|
||||
categories: [
|
||||
{ key: "thyroid", label: "Thyroid" },
|
||||
{ key: "other", label: "Other" },
|
||||
{ key: "lipids", label: "Lipids" },
|
||||
].map((category) => ({
|
||||
...category,
|
||||
markerKeys: [],
|
||||
counts: { inRange: 0, below: 0, above: 0, unknown: 0 },
|
||||
status: "unknown",
|
||||
})),
|
||||
});
|
||||
return renderToStaticMarkup(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<I18nProvider initialLocale="en">
|
||||
@@ -60,4 +75,26 @@ describe("<LabsSettings> categories", () => {
|
||||
"The order applies within each category.",
|
||||
);
|
||||
});
|
||||
|
||||
it("offers the category order only while categories are on", () => {
|
||||
expect(render({})).not.toContain('id="labs-category-order"');
|
||||
expect(render({ groupBy: "category" })).toContain(
|
||||
'id="labs-category-order"',
|
||||
);
|
||||
});
|
||||
|
||||
it("lists the categories as the server ordered them, under their labels", () => {
|
||||
const html = render({ groupBy: "category" });
|
||||
const card = html.slice(html.indexOf('id="labs-category-order"'));
|
||||
const at = (label: string) => card.indexOf(`title="${label}"`);
|
||||
expect(at("Thyroid")).toBeGreaterThan(-1);
|
||||
expect(at("Thyroid")).toBeLessThan(at("Other"));
|
||||
expect(at("Other")).toBeLessThan(at("Lipids"));
|
||||
});
|
||||
|
||||
it("says how the categories are ordered until the person moves one", () => {
|
||||
expect(render({ groupBy: "category" })).toContain(
|
||||
"Until you move one, built-in categories keep a fixed order, your own follow alphabetically and Other comes last.",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -5,8 +5,8 @@
|
||||
*
|
||||
* `LabsSettings` — Settings > Arrangement > Labs: the card-vs-list view
|
||||
* (MOD-03), the sort (recency, alphabetical or manual, MOD-04) and the manual
|
||||
* order of the biomarkers, and whether the list shows its categories and
|
||||
* which of them start open. Persisted per account through
|
||||
* order of the biomarkers, and whether the list shows its categories, which
|
||||
* of them start open and their order. Persisted per account through
|
||||
* `useModuleListPrefs("labs")`.
|
||||
*
|
||||
* `LabsModuleSettings` — Settings > Modules > Labs: the on-device text
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ArrowDownUp,
|
||||
FlaskConical,
|
||||
FolderTree,
|
||||
Layers,
|
||||
LayoutGrid,
|
||||
ListOrdered,
|
||||
ScanText,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { NativeSelect } from "@/components/ui/native-select";
|
||||
import { QueryErrorRow } from "@/components/ui/query-error-row";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Segmented } from "@/components/ui/segmented";
|
||||
@@ -47,16 +49,28 @@ import {
|
||||
type ModuleGroupsOpen,
|
||||
type ModuleSortDir,
|
||||
} from "@/lib/module-list-prefs";
|
||||
import { mergeCategoryOrder } from "@/lib/settings/module-list-layout";
|
||||
|
||||
import { BiomarkerManager } from "./biomarker-manager";
|
||||
import type { BiomarkerDto, BiomarkerListResponse } from "./types";
|
||||
import type {
|
||||
BiomarkerDto,
|
||||
BiomarkerListResponse,
|
||||
LabOverviewResponse,
|
||||
} from "./types";
|
||||
import { useLabsLocalOcr, useUpdateLabsLocalOcr } from "./use-ocr-extract";
|
||||
import { labUnitLabel } from "@/lib/labs/dimensionless";
|
||||
|
||||
export function LabsSettings() {
|
||||
const { t } = useTranslations();
|
||||
const { prefs, setView, setOrder, setSortDir, setGroupBy, setGroupsOpen } =
|
||||
useModuleListPrefs("labs");
|
||||
const {
|
||||
prefs,
|
||||
setView,
|
||||
setOrder,
|
||||
setSortDir,
|
||||
setGroupBy,
|
||||
setGroupsOpen,
|
||||
setCategoryOrder,
|
||||
} = useModuleListPrefs("labs");
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: queryKeys.biomarkers(),
|
||||
@@ -120,6 +134,14 @@ export function LabsSettings() {
|
||||
onGroupsOpen={setGroupsOpen}
|
||||
/>
|
||||
|
||||
{prefs.groupBy === "category" ? (
|
||||
<LabsCategoryOrderCard
|
||||
onChange={(shown) =>
|
||||
setCategoryOrder(mergeCategoryOrder(shown, prefs.categoryOrder))
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{manual ? (
|
||||
<SettingsCard id="labs-order" className="scroll-mt-28">
|
||||
<SettingsCardHeader
|
||||
@@ -204,6 +226,56 @@ function LabsGroupingCard({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The order of the categories. The server answers them already in the
|
||||
* person's order (`GET /api/labs/overview` applies `categoryOrder`), so the
|
||||
* editor lists them as given, under the labels the overview carries, and
|
||||
* sends the whole order after every move.
|
||||
*/
|
||||
function LabsCategoryOrderCard({
|
||||
onChange,
|
||||
}: {
|
||||
onChange: (categoryOrder: string[]) => void;
|
||||
}) {
|
||||
const { t, locale } = useTranslations();
|
||||
const { data, isLoading, isError, error, refetch } = useQuery({
|
||||
queryKey: queryKeys.labResultsOverview(locale),
|
||||
queryFn: () => apiGet<LabOverviewResponse>("/api/labs/overview"),
|
||||
});
|
||||
const items: ReorderItem[] = (data?.categories ?? []).map((category) => ({
|
||||
id: category.key,
|
||||
name: category.label,
|
||||
}));
|
||||
|
||||
return (
|
||||
<SettingsCard id="labs-category-order" className="scroll-mt-28">
|
||||
<SettingsCardHeader
|
||||
icon={Layers}
|
||||
title={t("labs.groups.orderHeading")}
|
||||
description={t("labs.groups.orderDefault", {
|
||||
other: t("labs.groups.other"),
|
||||
})}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<div className="space-y-2">
|
||||
{Array.from({ length: 3 }, (_, i) => (
|
||||
<Skeleton key={i} className="h-12 w-full" />
|
||||
))}
|
||||
</div>
|
||||
) : isError ? (
|
||||
<QueryErrorRow
|
||||
error={error}
|
||||
inset
|
||||
slot="labs-category-order-error"
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
) : (
|
||||
<ModuleOrderEditor items={items} onChange={onChange} />
|
||||
)}
|
||||
</SettingsCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function LabsModuleSettings() {
|
||||
const { t } = useTranslations();
|
||||
// v1.18.10 — local (in-browser) OCR opt-in. Lets a user whose AI provider
|
||||
|
||||
Reference in new issue
Block a user