fix(links): make the X the only unlink and let a chip open its record

A link chip was one button with an X drawn inside it, so tapping the
chip to open the linked record removed the link. The label now opens the
record (a dose, a visit, a condition or a document) and a separate X,
labelled with what it removes, unlinks, with an undo in the toast. Each
chip also carries its record's date, so several doses of one vaccine no
longer read as identical chips. The fix sits in the shared picker, which
is every chip list with this shape.
This commit is contained in:
Marc-André Bombeck committed 2026-09-25 07:10:50 +02:00
1 parent 0390d124cb
commit 31f0d7c9fb
13 files changed
+267 -34

No files matched your search

@@ -165,4 +165,78 @@ describe("<DocumentRecordLinks>", () => {
expect(html).not.toContain('data-slot="document-vaccination-links-add"');
expect(html).not.toContain('data-slot="document-visit-links-add"');
});
describe("chips open the record they point at (#1024)", () => {
it("a manager's dose chip carries the dose date and opens the dose", () => {
const html = render(
<DocumentRecordLinks
doc={doc({
vaccinationLinks: [
{
vaccinationId: "dose-1",
occurredAt: "1991-04-02T00:00:00.000Z",
catalogSlug: "tetanus",
vaccineName: null,
},
],
})}
canManage
seedFresh
onChange={() => undefined}
/>,
);
const open = html.match(
/<a[^>]*data-slot="document-vaccination-links-chip-open"[^>]*>/,
);
expect(open?.[0]).toContain('href="/vaccinations?dose=dose-1"');
// The date tells two doses of one vaccine apart.
expect(open?.[0]).toBeDefined();
expect(html).toMatch(/>Tetanus<\/span><span[^>]*>04\/02\/1991</);
// The label is not the unlink control; the X is.
expect(html).toContain(
'data-slot="document-vaccination-links-chip-remove"',
);
});
it("a reader's dose chip opens that dose, not the whole list", () => {
const html = render(
<DocumentRecordLinks
doc={doc({
vaccinationLinks: [
{
vaccinationId: "dose-1",
occurredAt: "1991-04-02T00:00:00.000Z",
catalogSlug: null,
vaccineName: "DTP",
},
],
})}
canManage={false}
seedFresh
onChange={() => undefined}
/>,
);
expect(html).toContain('href="/vaccinations?dose=dose-1"');
});
it("a reader's visit chip opens the visit", () => {
const html = render(
<DocumentRecordLinks
doc={doc({
encounterLinks: [
{
encounterId: "enc-1",
kind: "CHECKUP",
occurredAt: "2026-03-01T09:00:00.000Z",
},
],
} as never)}
canManage={false}
seedFresh
onChange={() => undefined}
/>,
);
expect(html).toContain('href="/checkups?visit=enc-1"');
});
});
});
@@ -47,6 +47,7 @@ export function recordOptions(
label: string;
meta?: string | null;
occurredAt: string;
href?: string | null;
}>,
anchor: string | null,
labels: { suggested: string; date: (iso: string) => string },
@@ -59,6 +60,7 @@ export function recordOptions(
label: record.label,
meta: record.meta ?? null,
dateLabel: labels.date(record.occurredAt),
href: record.href ?? null,
};
if (isNearAnchor(record.occurredAt, anchor)) {
near.push({
@@ -73,6 +75,16 @@ export function recordOptions(
return [...near, ...rest];
}
/** Where a visit opens: its sheet on the checkups page. */
function visitHref(encounterId: string): string {
return `/checkups?visit=${encodeURIComponent(encounterId)}`;
}
/** Where a dose opens: its sheet on the vaccinations page. */
function doseHref(vaccinationId: string): string {
return `/vaccinations?dose=${encodeURIComponent(vaccinationId)}`;
}
/** A dose named the way the vaccination page names it. */
function doseName(
t: Translate,
@@ -146,6 +158,7 @@ export function DocumentRecordLinks({
label: encounterKindText(t, visit.kind as EncounterKind),
meta: visit.practitioner?.name ?? null,
occurredAt: visit.occurredAt,
href: visitHref(visit.id),
})),
anchor,
labels,
@@ -155,6 +168,7 @@ export function DocumentRecordLinks({
id: dose.id,
label: doseName(t, dose.catalogEntry?.slug ?? null, dose.vaccineName),
occurredAt: dose.occurredAt,
href: doseHref(dose.id),
})),
anchor,
labels,
@@ -194,7 +208,7 @@ export function DocumentRecordLinks({
{doc.encounterLinks.map((link) => (
<Link
key={link.encounterId}
href={`/documents?encounter=${encodeURIComponent(link.encounterId)}`}
href={visitHref(link.encounterId)}
className="bg-muted text-foreground hover:bg-muted/70 focus-visible:ring-ring/50 inline-flex max-w-64 items-center gap-1.5 rounded-full px-2.5 py-1 text-xs focus-visible:ring-[3px] focus-visible:outline-none"
>
<span className="truncate">
@@ -244,7 +258,7 @@ export function DocumentRecordLinks({
{(doc.vaccinationLinks ?? []).map((link) => (
<Link
key={link.vaccinationId}
href="/vaccinations"
href={doseHref(link.vaccinationId)}
data-slot="document-vaccination-link"
className="bg-muted text-foreground hover:bg-muted/70 focus-visible:ring-ring/50 inline-flex max-w-64 items-center gap-1.5 rounded-full px-2.5 py-1 text-xs focus-visible:ring-[3px] focus-visible:outline-none"
>