Skip to content

Align immunization module UX with in-house medications OTR-2027#6565

Merged
saewitz merged 79 commits intodevelopfrom
feature/immunization-module-alignment
Apr 15, 2026
Merged

Align immunization module UX with in-house medications OTR-2027#6565
saewitz merged 79 commits intodevelopfrom
feature/immunization-module-alignment

Conversation

@dmabram
Copy link
Copy Markdown
Contributor

@dmabram dmabram commented Mar 16, 2026

Summary

  • Align the Immunization module's UX with the In-House Medications module
  • Add Pending/Completed accordion sections to the MAR
  • Add pre-visit immunization history and immunization notes to the MAR
  • Rename "Vaccine" terminology to "Immunization" for consistency
  • Add Back, Delete Order buttons and optional Associated Dx / Manufacturer fields to the order form
  • Add required field asterisks to administration fields
  • Add Immunization History and Notes to the Immunization Details screen
  • Navigate to MAR after ordering (instead of edit screen)

Changes by screen

MAR Screen: Pluralized title, Pending/Completed sections, Order button text, pre-visit history, immunization notes
Order Screen: Consistent "Immunization" terminology, field reordering, Back/Delete buttons, disable after save, post-order MAR navigation
Immunization Details Screen: Renamed tab/heading, required field indicators, Delete Order button, history and notes sections

Test plan

  • Navigate to Immunizations tab on a visit
  • Verify title says "Immunizations" and button says "Order"
  • Verify MAR shows Pending and Completed accordion sections
  • Verify pre-visit immunization history shows above MAR
  • Verify immunization note input appears below MAR
  • Click Order → verify form says "Order Immunization", has Associated Dx and Manufacturer fields
  • Verify Ordered By is above Instructions
  • Order an immunization → verify navigates to MAR with pending order
  • Click into Immunization Details tab → verify tab label
  • Verify required fields have asterisks (LOT, MVX, CVX, NDC, VIS, contact fields)
  • Verify Delete Order button appears for pending orders
  • Verify Immunization History and Notes appear on details screen

🤖 Generated with Claude Code

saewitz and others added 6 commits March 12, 2026 18:03
MAR screen:
- Pluralize title to "Immunizations"
- Add Pending/Completed accordion sections (match in-house meds)
- Change button text from "New Order" to "Order"
- Add pre-visit immunization history above MAR
- Add immunization note input at bottom of MAR

Order screen:
- Rename "Order Vaccine" to "Order Immunization" throughout
- Add Back and Delete Order buttons
- Add optional Associated Dx and Manufacturer fields
- Move Ordered By above Instructions
- Disable submit button after ordering
- Navigate to MAR after ordering instead of edit screen

Immunization Details screen:
- Rename tab from "Vaccine Details" to "Immunization Details"
- Rename heading to "Administering immunization"
- Add required field asterisks (LOT, MVX, CVX, NDC, VIS, contact)
- Add Delete Order button for pending orders
- Add Immunization History and Immunization Note sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show edit icon on completed immunization rows (same as pending).
Clicking a completed row or its edit icon navigates to the order
edit screen. Delete button remains pending-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the EHR Immunization module UI flows and terminology to match the in-house medications experience, including MAR grouping, pre-visit history, and immunization-specific notes.

Changes:

  • Adds a new NOTE_TYPE.IMMUNIZATION and introduces an Immunization Notes component using the shared GenericNoteList.
  • Updates Immunization MAR UI with a pre-visit history accordion and Pending/Completed grouping for orders.
  • Updates ordering and details flows (navigation back to MAR, Delete Order action, required indicators, terminology updates).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/utils/lib/types/api/chart-data/chart-data.types.ts Adds NOTE_TYPE.IMMUNIZATION to support immunization-scoped notes.
