You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(pharmacy): add PharmacyInput, PharmacySearch and PharmacyField - #737
Adds the e-prescribing pharmacy field to @mieweb/ui, re-implementing WebChart's PatPharmacyInput / PharmacySearch (MIEstache + jQuery) as React components for the prescription form. Stacked on #719 (med-component-update); retarget to main after it merges.
Components (src/components/Pharmacy):
Export
Role
PharmacyInput
Native <select> of the patient's likely pharmacies (preferred, then recent) + search button. Keeps WebChart's form contract: pharmacy / pharm_id fields, placeholder "0", and ncpdpid / npi / newrx option attributes for transmitOptionSet.
PharmacySearch
Directory search modal: postal / state-city / phone / fax filters, Exclude NPI, Mail Only, name search, details panel, Use Once / Make Preferred.
PharmacyField
Both of the above, wired to a host-supplied PharmacyDataSource (six async functions).
usePharmacyCandidates, usePharmacySearch
The orchestration PharmacyField uses (debounce, abort, stale-result guard), for custom layouts.
pharmacy.ts
The domain rules, implemented once: pharmId ⇄ (id, idType), classification, default selection, search params, role 5/6.
Problem. Every prescription form needs the patient's pharmacy, and the selection and search rules are subtle (N-prefixed NPI ids, override vs. preferred precedence, the 6-digit phone/fax rule). Implementing them once in the library keeps WebChart and any new host consistent.
Alternatives considered.
ProviderSearchBar / ProviderSearchFilters: provider-specific, with no candidate list or preferred-save flow.
UserPicker: people, not a directory with service levels.
CodeLookup: offline code shards, not a live directory.
Custom Select: the field must stay a native <select> for the legacy form and delegated jQuery change handlers.
The new components compose the existing Modal, Autocomplete, RadioGroup, Checkbox, Input, Tooltip and Button.
Relationships. All are declared in parameters.catalog on both pages and summarized in the Encounter & orders overview:
PharmacyInput ↔ PharmacySearch: composes with.
PharmacyInput ↔ PharmacyField: alternatives.
PharmacyField contains PharmacySearch.
PharmacyField ↔ MedicationList (MedicationEditor): composes with. The PharmacyField page shows the host wiring that chooses e-send or fax from serviceLevelFlags(pharmacy).newRx.
Boundaries (owned by the app or WebChart). These are documented in the story descriptions:
Endpoints, the candidates SQL and the server's multi-pass search.
City normalization, zip proximity and the staging directory.
Patient-level authorization on save.
Non-US state lists and persisting filter preferences.
Defects fixed relative to WebChart. Each is covered by a test:
Make Preferred no longer silently clears the mail-order slot when nothing is selected.
Only one candidate is selected when the patient has both a local and a mail-order preferred pharmacy.
"0" is treated as no prefill.
The 6-digit rule counts digits, not characters.
Save failures are shown and keep the modal open.
The modal closes once.
Also in this PR (separate commit):Autocomplete now stops Escape from propagating while its list is open, so Escape closes the list rather than the enclosing Modal. This also fixes the same bug in MedicationEditor's drug search.
Compatibility. Additive exports only, with no new dependencies. The Autocomplete change only affects Escape while the list is open.
Evidence.
32 stories, including Empty, Loading, Error, Mobile, RTL (Arabic), save failure, and a composed field with a fake data source and a visible call log.
All story play functions were run in a browser.
New visual baselines: light, dark, mobile, the results list, and the details panel's service-level branches.
RTL matrix entries with geometry and :dir() assertions.
I checked brand switching (WebChart, BlueHive) and text expansion (long-name fixture) by hand.
Known gap: I haven't checked focus return in Safari. It uses returnFocusRef, because Safari and macOS Firefox don't focus a button when it's clicked.
Maintainer notes: src/components/Pharmacy/MAINTAINERS.md, registered in CONTRIBUTING.md.
Checklist
pnpm typecheck && pnpm lint && pnpm format && pnpm test pass
pnpm typecheck, pnpm lint, pnpm format: passed.
pnpm test: 2662 passed, 9 failed. All 9 failures are 5-second timeouts under full-suite load in files this PR doesn't touch (EncounterVisit, MedicationEditor, OrgChart, SuperChat, TextAreaRich). Run alone, those 7 files pass (165/165). Pharmacy and Autocomplete: 91/91.
Run in mcr.microsoft.com/playwright:v1.58.2-noble against storybook-static, because Playwright 1.58 won't install on the Ubuntu 26.04 dev host. The container reproduces the existing CI baselines exactly (40/40 RTL matrix).
No new physical-direction classes or inline styles (ml-, left-, marginLeft, left:, textAlign: 'left', …) — pnpm rtl:scan passes
Directional icons flip with rtl:-scale-x-100; genuinely physical usages annotated // rtl-ignore -- <reason>. There are no directional icons: search, phone, truck, bolt and shield have fixed meaning.
ArrowLeft/ArrowRight handlers are direction-aware (useDirection()). None were added; Autocomplete handles only Up and Down.
New class strings safelisted in bothsrc/tailwind-preset.ts and src/tailwind-preset.cjs
Direction-sensitive rendering has RTL visual coverage: screenshot plus computed-style/geometry assertions
pharmacy-input: the search button sits at the inline end, with square joined corners.
pharmacy-info: labels are at the inline start, phone numbers are LTR islands, and street addresses aren't reordered (bdi matches :dir(ltr)).
…e list
Modal listens for Escape on document, so Escape meant to close the result
list also closed the dialog (e.g. MedicationEditor's drug search). Stop
propagation while the list is open; a second Escape still closes the Modal.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-implements WebChart's patient pharmacy input and pharmacy search for
the prescription form:
- pharmacy.ts: pharmId parsing, classification, default selection,
search params and preferred role, implemented once
- PharmacyInput: native select + search button, keeping WebChart's form
contract (pharmacy / pharm_id, "0" placeholder, ncpdpid/npi/newrx)
- PharmacySearch: directory search modal with Use Once / Make Preferred,
details panel, focus return
- PharmacyField + usePharmacyCandidates / usePharmacySearch: wired to a
host-supplied PharmacyDataSource with debounce and stale-result guard
Fixes WebChart defects: Make Preferred can no longer clear the mail-order
slot, only one preferred candidate is selected, "0" means no prefill,
and the 6-digit rule counts digits.
Includes stories, unit tests, visual and RTL baselines, condensed-density
rules, safelist entries and maintainer notes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds reusable pharmacy selection and directory search to @mieweb/ui prescription forms, preserving WebChart’s native form contract while leaving backend integration to the host application.
Changes:
Adds PharmacyInput, PharmacySearch, PharmacyField, shared domain rules, and data-source hooks.
Adds component documentation, fixtures, unit tests, visual coverage, RTL checks, and condensed styling.
Updates Autocomplete so Escape closes its open list without dismissing the enclosing modal.
File
Description
tests/visual/rtl-matrix.spec.ts
Adds pharmacy alignment and text-direction checks.
tests/visual/pharmacy.spec.ts
Adds theme, mobile, results, and details screenshots.
src/tailwind-preset.ts
Safelists new pharmacy utilities.
src/tailwind-preset.cjs
Mirrors the pharmacy safelist additions.
src/styles/condensed-view.css
Adds compact pharmacy styling.
src/index.ts
Exports the pharmacy module.
src/components/Pharmacy/usePharmacyData.ts
Adds loading, search, cancellation, and save hooks.
src/components/Pharmacy/storyData.ts
Supplies fictional fixtures and an in-memory data source.
src/components/Pharmacy/PharmacyTypeIcon.tsx
Displays pharmacy capability icons.
src/components/Pharmacy/PharmacySearch.tsx
Implements directory search and preferred selection.
…earch
- reset override, selection and search when patId changes
- block dismissal while Make Preferred saves; ignore a save that settles
after the dialog is gone
- required PharmacyInput fails validation on the "0" placeholder
- Enter in dialog fields no longer submits a host form
- hide superseded results while a search loads
- late initialFilters don't overwrite user-edited filters
- wait for web fonts before the details-panel screenshot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Changing patients can leave the host holding the previous patient’s pharmacy until loading succeeds, or indefinitely if it fails, risking use of the wrong pharmacy.
Invalidate pending saves when the dialog session closes
src/components/Pharmacy/PharmacySearch.tsx:254
With motion enabled, Modal keeps this body mounted during exit, so closing the dialog does not invalidate a pending save. If it settles during exit or after a quick reopen, it can publish the old choice and close the reopened search. Invalidate a dialog-session token on close, check it after the save settles, and cover animated close/reopen in the late-save test.
Restore focus after the modal exit animation completes
src/components/Pharmacy/PharmacySearch.tsx:610
Focus is returned as soon as open changes to false, but Modal keeps the dialog and focus guard active until its animated exit finishes (Modal.tsx:148–193). Focus then sits outside a still-visible modal, and Tab presses on the opener bypass the dialog's focus trap. Restore focus after the exit completes, using the immediate path only when there is no animated exit.
Prevent Escape from dismissing the enclosing modal
src/components/Pharmacy/PharmacySearch.tsx:670
When this search is inside another Modal, Escape with the result list closed reaches both document listeners and can dismiss the prescription editor too. The pending-save guard only blocks this search's dismissal, so the enclosing modal can still unmount it. Consume Escape inside the pharmacy dialog after Autocomplete handles its list, and use handleOpenChange to preserve the pending-save guard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Adds the e-prescribing pharmacy field to
@mieweb/ui, re-implementing WebChart'sPatPharmacyInput/PharmacySearch(MIEstache + jQuery) as React components for the prescription form. Stacked on #719 (med-component-update); retarget tomainafter it merges.Components (
src/components/Pharmacy):PharmacyInput<select>of the patient's likely pharmacies (preferred, then recent) + search button. Keeps WebChart's form contract:pharmacy/pharm_idfields, placeholder"0", andncpdpid/npi/newrxoption attributes fortransmitOptionSet.PharmacySearchPharmacyFieldPharmacyDataSource(six async functions).usePharmacyCandidates,usePharmacySearchPharmacyFielduses (debounce, abort, stale-result guard), for custom layouts.pharmacy.tspharmId⇄(id, idType), classification, default selection, search params, role 5/6.Problem. Every prescription form needs the patient's pharmacy, and the selection and search rules are subtle (N-prefixed NPI ids, override vs. preferred precedence, the 6-digit phone/fax rule). Implementing them once in the library keeps WebChart and any new host consistent.
Alternatives considered.
ProviderSearchBar/ProviderSearchFilters: provider-specific, with no candidate list or preferred-save flow.UserPicker: people, not a directory with service levels.CodeLookup: offline code shards, not a live directory.Select: the field must stay a native<select>for the legacy form and delegated jQuerychangehandlers.The new components compose the existing
Modal,Autocomplete,RadioGroup,Checkbox,Input,TooltipandButton.Relationships. All are declared in
parameters.catalogon both pages and summarized in the Encounter & orders overview:MedicationEditor): composes with. The PharmacyField page shows the host wiring that chooses e-send or fax fromserviceLevelFlags(pharmacy).newRx.Boundaries (owned by the app or WebChart). These are documented in the story descriptions:
Defects fixed relative to WebChart. Each is covered by a test:
"0"is treated as no prefill.Also in this PR (separate commit):
Autocompletenow stops Escape from propagating while its list is open, so Escape closes the list rather than the enclosingModal. This also fixes the same bug inMedicationEditor's drug search.Compatibility. Additive exports only, with no new dependencies. The
Autocompletechange only affects Escape while the list is open.Evidence.
playfunctions were run in a browser.:dir()assertions.Known gap: I haven't checked focus return in Safari. It uses
returnFocusRef, because Safari and macOS Firefox don't focus a button when it's clicked.Maintainer notes:
src/components/Pharmacy/MAINTAINERS.md, registered in CONTRIBUTING.md.Checklist
pnpm typecheck && pnpm lint && pnpm format && pnpm testpasspnpm typecheck,pnpm lint,pnpm format: passed.pnpm test: 2662 passed, 9 failed. All 9 failures are 5-second timeouts under full-suite load in files this PR doesn't touch (EncounterVisit, MedicationEditor, OrgChart, SuperChat, TextAreaRich). Run alone, those 7 files pass (165/165). Pharmacy and Autocomplete: 91/91.pnpm test:visual)mcr.microsoft.com/playwright:v1.58.2-nobleagainststorybook-static, because Playwright 1.58 won't install on the Ubuntu 26.04 dev host. The container reproduces the existing CI baselines exactly (40/40 RTL matrix).tests/visual/pharmacy.spec.ts+tests/visual/rtl-matrix.spec.ts: 49 passed.RTL — see CONTRIBUTING.md → RTL & direction-agnostic styling
ml-,left-,marginLeft,left:,textAlign: 'left', …) —pnpm rtl:scanpassesrtl:-scale-x-100; genuinely physical usages annotated// rtl-ignore -- <reason>. There are no directional icons: search, phone, truck, bolt and shield have fixed meaning.ArrowLeft/ArrowRighthandlers are direction-aware (useDirection()). None were added; Autocomplete handles only Up and Down.src/tailwind-preset.tsandsrc/tailwind-preset.cjspharmacy-input: the search button sits at the inline end, with square joined corners.pharmacy-info: labels are at the inline start, phone numbers are LTR islands, and street addresses aren't reordered (bdimatches:dir(ltr)).