import type { Address, Appointment, Location } from "fhir/r4"; /** * Where an appointment is, and whether a patient can be given directions to it. R6. * * THE RULE IS ADDRESS-DERIVED, NEVER TELEHEALTH-FLAG-DERIVED, and that is the whole design. Spencer: * *"keep the directions button with a map link action on the card"* - and R6 adds that it must be hidden * for a video visit. Asking "is this telehealth?" would answer only that case; asking "does this place * have an address?" answers it AND the site nobody has finished configuring, in one rule that cannot * drift from the data. * * A telehealth Location has no address ( - telehealth IS a Location), so it produces no button * for free. Offering directions to a video visit is the same class of error as printing the practice's * street address on one, which `appointmentLocation.ts` already exists to prevent. */ /** The `Location/` an appointment names, if it names one. */ export const appointmentLocationId = (appointment: Appointment | undefined): string | undefined => { const reference = (appointment?.participant ?? []).find((participant) => // Anchored on the prefix, matching appointmentLocation.ts. A `.includes("Location")` would let any // reference that merely CONTAINS the word win. participant.actor?.reference?.startsWith("Location/"), )?.actor?.reference; return reference?.split("/")[1] || undefined; }; /** * One line of address, or undefined when the place has none worth showing. * * `line` alone is not an address a map can find, and neither is a lone country. The test is whether * there is a STREET and a settlement - anything less produces a map pin in the middle of a state, which * is worse than no button because the patient trusts it. */ export const formatLocationAddress = (location: Location | undefined): string | undefined => { const address: Address | undefined = location?.address; if (!address) return undefined; const street = (address.line ?? []).map((part) => part?.trim()).filter(Boolean).join(", "); const settlement = address.city?.trim(); if (!street || !settlement) return undefined; return [street, settlement, address.state?.trim(), address.postalCode?.trim()] .filter(Boolean) .join(", "); }; /** * A maps URL for the appointment's location, or undefined when it has no usable address. * * A LINK, NOT AN INTEGRATION. There is no API, no key and nothing to wrap behind our own endpoint, so * is satisfied by there being no third-party call at all - * the browser opens whatever handles the URL. * * The honest tradeoff, stated rather than implied: this is Google's universal maps URL. It resolves in * every desktop browser and hands off to the Google Maps app on a phone that has one - but an iPhone * user whose map app is Apple Maps gets Google's. The neutral alternative (`geo:`) is not handled by * desktop browsers at all, which fails the larger group. */ export const directionsUrl = (location: Location | undefined): string | undefined => { const address = formatLocationAddress(location); if (!address) return undefined; return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(address)}`; };