mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(visits): keep the appointment rail action's wire tokens for the shipped app
The shipped iOS app routes the rail by intent and resolves the label from its own catalogue; a new `visit.view` intent would leave its tap on an appointment inert. The upcoming-visit action keeps `checkup.view` and `daily.action.viewCheckups` on the wire and only its `href` moves to the visit. The web card names what that link opens: "View appointment", or "View appointments" for the visits list.
This commit is contained in:
1 parent
99fec79177
commit
2914832ed8
5 files changed
+73
-20
No files matched your search
@@ -161,6 +161,37 @@ describe("<PriorityCard>", () => {
|
||||
expect(html).toContain("View check-ups");
|
||||
});
|
||||
|
||||
// An appointment keeps the check-up label on the wire for the shipped iOS
|
||||
// app; the web names what the link opens. The check-up item above is the
|
||||
// negative control.
|
||||
it("names an appointment's action for the visit it opens", () => {
|
||||
const action = (href: string) => ({
|
||||
labelKey: "daily.action.viewCheckups",
|
||||
intent: "checkup.view",
|
||||
href,
|
||||
});
|
||||
const one = render(
|
||||
<PriorityCard
|
||||
item={item({
|
||||
kind: "upcoming_visit",
|
||||
actions: [action("/checkups?visit=v1")],
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(one).toContain("View appointment");
|
||||
expect(one).not.toContain("View check-ups");
|
||||
expect(one).toContain('href="/checkups?visit=v1"');
|
||||
const several = render(
|
||||
<PriorityCard
|
||||
item={item({
|
||||
kind: "upcoming_visit",
|
||||
actions: [action("/checkups?view=visits")],
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(several).toContain("View appointments");
|
||||
});
|
||||
|
||||
describe("dismiss affordance — observational kinds only", () => {
|
||||
const DISMISSIBLE: PriorityItemKind[] = [
|
||||
"milestone",
|
||||
|
||||
@@ -36,6 +36,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { ProseBlocks } from "@/components/insights/prose-blocks";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { VISITS_PATH } from "@/lib/encounters/visit-links";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
@@ -205,7 +206,7 @@ export function PriorityCard({
|
||||
<ActionLink
|
||||
key={action.intent}
|
||||
href={action.href}
|
||||
label={t(action.labelKey)}
|
||||
label={t(actionLabelKey(item, action))}
|
||||
/>
|
||||
) : onAction ? (
|
||||
<ActionButton
|
||||
@@ -225,6 +226,24 @@ export function PriorityCard({
|
||||
}
|
||||
|
||||
/** A navigation action. Always offered — reading is never delegated away. */
|
||||
/**
|
||||
* The label key a web card shows for an action.
|
||||
*
|
||||
* The server's `labelKey`, except on an appointment: that item still carries
|
||||
* the check-up item's label on the wire ("View check-ups"), because the
|
||||
* shipped iOS app resolves the label and routes the intent it already knows.
|
||||
* Its `href` already opens the visit, so the web names what it opens.
|
||||
*/
|
||||
export function actionLabelKey(
|
||||
item: Pick<PriorityItem, "kind">,
|
||||
action: Pick<PriorityItemAction, "labelKey" | "href">,
|
||||
): string {
|
||||
if (item.kind !== "upcoming_visit" || !action.href) return action.labelKey;
|
||||
return action.href === VISITS_PATH
|
||||
? "daily.action.viewAppointments"
|
||||
: "daily.action.viewAppointment";
|
||||
}
|
||||
|
||||
function ActionLink({ href, label }: { href: string; label: string }) {
|
||||
return (
|
||||
<Link
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
* stands in its place — not an alarming empty card.
|
||||
*
|
||||
* Every `PriorityItem` action carries an `href` (dose.log → /medications,
|
||||
* sync.reconnect → /settings/integrations, checkup.view → /checkups,
|
||||
* visit.view → the visit on the checkups page's visits tab), so
|
||||
* sync.reconnect → /settings/integrations, checkup.view → /checkups, or the
|
||||
* visit itself on an upcoming_visit item), so
|
||||
* `PriorityCard` wires each tap as a `<Link>` to its existing destination
|
||||
* by construction; S2 invents no new backend action.
|
||||
*
|
||||
|
||||
@@ -1507,16 +1507,17 @@ describe("buildDailyDigest — due check-ups and today's visits hold their place
|
||||
).toEqual(["Dr. Weiss today", "Dentist tomorrow"]);
|
||||
});
|
||||
|
||||
// The tap opens where the appointment lives. The item used to borrow the
|
||||
// check-up item's action whole (label, intent and the bare `/checkups`),
|
||||
// which opens the preventive-care tab an appointment is never on.
|
||||
it("opens the visit itself, with its own label and intent", () => {
|
||||
// The tap opens where the appointment lives: the bare `/checkups` it carried
|
||||
// opens the preventive-care tab an appointment is never on. Label and intent
|
||||
// stay the check-up item's on the wire, for the shipped iOS app; the web
|
||||
// card relabels (`priority-card.test.tsx`).
|
||||
it("opens the visit itself, keeping the intent the app routes", () => {
|
||||
const d = buildDailyDigest(input({ upcomingVisits: [visit()] }), t);
|
||||
const item = d.worthALook.find((i) => i.kind === "upcoming_visit");
|
||||
expect(item?.actions).toEqual([
|
||||
{
|
||||
labelKey: "daily.action.viewAppointment",
|
||||
intent: "visit.view",
|
||||
labelKey: "daily.action.viewCheckups",
|
||||
intent: "checkup.view",
|
||||
href: "/checkups?visit=v1",
|
||||
},
|
||||
]);
|
||||
@@ -1535,8 +1536,8 @@ describe("buildDailyDigest — due check-ups and today's visits hold their place
|
||||
const item = d.worthALook.find((i) => i.kind === "upcoming_visit");
|
||||
expect(item?.actions).toEqual([
|
||||
{
|
||||
labelKey: "daily.action.viewAppointments",
|
||||
intent: "visit.view",
|
||||
labelKey: "daily.action.viewCheckups",
|
||||
intent: "checkup.view",
|
||||
href: "/checkups?view=visits",
|
||||
},
|
||||
]);
|
||||
|
||||
+11
-9
@@ -681,10 +681,9 @@ function buildPreventiveCareItem(
|
||||
* visit's own status field with a second place to set it.
|
||||
*
|
||||
* The action opens where the appointment lives: the visit itself, or the
|
||||
* visits list for a day with several. It is `visit.view` and not the checkup
|
||||
* item's `checkup.view`: the two kinds used to share that intent, its label
|
||||
* and its bare `/checkups`, which sent every tap on an appointment to the
|
||||
* preventive-care list the appointment is not on.
|
||||
* visits list for a day with several. The bare `/checkups` it used to carry
|
||||
* sent every tap on an appointment to the preventive-care list the
|
||||
* appointment is not on.
|
||||
*/
|
||||
function buildUpcomingVisitItems(
|
||||
visits: readonly DailyDigestUpcomingVisit[],
|
||||
@@ -713,11 +712,14 @@ function buildUpcomingVisitItems(
|
||||
status: "info",
|
||||
actions: [
|
||||
{
|
||||
labelKey:
|
||||
group.length === 1
|
||||
? "daily.action.viewAppointment"
|
||||
: "daily.action.viewAppointments",
|
||||
intent: "visit.view",
|
||||
// The wire keeps the check-up item's label and intent: the shipped
|
||||
// iOS app routes `checkup.view` and knows no other token for this
|
||||
// item, so a new intent would leave its tap inert. The web card
|
||||
// relabels it from the kind (`actionLabelKey` in
|
||||
// `priority-card.tsx`); the switch to `visit.view` waits for an
|
||||
// app that understands it.
|
||||
labelKey: "daily.action.viewCheckups",
|
||||
intent: "checkup.view",
|
||||
href: visitsPath(group.map((visit) => visit.id)),
|
||||
},
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user