apps/ehr/src/features/immunization/pages/ImmunizationOrderCreateEdit.tsx Adjusts order UX copy/navigation, adds Delete Order action, updates history accordion label.
apps/ehr/src/features/immunization/pages/Immunization.tsx Updates page title/button, adds history accordion and Immunization Notes on MAR tab.
apps/ehr/src/features/immunization/components/VaccineDetailsCard.tsx Adds required field indicators, adds Delete Order action, adds history + notes sections.
apps/ehr/src/features/immunization/components/OrderHistoryTableRow.tsx Adjusts row click behavior and action availability by status.
apps/ehr/src/features/immunization/components/OrderHistoryTable.tsx Replaces “see more” with Pending/Completed accordions and supports administered-only flat history view.
apps/ehr/src/features/immunization/components/OrderDetailsSection.tsx Adds optional Associated Dx and Manufacturer fields; reorders Instructions.
apps/ehr/src/features/immunization/components/ImmunizationNotes.tsx New immunization notes component wired to GenericNoteList.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +133 to +142
open={isDeleteDialogOpened}
handleClose={() => setIsDeleteDialogOpened(false)}
title="Delete vaccine order"
description={`Are you sure you want to delete the vaccine order?`}
closeButtonText="Cancel"
closeButton={false}
handleConfirm={handleConfirmDelete}
confirmText={isDeleting ? 'Deleting...' : 'Delete'}
confirmLoading={isDeleting}
/>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated dialog title and description to "immunization order".

This comment is from Claude (claude-opus-4-6).

Comment on lines +127 to +134
<ButtonRounded
variant="outlined"
color="primary"
size="large"
onClick={() => navigate(getImmunizationMARUrl(appointmentId!))}
>
Cancel
</ButtonRounded>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the Cancel button, keeping only Back. This matches the In-House Medications pattern (Back + Delete Order).

This comment is from Claude (claude-opus-4-6).

