mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(medications): list customization moves into its own settings section
/settings/medications now hosts the view preference (cards or table) and the manual-order editor inline, following the dashboard and insights pattern. The page header's gear icon links there instead of opening a dialog; the quick view toggle stays next to the add button. The order editor keeps its grouped active/inactive semantics — a saved order can never interleave the groups.
This commit is contained in:
1 parent
5142823b7e
commit
02642a964e
17 files changed
+441
-180
No files matched your search
@@ -33,9 +33,6 @@ vi.mock("@/components/medications/wizard/MedicationWizardDialog", () => ({
|
||||
vi.mock("@/components/medications/log-intake-dialog", () => ({
|
||||
LogIntakeDialog: () => null,
|
||||
}));
|
||||
vi.mock("@/components/medications/medication-reorder-dialog", () => ({
|
||||
MedicationReorderDialog: () => null,
|
||||
}));
|
||||
|
||||
import MedicationsPage from "@/app/medications/page";
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
@@ -133,9 +130,15 @@ describe("/medications — view renders from the persisted preference", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("offers the manual-order editor from the header", () => {
|
||||
it("links the header customize glyph to /settings/medications", () => {
|
||||
// v1.16.10 — same entry-point logic as the dashboard and insights
|
||||
// headers: the Settings2 ghost icon is a plain link to the page's
|
||||
// settings section (view preference + manual order live there).
|
||||
const html = renderPage({ version: 1, view: "cards", order: [] });
|
||||
expect(html).toContain('aria-label="Adjust order"');
|
||||
expect(html).toContain('href="/settings/medications"');
|
||||
expect(html).toContain('aria-label="Customize medications"');
|
||||
// The in-page order dialog is gone — no dialog trigger remains.
|
||||
expect(html).not.toContain('aria-label="Adjust order"');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
MedicationTableSkeleton,
|
||||
} from "@/components/medications/medication-table";
|
||||
import { MedicationViewToggle } from "@/components/medications/medication-view-toggle";
|
||||
import { MedicationReorderDialog } from "@/components/medications/medication-reorder-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
@@ -149,16 +149,13 @@ export default function MedicationsPage() {
|
||||
// two paths: log an intake (incl. a backdated one) against an existing
|
||||
// medication, or create a new medication (the existing wizard).
|
||||
const [logIntakeOpen, setLogIntakeOpen] = useState(false);
|
||||
// v1.16.10 — manual-order editor (shared by both views).
|
||||
const [reorderOpen, setReorderOpen] = useState(false);
|
||||
|
||||
// v1.16.10 — the persisted list presentation: cards vs table plus the
|
||||
// manual medication order, server-side per user
|
||||
// (`GET`/`PUT /api/medications/layout`). The toggle writes
|
||||
// optimistically; the reorder dialog saves explicitly.
|
||||
const { layout, isLayoutLoading, setView } = useMedicationListLayout(
|
||||
isAuthenticated,
|
||||
);
|
||||
// optimistically; the order editor lives at /settings/medications.
|
||||
const { layout, isLayoutLoading, setView } =
|
||||
useMedicationListLayout(isAuthenticated);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldOpenFromUrl) {
|
||||
@@ -268,25 +265,27 @@ export default function MedicationsPage() {
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{/* v1.16.10 — card ⇄ table view toggle, persisted server-side
|
||||
per user; plus the manual-order editor shared by both
|
||||
views. The editor opens behind the same Settings2 glyph
|
||||
the dashboard and insights headers use for their
|
||||
customize entry points, in the same slot (left of the
|
||||
add button). It only earns its slot once there is
|
||||
something to reorder. */}
|
||||
per user. The Settings2 glyph next to it is the customize
|
||||
shortcut to /settings/medications (view preference + the
|
||||
manual-order editor), exactly like the dashboard and
|
||||
insights headers link to their settings sections, in the
|
||||
same slot (left of the add button) and with the same
|
||||
responsive 44-px mobile tap floor. */}
|
||||
<MedicationViewToggle view={layout.view} onChange={setView} />
|
||||
{medsArray.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => setReorderOpen(true)}
|
||||
aria-label={t("medications.reorderTitle")}
|
||||
title={t("medications.reorderTitle")}
|
||||
<Button
|
||||
asChild
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9"
|
||||
>
|
||||
<Link
|
||||
href="/settings/medications"
|
||||
aria-label={t("medications.customize")}
|
||||
title={t("medications.customize")}
|
||||
>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Settings2 className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</Button>
|
||||
{/* v1.14.0 — the "Add" button is now a choice: log an intake
|
||||
(incl. a backdated one) against an existing medication, or
|
||||
create a new medication. v1.12.2 — match the dashboard Add
|
||||
@@ -324,11 +323,7 @@ export default function MedicationsPage() {
|
||||
// persisted view (table rows vs card grid) so resolving the data
|
||||
// never swaps the footprint; while the view preference itself is
|
||||
// still loading the default cards skeleton stands in.
|
||||
<div
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label={t("medications.title")}
|
||||
>
|
||||
<div role="status" aria-busy="true" aria-label={t("medications.title")}>
|
||||
{tableView && !isLayoutLoading ? (
|
||||
<MedicationTableSkeleton />
|
||||
) : (
|
||||
@@ -462,22 +457,6 @@ export default function MedicationsPage() {
|
||||
date+time that supports backdating. Submits to the existing
|
||||
per-medication intake route (same slot upsert as a live "Taken"
|
||||
tap) so the one-row-per-dose-slot invariant holds. */}
|
||||
{/* v1.16.10 — manual-order editor. The list is the page's current
|
||||
effective order (active block first, inactive after), so the
|
||||
dialog opens showing exactly what both views render. */}
|
||||
{reorderOpen && (
|
||||
<MedicationReorderDialog
|
||||
open={reorderOpen}
|
||||
onOpenChange={setReorderOpen}
|
||||
medications={[...activeMeds, ...inactiveMeds].map((m) => ({
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
dose: m.dose,
|
||||
active: m.active,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{logIntakeOpen && (
|
||||
<LogIntakeDialog
|
||||
open={logIntakeOpen}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DashboardSection } from "@/components/settings/dashboard-section";
|
||||
import { ExportSection } from "@/components/settings/export-section";
|
||||
import { InsightsSection } from "@/components/settings/insights-section";
|
||||
import { IntegrationsSection } from "@/components/settings/integrations-section";
|
||||
import { MedicationsSection } from "@/components/settings/medications-section";
|
||||
import { NotificationsSection } from "@/components/settings/notifications-section";
|
||||
import { SectionPlaceholder } from "@/components/settings/section-placeholder";
|
||||
// v1.8.7.1 — `thresholds` (Targets) and `sources` (Sources) are two
|
||||
@@ -51,6 +52,7 @@ const SECTION_COMPONENTS: Record<
|
||||
notifications: NotificationsSection,
|
||||
dashboard: DashboardSection,
|
||||
insights: InsightsSection,
|
||||
medications: MedicationsSection,
|
||||
thresholds: ThresholdsSection,
|
||||
sources: () => <SourcesSection />,
|
||||
api: ApiSection,
|
||||
|
||||
Reference in new issue
Block a user