mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
refactor(visits): build every link to a visit in one place
Seven surfaces spelled `/checkups?visit=<id>` by hand, some encoding the id and some not. They now share `visitPath()` from `src/lib/encounters/visit-links.ts`, the place the next fix extends. No behaviour change: a cuid encodes to itself.
This commit is contained in:
1 parent
421aacf457
commit
78bacb0d86
9 files changed
+41
-7
No files matched your search
@@ -50,6 +50,7 @@ import { queryKeys } from "@/lib/query-keys";
|
||||
import type { EncounterDTO, EncounterListDTO } from "@/lib/encounters/dto";
|
||||
import type { EncounterKind } from "@/generated/prisma/client";
|
||||
import type { InboundDocumentDetailDto } from "@/lib/validations/inbound-documents";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
type Translate = ReturnType<typeof useTranslations>["t"];
|
||||
|
||||
@@ -109,7 +110,7 @@ export async function fetchEncounterPage(
|
||||
|
||||
/** Where a visit opens: its sheet on the checkups page. */
|
||||
function visitHref(encounterId: string): string {
|
||||
return `/checkups?visit=${encodeURIComponent(encounterId)}`;
|
||||
return visitPath(encounterId);
|
||||
}
|
||||
|
||||
/** Where a dose opens: its sheet on the vaccinations page. */
|
||||
|
||||
@@ -64,6 +64,7 @@ import type { IllnessEpisodeDTO } from "@/lib/illness/dto";
|
||||
import type { RelatedLink } from "@/lib/links/related";
|
||||
import type { EncounterKind } from "@/generated/prisma/client";
|
||||
import { translateOrNull } from "@/lib/i18n/translate-code";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
type Translate = ReturnType<typeof useTranslations>["t"];
|
||||
type RelatedKind = RelatedLink["kind"];
|
||||
@@ -311,7 +312,7 @@ function VisitPicker({ selected, onChange, anchor, known }: PickerProps) {
|
||||
label: encounterKindText(t, visit.kind as EncounterKind),
|
||||
meta: visit.practitioner?.name ?? null,
|
||||
occurredAt: visit.occurredAt,
|
||||
href: `/checkups?visit=${encodeURIComponent(visit.id)}`,
|
||||
href: visitPath(visit.id),
|
||||
})),
|
||||
anchor,
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ import { resolveCycleDay } from "@/lib/cycle/verdict";
|
||||
import { effectiveMoodTz } from "@/lib/mood/date-key";
|
||||
import { dateOnlyKey, dayKeyAsUtcMidnight } from "@/lib/tz/date-only";
|
||||
import { daysBetweenDateKeys, shiftDateKey, userDayKey } from "@/lib/tz/format";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
/** Everything a section reader needs about the day. */
|
||||
export interface DayFrame {
|
||||
@@ -630,7 +631,7 @@ async function visitsPart(frame: DayFrame): Promise<SectionPart> {
|
||||
meta: row.status === "PLANNED" ? "PLANNED" : row.kind,
|
||||
note: openText(row.outcomeEncrypted, "visit outcome"),
|
||||
docs: docs.get(row.id) ?? [],
|
||||
href: `/checkups?visit=${row.id}`,
|
||||
href: visitPath(row.id),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ import { loadUserSourcePriority } from "@/lib/rollups/measurement-read";
|
||||
import { dateOnlyKey, dayKeyAsUtcMidnight } from "@/lib/tz/date-only";
|
||||
import { daysBetweenDateKeys, shiftDateKey, userDayKey } from "@/lib/tz/format";
|
||||
import { startOfLocalDayKey } from "@/lib/tz/local-day";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
/** The window when the record holds no visit the caller may see. */
|
||||
export const FALLBACK_WINDOW_DAYS = 90;
|
||||
@@ -275,7 +276,7 @@ async function procedureChanges(w: Window): Promise<DayChange[]> {
|
||||
kind: "procedure",
|
||||
id: row.id,
|
||||
title: openText(row.reasonEncrypted, "procedure reason") ?? "",
|
||||
href: `/checkups?visit=${row.id}`,
|
||||
href: visitPath(row.id),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { visitPath } from "../visit-links";
|
||||
|
||||
describe("visit links", () => {
|
||||
it("opens one visit on the visits tab, never the bare checkups page", () => {
|
||||
expect(visitPath("v1")).toBe("/checkups?visit=v1");
|
||||
expect(visitPath("a b&c")).toBe("/checkups?visit=a%20b%26c");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Where a visit lives in the app, spelled once.
|
||||
*
|
||||
* Visits have no page of their own: they are the second tab of `/checkups`,
|
||||
* beside the preventive-care list that opens by default. The checkups page
|
||||
* opens the visit a `visit` parameter names on the visits tab
|
||||
* (`src/hooks/use-deep-linked-record.ts`).
|
||||
*
|
||||
* Every link to one visit is built here.
|
||||
*
|
||||
* Pure and client-safe: no server imports.
|
||||
*/
|
||||
|
||||
/** One visit: the visits tab, scrolled to the visit and opened. */
|
||||
export function visitPath(id: string): string {
|
||||
return `/checkups?visit=${encodeURIComponent(id)}`;
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import type {
|
||||
RELATED_KINDS,
|
||||
RELATED_VIA,
|
||||
} from "@/lib/validations/relation-links";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
export type RelatedKind = (typeof RELATED_KINDS)[number];
|
||||
export type RelatedVia = (typeof RELATED_VIA)[number];
|
||||
@@ -191,7 +192,7 @@ async function hydrate(
|
||||
row.practitioner?.name ??
|
||||
row.kind,
|
||||
date: iso(row.occurredAt),
|
||||
href: `/checkups?visit=${encodeURIComponent(row.id)}`,
|
||||
href: visitPath(row.id),
|
||||
});
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
queryWords,
|
||||
type SearchCandidate,
|
||||
} from "@/lib/search/match";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
// ── Bounds ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -227,7 +228,7 @@ export function recordUrl(
|
||||
const rid = encodeURIComponent(id);
|
||||
switch (kind) {
|
||||
case "visit":
|
||||
return `${origin}/checkups?visit=${rid}`;
|
||||
return `${origin}${visitPath(id)}`;
|
||||
case "condition":
|
||||
return `${origin}/illness/${rid}`;
|
||||
case "document":
|
||||
|
||||
@@ -34,6 +34,7 @@ import { surfaceModule } from "@/lib/modules/surface";
|
||||
import type { ShareDomain } from "@/lib/sharing/scope";
|
||||
import { dateOnlyKey } from "@/lib/tz/date-only";
|
||||
import { userDayKey } from "@/lib/tz/format";
|
||||
import { visitPath } from "@/lib/encounters/visit-links";
|
||||
|
||||
/**
|
||||
* The sharing domain each lane reads, or `null` for a lane only the record's
|
||||
@@ -423,7 +424,7 @@ async function visitsLane(frame: LaneFrame): Promise<LaneRead> {
|
||||
r.practitioner?.name ??
|
||||
"",
|
||||
sub: r.kind,
|
||||
href: `/checkups?visit=${r.id}`,
|
||||
href: visitPath(r.id),
|
||||
}),
|
||||
),
|
||||
standing: [],
|
||||
|
||||
Reference in new issue
Block a user