Comment on lines +135 to +148
{orderId && (
<LoadingButton
variant="outlined"
color="warning"
size="large"
loading={isDeleting}
onClick={handleDeleteOrder}
sx={{
borderRadius: '20px',
textTransform: 'none',
}}
>
Delete Order
</LoadingButton>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a CustomDialog confirmation before canceling the order, matching the pattern used in OrderHistoryTableRow.

This comment is from Claude (claude-opus-4-6).

Comment on lines +265 to +281
<Stack direction="row" spacing={1} alignItems="center">
<OrderStatusChip status={order.status} />
{order.status === 'pending' && (
<LoadingButton
variant="outlined"
color="warning"
size="large"
loading={isDeleting}
onClick={handleDeleteOrder}
sx={{
borderRadius: '20px',
textTransform: 'none',
}}
>
Delete Order
</LoadingButton>
)}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added both fixes: Delete Order button is now hidden when isAppointmentReadOnly is true, and a confirmation dialog is shown before cancellation.

This comment is from Claude (claude-opus-4-6).

/>
</Grid>
<Grid xs={6} item>
<TextInput name="details.associatedDx" label="Associated Dx" />
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added dataTestId prop and corresponding entry in data-test-ids.ts.

This comment is from Claude (claude-opus-4-6).

/>
</Grid>
<Grid xs={6} item>
<TextInput name="details.manufacturer" label="Manufacturer" />
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added dataTestId prop and corresponding entry in data-test-ids.ts.

This comment is from Claude (claude-opus-4-6).

Comment on lines +100 to +113
<TableHead>
<TableRow>
<TableCell>Vaccine</TableCell>
<TableCell>Dose / Route / Instructions</TableCell>
<TableCell>Ordered</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{pendingOrders.map((order) => (
<OrderHistoryTableRow key={order.id} order={order} showActions={showActions} />
))}
</TableBody>
</Table>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added showGiven prop to OrderHistoryTableRow (default true). Pending section passes showGiven={false} so the cell count matches the 4-column header.

This comment is from Claude (claude-opus-4-6).

Comment on lines +102 to +106
<TableCell>Vaccine</TableCell>
<TableCell>Dose / Route / Instructions</TableCell>
<TableCell>Ordered</TableCell>
<TableCell>Status</TableCell>
</TableRow>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed all "Vaccine" table headers to "Immunization" across flat and accordion tables.

This comment is from Claude (claude-opus-4-6).

Daniel Abrams and others added 3 commits March 17, 2026 10:32
1. Update delete dialog copy from "vaccine" to "immunization"
2. Remove redundant Cancel button (keep Back only)
3. Add confirmation dialog before Delete Order on order page
4. Gate Delete Order by read-only + add confirmation on details page
5-6. Add dataTestId to Associated Dx and Manufacturer fields
7. Fix column count mismatch with showGiven prop
8. Rename "Vaccine" to "Immunization" in table headers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clicking a completed immunization row or edit icon now navigates to
the Immunization Details tab (administration screen with LOT, NDC,
MVX, CVX, VIS, emergency contact fields) instead of the basic order
edit screen. The details card list now shows all orders (not just
pending) so completed immunizations are visible and editable there.

Pending orders: edit icon → order edit screen (to change vaccine/dose)
Completed orders: edit icon → immunization details screen (to view/edit
administration data like lot number, codes, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dmabram dmabram changed the title Align immunization module UX with in-house medications Align immunization module UX with in-house medications OTR-2032 Mar 17, 2026
@dmabram dmabram changed the title Align immunization module UX with in-house medications OTR-2032 Align immunization module UX with in-house medications OTR-2027 Mar 17, 2026
Daniel Abrams and others added 16 commits March 17, 2026 11:52
Wrap the entire VaccineDetailsCard form in a disabled fieldset when
isAppointmentReadOnly is true, preventing edits to all fields (order
details, LOT, NDC, MVX, CVX, VIS, emergency contact, etc.) after
the note is signed. Administration and delete buttons were already
gated by isReadOnly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…edication config

Replace free-text name input with medispan autocomplete search on add/update pages. Remove manual NDC and medispan ID inputs (derived from API response). Add CPT and HCPCS multi-value inputs stored with proper coding systems.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on create/update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ImmunizationQuickPickData type with order fields (vaccine, dose, units,
  route, location, associatedDx, manufacturer, instructions) and admin
  fields (cvx, mvx, cpt, ndc, lot, expDate)
- 4 zambdas using shared quick-pick factory pattern
- Quick pick selection and save-as-quick-pick on immunization order page
  (save restricted to admin role)
- Immunizations tab on Admin Quick Picks page for rename/delete
- useMergedImmunizationQuickPicks hook for FHIR-based data fetching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quick picks dropdown now appears on both the order creation page and the
vaccine details/administration page. On the order page it fills order
fields; on the details page it fills both order and administration fields
(lot, expDate, mvx, cvx, cpt, ndc). Single quick pick data object applied
contextually to whichever page is displayed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quick picks button now always visible below each vaccine's
Administering immunization header (showAddOption ensures it renders
even when no quick picks exist yet). Admin users can save current
form state as a new quick pick or overwrite an existing one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use dedicated ImmunizationQuickPicksPage (matching ProcedureQuickPicksPage
pattern) instead of QuickPickEditor. No add button — quick picks are
created from the ordering/vaccine details pages. Admin page only supports
rename and delete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests cover ActivityDefinition parsing (all fields, minimal, missing
extension), serialization (tag code, config exclusion), and round-trip
conversion preserving all immunization fields including vaccine, dose,
units, route, location, manufacturer, cvx, mvx, cpt, ndc, lot, expDate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Completed medication rows in MAR table are now clickable, navigating
  to the medication details screen
- New 'completed-edit' form type shows all order and administration
  fields (medication, dose, units, route, lot, expDate, etc.) as editable
- Save works immediately without confirmation modal (same as order-edit)
- Page title shows "Medication Details" for completed medications
- Backend already supports updating fields on non-pending medications
  (only status changes are blocked)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment on lines +33 to +34
setSearchTerm('');
setDebouncedSearchTerm('');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setSearchTerm and setDebouncedSearchTerm - they look like duplication in the state of the same thing. Why not just use one state and have the handler call with debouncing where needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation. Simplified to use a single useCallback with setTimeout for debouncing instead of the separate useDebounce hook + dual state. The two states (inputValue for immediate input display, searchQuery for API calls) are still needed since they serve different purposes — the input must update immediately while the API call should be debounced — but the implementation is now cleaner with the debounce logic inline.

This comment is from Claude (claude-opus-4-6).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For user input in the UI - debounce isn’t always needed (e.g., clearing a field). Same local state - no duplication. Just use debounce in the handler when needed - and skip it otherwise. The non-debounced handler should cancel any pending debounce - to avoid race conditions. I think there’s a clear-field case - please check it doesn’t use debounce and handle race conditions correctly. You can make two helpers - with debounce and without with single source of truth.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, a non-debounced function is needed for features like a clear (×) button that clears the field instantly. If there’s no such functionality, things can be simplified.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clear reason is already handled without debounce (lines 57–60), but the
race condition is a valid catch — clearing or selecting an option doesn't
cancel the pending debounce, so it could still fire and overwrite the reset.
Will fix by calling clear() from useDebounce in both the reason === 'clear' handler and handleAdd.

The two states are still needed:

  • searchTerm — drives the input display, must update immediately
  • debouncedSearchTerm — drives the API call, should be debounced

This comment is from Claude (claude-sonnet-4-6).

Comment on lines +37 to +72
// When administeredOnly is true (backward compat for pre-visit history accordion), show flat table
if (administeredOnly) {
return (
<TableContainer component={Paper} elevation={0}>
<Table>
<TableHead>
<TableRow>
<TableCell>Immunization</TableCell>
<TableCell>Dose / Route / Instructions</TableCell>
<TableCell>Ordered</TableCell>
<TableCell>Given</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
{isLoading ? (
<OrderHistoryTableSkeletonBody />
) : (
<TableBody>
{orders.map((order) => (
<OrderHistoryTableRow key={order.id} order={order} showActions={showActions} />
))}
{orders.length === 0 && (
<TableRow>
<TableCell colSpan={5}>
<Typography variant="body1" sx={{ opacity: 0.65 }}>
No items
</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
)}
</Table>
</TableContainer>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we should render this after the loading check, so the skeleton works correctly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Moved the loading check above the filtering so the skeleton renders immediately without unnecessary computation.

This comment is from Claude (claude-opus-4-6).

Comment on lines +67 to +71
if (isPending) {
navigateToDetails();
} else {
navigateToDetails();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this condition there is no sense, because this function is always called.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — both branches called navigateToDetails(), making the isPending check redundant. This was left over from an earlier iteration where pending and completed rows had different click behavior. Simplified to just call navigateToDetails() directly.

This comment is from Claude (claude-opus-4-6).

…ptCodesInput debounce

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
.sort(ordersRecentFirstComparator)
.filter((order) => order.status === 'pending');
const activeOrders = (ordersResponse?.orders ?? [])
.filter((order) => order.status !== 'cancelled')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct that we consider any uncancelled order to be active?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is intentional — on the vaccine details tab we show all orders that haven't been cancelled (pending, administered, partially administered). This matches how the in-house medications detail view works. Renamed the variable from activeOrders to nonCancelledOrders to make the intent clearer.

This comment is from Claude (claude-opus-4-6).

...(await cleanupProperties(data)),
});
navigate(getImmunizationOrderEditUrl(appointmentId!, response.orderId));
setIsOrderSaved(true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why set up local state if the next line performs navigation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Moved setIsDeleteDialogOpen(false) to the catch block only — on success the navigation unmounts the component so the state update was unnecessary.

This comment is from Claude (claude-opus-4-6).

Daniel Abrams and others added 7 commits March 31, 2026 13:38
…iveOrders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation and placeholder

- Extract duplicated quick pick logic from VaccineDetailsCard and
  ImmunizationOrderCreateEdit into shared hook with applyOrderDetails flag
- Fix missing refetch after saving quick picks in order create page
- Fix array mutation in AutocompleteInput (use spread instead of push)
- Make AutocompleteInput placeholder consistent with TextInput (remove "Select" prefix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nistration

Same pattern as the codes fix — VIS date is saved on the contained
Medication but was being read from the MedicationAdministration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Accept completed and stopped statuses in addition to in-progress.
Clear existing medication extensions before re-adding to prevent
duplicates on re-administration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
if (field === 'medicationId' && value && value !== IN_HOUSE_CONTAINED_MEDICATION_ID && oystehr) {
void (async () => {
try {
const med = await oystehr.fhir.get<import('fhir/r4b').Medication>({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<import('fhir/r4b').Medication> - The syntax looks unusual. Maybe it's better to use a regular type import.

key !== 'performerType' &&
key !== 'consentObtained' &&
key !== 'diagnoses'
) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to define a constant for checking these keys, and also consider making it reliable so it doesn't break when new keys are added. Also worth considering is the option to use positive comparison.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Will switch to a positive inclusion list — defining the keys that
should be applied from a quick pick, rather than the ones to exclude. This
is more reliable since new keys added to ProcedureQuickPickData won't be
silently applied without an explicit decision.

This comment is from Claude (claude-sonnet-4-6).


useEffect(() => {
void fetchQuickPick();
}, [fetchQuickPick]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The effect caused by the function when this function changes looks very strange. Also considering that useEffect is an anti-pattern in many cases where it is used.

Comment on lines +55 to +57
useEffect(() => {
void fetchQuickPicks();
}, [fetchQuickPicks]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please find all similar places, because calling a function in response to this change looks like an anti-pattern. I've already encountered similar code during a code review.

Comment on lines +41 to +53
const fetchQuickPicks = useCallback(async () => {
if (!oystehrZambda) return;
setLoading(true);
try {
const response = await getImmunizationQuickPicks(oystehrZambda);
setQuickPicks([...response.quickPicks].sort((a, b) => a.name.localeCompare(b.name)));
} catch (error) {
console.error('Failed to fetch immunization quick picks:', error);
enqueueSnackbar('Failed to load immunization quick picks', { variant: 'error' });
} finally {
setLoading(false);
}
}, [oystehrZambda]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can significantly simplify component code by using React Query and handling state less manually. Since this pattern is common in this pull request, please take a look and see if we can apply it here and in other places.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will refactor the new quick pick pages to use useQuery for
fetching and useMutation for rename/delete, following the existing pattern
in admin.queries.tsx. This covers ImmunizationQuickPicksPage,
InHouseMedicationQuickPicksPage, and the two detail pages.

This comment is from Claude (claude-sonnet-4-6).

Comment on lines +39 to +57
const fetchQuickPick = useCallback(async () => {
if (!oystehrZambda || !quickPickId) return;
setLoading(true);
try {
const response = await getProcedureQuickPicks(oystehrZambda);
const found = response.quickPicks.find((qp) => qp.id === quickPickId);
if (found) {
setQuickPick(found);
} else {
enqueueSnackbar('Quick pick not found', { variant: 'error' });
navigate('/admin/quick-picks');
}
} catch (error) {
console.error('Failed to load quick pick:', error);
enqueueSnackbar('Failed to load quick pick details', { variant: 'error' });
} finally {
setLoading(false);
}
}, [oystehrZambda, quickPickId, navigate]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write a general hook that you can reuse across different components where this logic is needed? This is a general observation. Check similar components in this pull request and see if you can unify their logic into reusable hooks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted three generic factory functions (makeQuickPicksQuery,
makeRenameQuickPickMutation, makeRemoveQuickPickMutation) in
admin.queries.tsx to generate typed hooks for all four quick pick types. All
pages now use the generated hooks instead of manual useState/useCallback/useEffect.

This comment is from Claude (claude-sonnet-4-6).

Comment on lines +81 to +87
export function useMergedImmunizationQuickPicks(): UseFhirQuickPicksResult<ImmunizationQuickPickData> {
return useFhirQuickPicks(getImmunizationQuickPicks);
}

export function useMergedInHouseMedicationQuickPicks(): UseFhirQuickPicksResult<InHouseMedicationQuickPickData> {
return useFhirQuickPicks(getInHouseMedicationQuickPicks);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these wrappers needed if they do nothing but call another function? If this is for TypeScript, it's better to use Generics, or use origin function as is.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the two wrappers — exported useFhirQuickPicks directly and updated
the three call sites to use useFhirQuickPicks(getImmunizationQuickPicks) /
useFhirQuickPicks(getInHouseMedicationQuickPicks) directly.

This comment is from Claude (claude-sonnet-4-6).

Comment on lines +51 to +61
const fetchMedications = useCallback(async (): Promise<void> => {
if (!oystehrZambda) {
return;
}
fetchMedications().catch((error) => console.log('Error fetching medications', error));
const medicationsTemp = await getInHouseMedications(oystehrZambda);
setMedications(medicationsTemp);
}, [oystehrZambda]);

useEffect(() => {
fetchMedications().catch((error) => console.log('Error fetching medications', error));
}, [fetchMedications]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a rather non-standard approach that might cause problems later. It's better to call data loading in useEffect within a single listener for oystehrZambda rather than the current implementation. Or even better, use React Query if possible.

Comment thread apps/ehr/src/pages/AdminPage.tsx Outdated

useEffect(() => {
if (page) {
setPageTab(page);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like a callback should be used in case the quickly clicks between tabs and there are no synchronization bugs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted to the develop version of AdminPage

This comment is from Claude (claude-sonnet-4-6).

saewitz and others added 15 commits April 7, 2026 16:08
…ctor quick picks to React Query

- Remove `as any` casts from zambda execute calls and quick pick index files
- Fix typed signatures in AutocompleteInput, AdminPage, and CptCodesInput
- Add `clear()` debounce cancellation in CptCodesInput on immediate actions
- Replace `useMergedImmunizationQuickPicks`/`useMergedInHouseMedicationQuickPicks` wrappers with direct `useFhirQuickPicks` calls
- Add generic factory functions in admin.queries.tsx for quick picks query/mutation hooks
- Refactor all 8 quick pick list/detail pages to use React Query hooks
- Replace negative exclusion with positive inclusion list in ProceduresNew quick pick apply logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AutocompleteInput: use remote cast types (include | undefined)
- CptCodesInput: use remote inputValue/searchQuery/timeoutRef pattern with clearTimeout
- VaccineDetailsCard/ImmunizationOrderCreateEdit: remove useFhirQuickPicks usage in favor of useImmunizationQuickPickManagement hook from remote

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	apps/ehr/src/App.tsx
#	apps/ehr/src/api/api.ts
#	apps/ehr/src/features/visits/telemed/components/admin/admin.queries.tsx
#	apps/ehr/src/pages/AdminPage.tsx
- Navigate to MAR tab after dispense/dispense-not-administered saves,
  not just after order-new
- Allow saving in completed-edit mode by excluding it from the
  hasNotEditableStatus check; disable only when no unsaved changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…immunization quick picks export

- Allow status transitions from administered/partly-administered/not-administered
  states, only blocking changes on cancelled orders
- Show dispense footer (Administered, Partly Administered, Not Administered
  buttons) when editing completed orders
- Remove isCompletedEditWithNothingToSave gate so status buttons are enabled
- Add useMergedImmunizationQuickPicks export to fix missing import error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…CodeDTO shape and fix lotNumber field reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/hcpcs codes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… matching

After ordering a medication the app now navigates to the MAR instead of the
edit page; update orderDeletion and EditVaccineOrderPage tests to wait for the
MAR URL and extract the medication ID from the MAR table row. Also tighten
quick-picks-admin tab selectors to exact matches to avoid ambiguity with the
new In-House Medications tab, and update createAndOrderMedication to use real
diagnosis/medication names.
…lision

Changed medicationRowPrefix from 'mar-table-medication-' to 'mar-table-medication-row-'
so the prefix selector no longer matches the 'mar-table-medication-cell' td element,
fixing a Playwright strict mode violation in the order deletion E2E test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The E2E test for administering an immunization order was timing out because
the CptCodesInput Autocomplete had no data-testid, making it unfindable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	apps/ehr/src/components/input/AutocompleteInput.tsx
@saewitz saewitz merged commit 12a9bb8 into develop Apr 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants