@medplum/react-hooks 5.1.14 → 5.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts", "../../src/MedplumProvider/MedplumProvider.tsx", "../../src/MedplumProvider/MedplumProvider.context.ts", "../../src/useCachedBinaryUrl/useCachedBinaryUrl.ts", "../../src/useMedicationIFrame/useMedicationIFrame.ts", "../../src/useMedicationOrder/useMedicationOrder.ts", "../../src/useMedicationOrderSet/useMedicationOrderSet.ts", "../../src/useNotificationCount/useNotificationCount.ts", "../../src/useSubscription/useSubscription.ts", "../../src/usePatientSummaryData/usePatientSummaryData.ts", "../../src/usePharmacySearch/usePharmacySearch.ts", "../../src/usePrevious/usePrevious.ts", "../../src/useQuestionnaireForm/useQuestionnaireForm.ts", "../../src/useResource/useResource.ts", "../../src/useQuestionnaireForm/utils.ts", "../../src/useSearch/useSearch.ts", "../../src/useDebouncedValue/useDebouncedValue.ts", "../../src/useSyncOrderSet/useSyncOrderSet.ts", "../../src/useThreadInbox/useThreadInbox.ts", "../../src/useWhisper/useWhisper.ts"],
4
- "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nexport * from './MedplumProvider/MedplumProvider';\nexport * from './MedplumProvider/MedplumProvider.context';\nexport * from './useCachedBinaryUrl/useCachedBinaryUrl';\nexport * from './useMedicationIFrame/useMedicationIFrame';\nexport * from './useMedicationOrder/useMedicationOrder';\nexport * from './useMedicationOrderSet/useMedicationOrderSet';\nexport * from './useNotificationCount/useNotificationCount';\nexport * from './usePatientSummaryData/usePatientSummaryData';\nexport * from './usePharmacySearch/usePharmacySearch';\nexport * from './usePrevious/usePrevious';\nexport * from './useQuestionnaireForm/useQuestionnaireForm';\nexport * from './useQuestionnaireForm/utils';\nexport * from './useResource/useResource';\nexport * from './useSearch/useSearch';\nexport * from './useSubscription/useSubscription';\nexport * from './useSyncOrderSet/useSyncOrderSet';\nexport * from './useThreadInbox/useThreadInbox';\nexport * from './useWhisper/useWhisper';\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport type { JSX, ReactNode } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\nimport type { MedplumNavigateFunction } from './MedplumProvider.context';\nimport { reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n readonly medplum: MedplumClient;\n readonly navigate?: MedplumNavigateFunction;\n readonly children: ReactNode;\n}\n\nconst EVENTS_TO_TRACK = [\n 'change',\n 'storageInitialized',\n 'storageInitFailed',\n 'profileRefreshing',\n 'profileRefreshed',\n] satisfies (keyof MedplumClientEventMap)[];\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props - The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState((s) => ({\n ...s,\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n }));\n }\n\n for (const event of EVENTS_TO_TRACK) {\n medplum.addEventListener(event, eventListener);\n }\n return () => {\n for (const event of EVENTS_TO_TRACK) {\n medplum.removeEventListener(event, eventListener);\n }\n };\n }, [medplum]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path - The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MedplumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MedplumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MedplumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useMemo } from 'react';\n\n// Maintain a cache of urls to avoid unnecessary re-download of attachments\n// The following is a workaround for the fact that each request to a resource containing a Binary data reference\n// returns a NEW signed S3 URL for each bypassing the native browser caching mechanism\n// resulting in unnecessary bandwidth consumption.\n// https://www.medplum.com/docs/fhir-datastore/binary-data#consuming-a-fhir-binary-in-an-application\n// https://github.com/medplum/medplum/issues/3815\n\n// The S3 presigned URLs expire after 1 hour with the default configuration and hard refreshes are not uncommon even in SPAs so this\n// could be a good way to get additional cache hits\n// This would require additional logic for initialization, saving, and purging of expired keys\nconst urls = new Map<string, string>();\n\nexport const useCachedBinaryUrl = (binaryUrl: string | undefined): string | undefined => {\n return useMemo(() => {\n if (!binaryUrl) {\n return undefined;\n }\n\n const binaryResourceUrl = binaryUrl.split('?')[0];\n if (!binaryResourceUrl) {\n return binaryUrl;\n }\n\n // Check if the binaryUrl is a presigned S3 URL\n let binaryUrlSearchParams: URLSearchParams;\n try {\n binaryUrlSearchParams = new URLSearchParams(new URL(binaryUrl).search);\n } catch (_err) {\n return binaryUrl;\n }\n\n if (!binaryUrlSearchParams.has('Key-Pair-Id') || !binaryUrlSearchParams.has('Signature')) {\n return binaryUrl;\n }\n\n // https://stackoverflow.com/questions/23929145/how-to-test-if-a-given-time-stamp-is-in-seconds-or-milliseconds\n const binaryUrlExpires = binaryUrlSearchParams.get('Expires');\n if (!binaryUrlExpires || binaryUrlExpires.length > 13) {\n // Expires is expected to be in seconds, not milliseconds\n return binaryUrl;\n }\n\n const cachedUrl = urls.get(binaryResourceUrl);\n if (cachedUrl) {\n const searchParams = new URLSearchParams(new URL(cachedUrl).search);\n\n // This is fairly brittle as it relies on the current structure of the Medplum returned URL\n const expires = searchParams.get('Expires');\n\n // `expires` is in seconds, Date.now() is in ms\n // Add padding to mitigate expiration between time of check and time of use\n if (expires && Number.parseInt(expires, 10) * 1000 - 5_000 > Date.now()) {\n return cachedUrl;\n }\n }\n\n urls.set(binaryResourceUrl, binaryUrl);\n return binaryUrl;\n }, [binaryUrl]);\n};\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Identifier } from '@medplum/fhirtypes';\nimport { useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface MedicationIFrameOptions {\n readonly patientId?: string;\n readonly onPatientSyncSuccess?: () => void;\n readonly onIframeSuccess?: (url: string) => void;\n readonly onError?: (err: unknown) => void;\n}\n\n/**\n * Generic React hook that syncs a patient to a medication-order vendor and\n * returns the chart iframe URL.\n *\n * Executes the patient-sync bot first (if patientId is provided), then\n * the iframe bot to obtain the prescribing UI URL.\n *\n * Uses an effect cleanup flag so React 18 Strict Mode double-mount does not\n * trigger duplicate bot executions.\n *\n * @param syncBotIdentifier - Bot identifier for the patient sync bot.\n * @param iframeBotIdentifier - Bot identifier for the iframe URL bot.\n * @param options - Configuration and callback options.\n * @returns The medication-order iframe URL, or undefined while loading.\n */\nexport function useMedicationIFrame(\n syncBotIdentifier: Identifier,\n iframeBotIdentifier: Identifier,\n options: MedicationIFrameOptions\n): string | undefined {\n const medplum = useMedplum();\n const { patientId, onPatientSyncSuccess, onIframeSuccess, onError } = options;\n const [iframeUrl, setIframeUrl] = useState<string | undefined>(undefined);\n\n const onPatientSyncSuccessRef = useRef(onPatientSyncSuccess);\n const onIframeSuccessRef = useRef(onIframeSuccess);\n const onErrorRef = useRef(onError);\n\n useEffect(() => {\n onPatientSyncSuccessRef.current = onPatientSyncSuccess;\n onIframeSuccessRef.current = onIframeSuccess;\n onErrorRef.current = onError;\n }, [onPatientSyncSuccess, onIframeSuccess, onError]);\n\n useEffect(() => {\n let cancelled = false;\n\n const run = async (): Promise<void> => {\n try {\n if (patientId) {\n await medplum.executeBot(syncBotIdentifier, { patientId });\n if (cancelled) {\n return;\n }\n onPatientSyncSuccessRef.current?.();\n }\n const result = await medplum.executeBot(iframeBotIdentifier, { patientId });\n if (cancelled) {\n return;\n }\n if (result.url) {\n setIframeUrl(result.url);\n onIframeSuccessRef.current?.(result.url);\n }\n } catch (err: unknown) {\n if (!cancelled) {\n onErrorRef.current?.(err);\n }\n }\n };\n\n run().catch(() => {\n // Handled via onErrorRef when !cancelled\n });\n\n return (): void => {\n cancelled = true;\n };\n }, [medplum, syncBotIdentifier, iframeBotIdentifier, patientId]);\n\n return iframeUrl;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { MedicationOrderRequest, MedicationOrderResponse, MedicationSearchParams } from '@medplum/core';\nimport {\n INVALID_MEDICATION_ORDER_RESPONSE,\n INVALID_MEDICATION_SEARCH_RESPONSE,\n isResource,\n medicationOrderRequestToParameters,\n medicationSearchParamsToParameters,\n parametersToMedicationOrderResponse,\n} from '@medplum/core';\nimport type { Bundle, Medication, Parameters } from '@medplum/fhirtypes';\nimport { useCallback } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UseMedicationOrderReturn {\n searchMedications: (params: MedicationSearchParams) => Promise<Medication[]>;\n orderMedication: (input: MedicationOrderRequest) => Promise<MedicationOrderResponse>;\n}\n\n/**\n * Vendor-neutral hook for e-prescribing drug search and order-medication via\n * **FHIR custom operations** (no Bot identifiers required).\n *\n * Hits two project-scoped operations whose backing Bot is chosen at deploy time\n * via an `OperationDefinition` resource carrying the\n * `operationDefinition-implementation` extension \u2014 see\n * [bot operations docs](https://www.medplum.com/docs/bots/custom-fhir-operations).\n * The server's `tryCustomOperation` dispatch handles the OD \u2192 Bot lookup, so\n * projects can swap vendors (ScriptSure today, DoseSpot tomorrow) by deploying\n * a different bot under the same operation code.\n *\n * - `searchMedications`: `POST /fhir/R4/Medication/$drug-search` \u2014 expects the\n * bot to return a `Bundle<Medication>` (single-Resource `return` shortcut on\n * the OperationDefinition).\n * - `orderMedication`: `POST /fhir/R4/MedicationRequest/$order-medication` \u2014\n * expects the bot to return a `Parameters` envelope with named primitives;\n * `parametersToMedicationOrderResponse` decodes it.\n *\n * @returns Callbacks to search medications and submit an order request.\n */\nexport function useMedicationOrder(): UseMedicationOrderReturn {\n const medplum = useMedplum();\n\n const searchMedications = useCallback(\n async (params: MedicationSearchParams): Promise<Medication[]> => {\n const url = medplum.fhirUrl('Medication', '$drug-search');\n const body = medicationSearchParamsToParameters(params);\n const response = await medplum.post(url, body);\n if (!isResource<Bundle<Medication>>(response, 'Bundle')) {\n throw new Error(INVALID_MEDICATION_SEARCH_RESPONSE);\n }\n return (response.entry ?? [])\n .map((e) => e.resource)\n .filter((r): r is Medication => isResource<Medication>(r, 'Medication'));\n },\n [medplum]\n );\n\n const orderMedication = useCallback(\n async (input: MedicationOrderRequest): Promise<MedicationOrderResponse> => {\n const url = medplum.fhirUrl('MedicationRequest', '$order-medication');\n const body = medicationOrderRequestToParameters(input);\n const response = await medplum.post(url, body);\n if (!isResource<Parameters>(response, 'Parameters')) {\n // Server returned something other than a Parameters envelope. Surface the\n // existing INVALID_MEDICATION_ORDER_RESPONSE so callers (e.g. the Provider\n // App's draft-MR cleanup branch) keep their single error-handling path.\n throw new Error(INVALID_MEDICATION_ORDER_RESPONSE);\n }\n return parametersToMedicationOrderResponse(response);\n },\n [medplum]\n );\n\n return { searchMedications, orderMedication };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedicationOrderSetRequest } from '@medplum/core';\nimport {\n INVALID_MEDICATION_ORDER_SET_RESPONSE,\n isResource,\n medicationOrderSetRequestToParameters,\n parametersToMedicationOrderSetResponse,\n} from '@medplum/core';\nimport type { Parameters } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UseMedicationOrderSetOptions {\n /** Patient to prescribe against. Hook stays idle (no operation call) until set. */\n readonly patientId: string | undefined;\n /** Medplum PlanDefinition id (vendor-neutral). Bot resolves it to the vendor's order set id. */\n readonly planDefinitionId?: string;\n /** Vendor-side order set id, when picked directly (escape hatch when no synced PD exists yet). */\n readonly vendorOrderSetId?: number | string;\n readonly appId?: string;\n}\n\nexport interface UseMedicationOrderSetReturn {\n /** Most recent successful URL from the order-set operation, or undefined while loading / on error. */\n readonly url: string | undefined;\n /** True while a request is in flight. */\n readonly loading: boolean;\n /** Last error from the operation call, or undefined. */\n readonly error: unknown;\n /**\n * Force a re-fetch using the current options. Useful when wiring\n * `PrescriptionIFrameModal.onRefreshLaunchUrl` so the session token in\n * the returned widget URL is fresh on every modal open.\n */\n readonly refresh: () => Promise<string | undefined>;\n}\n\n/**\n * Vendor-neutral React hook that calls the `$order-set-url` custom FHIR\n * operation and exposes the resulting iframe URL plus refresh/loading/error\n * state.\n *\n * Hits the project-scoped operation whose backing bot is chosen at deploy time\n * via an `OperationDefinition` resource carrying the\n * `operationDefinition-implementation` extension \u2014 see\n * [bot operations docs](https://www.medplum.com/docs/bots/custom-fhir-operations).\n * The server's `tryCustomOperation` dispatch handles the OD \u2192 Bot lookup, so\n * projects can swap vendors (ScriptSure today, DoseSpot tomorrow) by deploying\n * a different bot under the same operation code.\n *\n * - URL: `POST /fhir/R4/PlanDefinition/$order-set-url`\n * - Body: `Parameters` with `patientId` + (`planDefinitionId` XOR `vendorOrderSetId`) + optional `appId`.\n * - Returns: `Parameters` whose `launchUrl` is exposed as `url` on the hook.\n *\n * The hook is a \"build a URL\" hook (mirrors {@link useMedicationIFrame}); it\n * does not stamp Medplum resources or create vendor-side resources, so\n * `refresh` is safe to call repeatedly (the operation is naturally idempotent).\n *\n * Re-runs whenever the input options change. In-flight calls are cancelled on\n * input change and on unmount via a per-effect `cancelled` flag, so React 18\n * Strict Mode double-mount does not surface stale URLs.\n *\n * @param options - Patient, picker (PD or vendor id), and optional appId.\n * @returns `{ url, loading, error, refresh }`.\n */\nexport function useMedicationOrderSet(options: UseMedicationOrderSetOptions): UseMedicationOrderSetReturn {\n const medplum = useMedplum();\n const { patientId, planDefinitionId, vendorOrderSetId, appId } = options;\n\n const [url, setUrl] = useState<string | undefined>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>(undefined);\n\n // Latest options snapshot so `refresh` always uses current values without\n // re-creating the callback when only the inputs change.\n const optionsRef = useRef({ patientId, planDefinitionId, vendorOrderSetId, appId });\n optionsRef.current = { patientId, planDefinitionId, vendorOrderSetId, appId };\n\n const buildRequest = (): MedicationOrderSetRequest | undefined => {\n const o = optionsRef.current;\n if (!o.patientId) {\n return undefined;\n }\n const hasPd = Boolean(o.planDefinitionId);\n const hasVendorId = o.vendorOrderSetId !== undefined && o.vendorOrderSetId !== null && o.vendorOrderSetId !== '';\n if (!hasPd && !hasVendorId) {\n return undefined;\n }\n const req: MedicationOrderSetRequest = {\n patientId: o.patientId,\n planDefinitionId: hasPd ? o.planDefinitionId : undefined,\n vendorOrderSetId: hasVendorId ? o.vendorOrderSetId : undefined,\n appId: o.appId,\n };\n return req;\n };\n\n const callOperation = useCallback(\n async (req: MedicationOrderSetRequest): Promise<string | undefined> => {\n const operationUrl = medplum.fhirUrl('PlanDefinition', '$order-set-url');\n const body = medicationOrderSetRequestToParameters(req);\n const response = await medplum.post(operationUrl, body);\n if (!isResource<Parameters>(response, 'Parameters')) {\n throw new Error(INVALID_MEDICATION_ORDER_SET_RESPONSE);\n }\n const decoded = parametersToMedicationOrderSetResponse(response);\n return decoded.launchUrl;\n },\n [medplum]\n );\n\n // `refresh()` and the input-driven effect share a monotonically-incrementing\n // run id (`runIdRef`). Every fetch \u2014 whether kicked off by the effect or by\n // a `refresh()` call \u2014 captures its own `myRunId` at start, and only writes\n // state if `runIdRef.current === myRunId` when the fetch resolves. The\n // effect's per-run `cancelled` flag also short-circuits on unmount / deps\n // change. This gives `refresh()` the same cancellation semantics as the\n // effect without the act+await deadlock that a resolver-queue approach\n // creates (a refresh promise that depends on a nonce-triggered effect\n // re-render cannot resolve while React's `act` is awaiting the refresh).\n // (PR https://github.com/medplum/medplum/pull/8999#discussion_r3277116116)\n const runIdRef = useRef(0);\n\n const refresh = useCallback(async (): Promise<string | undefined> => {\n const req = buildRequest();\n if (!req) {\n return undefined;\n }\n runIdRef.current += 1;\n const myRunId = runIdRef.current;\n setLoading(true);\n setError(undefined);\n try {\n const next = await callOperation(req);\n if (runIdRef.current !== myRunId) {\n // A newer run started while we were in flight \u2014 drop the result so\n // neither the state nor the returned promise can clobber the newer\n // fetch's URL.\n return undefined;\n }\n setUrl(next);\n return next;\n } catch (err: unknown) {\n if (runIdRef.current === myRunId) {\n setError(err);\n setUrl(undefined);\n }\n return undefined;\n } finally {\n if (runIdRef.current === myRunId) {\n setLoading(false);\n }\n }\n }, [callOperation]);\n\n useEffect(() => {\n let cancelled = false;\n const req = buildRequest();\n if (!req) {\n // Inputs incomplete \u2014 clear any stale URL so the consumer doesn't\n // keep showing a URL that no longer matches the current selection.\n runIdRef.current += 1;\n setUrl(undefined);\n setLoading(false);\n setError(undefined);\n return undefined;\n }\n\n runIdRef.current += 1;\n const myRunId = runIdRef.current;\n setLoading(true);\n setError(undefined);\n\n callOperation(req)\n .then((next) => {\n if (cancelled || runIdRef.current !== myRunId) {\n return;\n }\n setUrl(next);\n })\n .catch((err: unknown) => {\n if (cancelled || runIdRef.current !== myRunId) {\n return;\n }\n setError(err);\n setUrl(undefined);\n })\n .finally(() => {\n if (!cancelled && runIdRef.current === myRunId) {\n setLoading(false);\n }\n });\n\n return (): void => {\n cancelled = true;\n };\n }, [callOperation, patientId, planDefinitionId, vendorOrderSetId, appId]);\n\n return { url, loading, error, refresh };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { ResourceType } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\nimport { useSubscription } from '../useSubscription/useSubscription';\n\nexport interface UseNotificationCountOptions {\n readonly resourceType: ResourceType;\n readonly countCriteria: string;\n readonly subscriptionCriteria: string;\n}\n\n/**\n * Returns a live notification count for a given resource type.\n *\n * Uses `medplum.search()` for the initial count (with default cache policy) and\n * subscribes to real-time updates via `useSubscription()`, re-fetching with\n * `cache: 'reload'` whenever a matching event arrives.\n *\n * @param options - The resource type, count search criteria, and subscription criteria.\n * @returns The current notification count.\n */\nexport function useNotificationCount(options: UseNotificationCountOptions): number {\n const medplum = useMedplum();\n const { resourceType, countCriteria, subscriptionCriteria } = options;\n const [count, setCount] = useState(0);\n\n const updateCount = useCallback(\n (cache: 'default' | 'reload') => {\n medplum\n .search(resourceType, countCriteria, { cache })\n .then((result) => setCount(result.total as number))\n .catch(console.error);\n },\n [medplum, resourceType, countCriteria]\n );\n\n // Initial count\n useEffect(() => {\n updateCount('default');\n }, [updateCount]);\n\n // Subscribe to the criteria\n useSubscription(subscriptionCriteria, () => {\n updateCount('reload');\n });\n\n return count;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { SubscriptionEmitter, SubscriptionEventMap } from '@medplum/core';\nimport { deepEquals } from '@medplum/core';\nimport type { Bundle, Subscription } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum, useMedplumProfile } from '../MedplumProvider/MedplumProvider.context';\n\nconst SUBSCRIPTION_DEBOUNCE_MS = 3000;\n\nexport type UseSubscriptionOptions = {\n subscriptionProps?: Partial<Subscription>;\n onWebSocketOpen?: () => void;\n onWebSocketClose?: () => void;\n onSubscriptionConnect?: (subscriptionId: string) => void;\n onSubscriptionDisconnect?: (subscriptionId: string) => void;\n onError?: (err: Error) => void;\n};\n\n/**\n * Creates an in-memory `Subscription` resource with the given criteria on the Medplum server and calls the given callback when an event notification is triggered by a resource interaction over a WebSocket connection.\n *\n * Subscriptions created with this hook are lightweight, share a single WebSocket connection, and are automatically untracked and cleaned up when the containing component is no longer mounted.\n *\n * @param criteria - The FHIR search criteria to subscribe to.\n * @param callback - The callback to call when a notification event `Bundle` for this `Subscription` is received.\n * @param options - Optional options used to configure the created `Subscription`. See {@link UseSubscriptionOptions}\n *\n * --------------------------------------------------------------------------------------------------------------------------------\n *\n * `options` contains the following properties, all of which are optional:\n * - `subscriptionProps` - Allows the caller to pass a `Partial<Subscription>` to use as part of the creation\n * of the `Subscription` resource for this subscription. It enables the user namely to pass things like the `extension` property and to create\n * the `Subscription` with extensions such the {@link https://www.medplum.com/docs/subscriptions/subscription-extensions#interactions | Supported Interaction} extension which would enable to listen for `create` or `update` only events.\n * - `onWebsocketOpen` - Called when the WebSocket connection is established with Medplum server.\n * - `onWebsocketClose` - Called when the WebSocket connection disconnects.\n * - `onSubscriptionConnect` - Called when the corresponding subscription starts to receive updates after the subscription has been initialized and connected to.\n * - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.\n * - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.\n */\nexport function useSubscription(\n criteria: string | undefined,\n callback: (bundle: Bundle) => void,\n options?: UseSubscriptionOptions\n): void {\n const medplum = useMedplum();\n const profile = useMedplumProfile();\n // When the user is not authenticated, the subscription becomes a no-op. Subscribing would\n // create a `Subscription` resource and request a WebSocket binding token, both of which 401\n // without a profile. Treating criteria as `undefined` skips all subscription work; when the\n // user authenticates, `profile` changes and the effect re-runs to subscribe.\n const effectiveCriteria = profile ? criteria : undefined;\n const [emitter, setEmitter] = useState<SubscriptionEmitter>();\n // We don't memoize the entire options object since it contains callbacks and if the callbacks change identity, we don't want to trigger a resubscribe to criteria\n const [memoizedSubProps, setMemoizedSubProps] = useState(options?.subscriptionProps);\n\n const listeningRef = useRef(false);\n const unsubTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const prevCriteriaRef = useRef<string | undefined>(undefined);\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>(undefined);\n\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef(options?.onWebSocketOpen);\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef(options?.onWebSocketClose);\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef(options?.onSubscriptionConnect);\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef(options?.onSubscriptionDisconnect);\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef(options?.onError);\n onErrorRef.current = options?.onError;\n\n useEffect(() => {\n // Deep equals checks referential equality first\n if (!deepEquals(options?.subscriptionProps, memoizedSubProps)) {\n setMemoizedSubProps(options?.subscriptionProps);\n }\n }, [memoizedSubProps, options]);\n\n useEffect(() => {\n if (unsubTimerRef.current) {\n clearTimeout(unsubTimerRef.current);\n unsubTimerRef.current = undefined;\n }\n\n let shouldSubscribe = false;\n if (\n prevCriteriaRef.current !== effectiveCriteria ||\n !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)\n ) {\n shouldSubscribe = true;\n }\n\n if (shouldSubscribe && prevCriteriaRef.current) {\n medplum.unsubscribeFromCriteria(prevCriteriaRef.current, prevMemoizedSubPropsRef.current);\n }\n\n // Set prev criteria and options to latest after checking them\n prevCriteriaRef.current = effectiveCriteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe && effectiveCriteria) {\n setEmitter(medplum.subscribeToCriteria(effectiveCriteria, memoizedSubProps));\n } else if (!effectiveCriteria) {\n setEmitter(undefined);\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n if (effectiveCriteria) {\n medplum.unsubscribeFromCriteria(effectiveCriteria, memoizedSubProps);\n }\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, effectiveCriteria, memoizedSubProps]);\n\n const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => {\n callbackRef.current?.(event.payload);\n }, []);\n\n const onWebSocketOpen = useCallback(() => {\n onWebSocketOpenRef.current?.();\n }, []);\n\n const onWebSocketClose = useCallback(() => {\n onWebSocketCloseRef.current?.();\n }, []);\n\n const onSubscriptionConnect = useCallback((event: SubscriptionEventMap['connect']) => {\n onSubscriptionConnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onSubscriptionDisconnect = useCallback((event: SubscriptionEventMap['disconnect']) => {\n onSubscriptionDisconnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onError = useCallback((event: SubscriptionEventMap['error']) => {\n onErrorRef.current?.(event.payload);\n }, []);\n\n useEffect(() => {\n if (!emitter) {\n return () => undefined;\n }\n if (!listeningRef.current) {\n emitter.addEventListener('message', emitterCallback);\n emitter.addEventListener('open', onWebSocketOpen);\n emitter.addEventListener('close', onWebSocketClose);\n emitter.addEventListener('connect', onSubscriptionConnect);\n emitter.addEventListener('disconnect', onSubscriptionDisconnect);\n emitter.addEventListener('error', onError);\n listeningRef.current = true;\n }\n return () => {\n listeningRef.current = false;\n emitter.removeEventListener('message', emitterCallback);\n emitter.removeEventListener('open', onWebSocketOpen);\n emitter.removeEventListener('close', onWebSocketClose);\n emitter.removeEventListener('connect', onSubscriptionConnect);\n emitter.removeEventListener('disconnect', onSubscriptionDisconnect);\n emitter.removeEventListener('error', onError);\n };\n }, [\n emitter,\n emitterCallback,\n onWebSocketOpen,\n onWebSocketClose,\n onSubscriptionConnect,\n onSubscriptionDisconnect,\n onError,\n ]);\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { QueryTypes } from '@medplum/core';\nimport { resolveId } from '@medplum/core';\nimport type { Patient, Reference, Resource, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/** Descriptor for a single FHIR search that a section needs. */\nexport interface FhirSearchDescriptor {\n /** Unique key used to access this search's results via `SectionResults[key]`. */\n readonly key: string;\n readonly resourceType: ResourceType;\n /** Which search param references the patient. Defaults to 'subject'. Examples: 'patient', 'beneficiary'. */\n readonly patientParam?: string;\n /**\n * Additional search params \u2014 same format as the 2nd arg to medplum.searchResources().\n * When using a string, do not include _count or _sort; they are appended automatically.\n */\n readonly query?: QueryTypes;\n}\n\n/** Named map of FHIR results for a section: `results[searchKey]` returns the Resource[] for that search. */\nexport type SectionResults = Record<string, Resource[]>;\n\nexport interface PatientSummaryData {\n /** One SectionResults map per section, indexed to match the sections array. */\n readonly sectionData: SectionResults[];\n readonly loading: boolean;\n readonly error: Error | undefined;\n}\n\n/**\n * Build a deduplication key for a search descriptor.\n * Searches with the same key are executed only once and their results shared.\n * @param search - The search descriptor to build a key for.\n * @returns A string key that uniquely identifies the search configuration.\n */\nfunction buildSearchKey(search: FhirSearchDescriptor): string {\n const param = search.patientParam ?? 'subject';\n const query = search.query;\n\n let queryStr = '';\n if (query !== undefined && query !== null) {\n if (typeof query === 'string') {\n queryStr = query;\n } else if (query instanceof URLSearchParams) {\n const entries = Array.from(query.entries()).sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));\n queryStr = JSON.stringify(entries);\n } else if (Array.isArray(query)) {\n const sorted = [...query].sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));\n queryStr = JSON.stringify(sorted);\n } else {\n const sorted = Object.entries(query)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => a.localeCompare(b));\n queryStr = JSON.stringify(sorted);\n }\n }\n\n return `${search.resourceType}:${param}:${queryStr}`;\n}\n\n/**\n * Build a stable fingerprint from sections' search configurations.\n * Used to avoid re-fetching when sections change by reference but not by content.\n * @param sections - The section configs to fingerprint.\n * @returns A string fingerprint representing the search configuration.\n */\nfunction buildSectionsFingerprint(\n sections: { readonly key: string; readonly searches?: FhirSearchDescriptor[] }[]\n): string {\n return sections\n .map((s) => {\n const searchKeys = s.searches ? s.searches.map(buildSearchKey).join(',') : '';\n return `${s.key}:[${searchKeys}]`;\n })\n .join('|');\n}\n\n/**\n * Hook that collects all FHIR searches from section configs, deduplicates them,\n * executes them in parallel, and routes results back to each section.\n * Uses Promise.allSettled so a single failing search does not block all sections \u2014\n * sections whose searches fail gracefully receive empty arrays.\n * @param patient - The patient or patient reference to fetch data for.\n * @param sections - The section configs defining which searches to execute.\n * @returns Section data, loading state, and any error.\n */\nexport function usePatientSummaryData(\n patient: Patient | Reference<Patient>,\n sections: { readonly key: string; readonly searches?: FhirSearchDescriptor[] }[]\n): PatientSummaryData {\n const medplum = useMedplum();\n const [sectionData, setSectionData] = useState<SectionResults[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>();\n\n // Stabilize sections reference: only change when the search configuration actually changes.\n const sectionsFingerprint = buildSectionsFingerprint(sections);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally keyed on fingerprint for content-based stability\n const stableSections = useMemo(() => sections, [sectionsFingerprint]);\n\n // Memoize the patient ID to avoid re-fetching on reference changes\n const patientId = useMemo(() => resolveId(patient), [patient]);\n\n useEffect(() => {\n if (!patientId) {\n return undefined;\n }\n\n let stale = false;\n const ref = `Patient/${patientId}`;\n const searchMeta = { _count: 100, _sort: '-_lastUpdated' };\n\n // Collect unique searches and build a mapping from deduplication key to index\n const uniqueSearches: FhirSearchDescriptor[] = [];\n const searchKeyToIndex = new Map<string, number>();\n\n // For each section, for each search, record the deduplication index and result key\n const sectionSearchMapping: { searchIdx: number; resultKey: string }[][] = [];\n\n for (const section of stableSections) {\n const mapping: { searchIdx: number; resultKey: string }[] = [];\n if (section.searches) {\n for (const search of section.searches) {\n const deduplicationKey = buildSearchKey(search);\n let idx = searchKeyToIndex.get(deduplicationKey);\n if (idx === undefined) {\n idx = uniqueSearches.length;\n searchKeyToIndex.set(deduplicationKey, idx);\n uniqueSearches.push(search);\n }\n mapping.push({ searchIdx: idx, resultKey: search.key });\n }\n }\n sectionSearchMapping.push(mapping);\n }\n\n if (uniqueSearches.length === 0) {\n // No searches needed \u2014 fill empty results\n setSectionData(stableSections.map(() => ({})));\n setLoading(false);\n return undefined;\n }\n\n // Execute all unique searches in parallel\n const promises = uniqueSearches.map((search) => {\n const patientParam = search.patientParam ?? 'subject';\n const baseQuery: Record<string, string | number | boolean> = {\n [patientParam]: ref,\n };\n\n if (search.query) {\n if (typeof search.query === 'string') {\n // String query \u2014 _count and _sort are appended automatically; do not include them in the query string.\n return medplum.searchResources(\n search.resourceType,\n `${patientParam}=${ref}&${search.query}&_count=100&_sort=-_lastUpdated`\n );\n } else if (search.query instanceof URLSearchParams) {\n search.query.forEach((value, key) => {\n baseQuery[key] = value;\n });\n } else if (Array.isArray(search.query)) {\n for (const [key, value] of search.query) {\n baseQuery[key] = value;\n }\n } else {\n for (const [key, value] of Object.entries(search.query)) {\n if (value !== undefined) {\n baseQuery[key] = value;\n }\n }\n }\n }\n\n return medplum.searchResources(search.resourceType, { ...searchMeta, ...baseQuery });\n });\n\n setLoading(true);\n setError(undefined);\n\n // allSettled ensures a single failing search does not block the rest \u2014 each section\n // renders with whatever data is available; failed searches produce empty arrays.\n Promise.allSettled(promises)\n .then((settledResults) => {\n if (stale) {\n return;\n }\n\n // Route results back to sections using named keys\n const data: SectionResults[] = sectionSearchMapping.map((mapping) => {\n const sectionResult: SectionResults = {};\n for (const { searchIdx, resultKey } of mapping) {\n const settled = settledResults[searchIdx];\n sectionResult[resultKey] = settled.status === 'fulfilled' ? settled.value : [];\n }\n return sectionResult;\n });\n\n setSectionData(data);\n setLoading(false);\n\n // Surface the first error so the UI can indicate a partial load failure\n const failures = settledResults.filter((r): r is PromiseRejectedResult => r.status === 'rejected');\n if (failures.length > 0) {\n console.error(\n 'Some patient summary searches failed:',\n failures.map((f) => f.reason)\n );\n const firstError = failures[0].reason;\n setError(firstError instanceof Error ? firstError : new Error(String(firstError)));\n }\n })\n .catch((err: unknown) => {\n if (!stale) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n });\n\n return () => {\n stale = true;\n };\n }, [medplum, patientId, stableSections]);\n\n return { sectionData, loading, error };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AddFavoriteParams, AddPharmacyResponse, PharmacySearchParams } from '@medplum/core';\nimport { isAddPharmacyResponse, isOrganizationArray } from '@medplum/core';\nimport type { Identifier, Organization } from '@medplum/fhirtypes';\nimport { useCallback } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UsePharmacySearchReturn {\n searchPharmacies: (params: PharmacySearchParams) => Promise<Organization[]>;\n addToFavorites: (params: AddFavoriteParams) => Promise<AddPharmacyResponse>;\n}\n\n/**\n * Generic React hook that provides pharmacy search and add-to-favorites\n * functionality for any e-prescribing integration.\n *\n * Encapsulates calls to a search-pharmacy bot and an add-patient-pharmacy bot,\n * and can be composed with the generic `PharmacyDialog` component from `@medplum/react`.\n *\n * @param searchBotIdentifier - Bot identifier for the pharmacy search bot.\n * @param addPharmacyBotIdentifier - Bot identifier for the add-patient-pharmacy bot.\n * @returns An object with `searchPharmacies` and `addToFavorites` callbacks.\n */\nexport function usePharmacySearch(\n searchBotIdentifier: Identifier,\n addPharmacyBotIdentifier: Identifier\n): UsePharmacySearchReturn {\n const medplum = useMedplum();\n\n const searchPharmacies = useCallback(\n async (params: PharmacySearchParams): Promise<Organization[]> => {\n const response = await medplum.executeBot(searchBotIdentifier, params);\n\n if (!isOrganizationArray(response)) {\n throw new Error('Invalid response from pharmacy search');\n }\n\n return response;\n },\n [medplum, searchBotIdentifier]\n );\n\n const addToFavorites = useCallback(\n async (params: AddFavoriteParams): Promise<AddPharmacyResponse> => {\n const response = await medplum.executeBot(addPharmacyBotIdentifier, {\n patientId: params.patientId,\n pharmacy: params.pharmacy,\n setAsPrimary: params.setAsPrimary,\n });\n\n if (!isAddPharmacyResponse(response)) {\n throw new Error('Invalid response from add pharmacy bot');\n }\n\n return response;\n },\n [medplum, addPharmacyBotIdentifier]\n );\n\n return { searchPharmacies, addToFavorites };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useEffect, useRef } from 'react';\n\n/**\n * React Hook to keep track of the passed-in value from the previous render of the containing component.\n * @param value - The value to track.\n * @returns The value passed in from the previous render.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>(undefined);\n useEffect(() => {\n ref.current = value;\n });\n return ref.current;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { getExtension } from '@medplum/core';\nimport type {\n Encounter,\n Questionnaire,\n QuestionnaireItem,\n QuestionnaireResponse,\n QuestionnaireResponseItem,\n QuestionnaireResponseItemAnswer,\n Reference,\n Signature,\n} from '@medplum/fhirtypes';\nimport { useReducer, useRef } from 'react';\nimport { useResource } from '../useResource/useResource';\nimport {\n buildInitialResponse,\n buildInitialResponseItem,\n evaluateCalculatedExpressionsInQuestionnaire,\n QUESTIONNAIRE_ITEM_CONTROL_URL,\n QUESTIONNAIRE_SIGNATURE_RESPONSE_URL,\n removeDisabledItems,\n} from './utils';\n\n// React Hook for Questionnaire Form\n\n// Why is this hard?\n// 1. It needs to handle both initial loading of a questionnaire and updating the response as the user interacts with it.\n// 2. It needs to support pagination and navigation through the questionnaire.\n// 3. It needs to handle complex items like groups and repeatable items.\n\n// Conventions we use:\n// 1. We use `QuestionnaireResponse` to track the user's answers.\n// 2. We use `QuestionnaireItem` to define the structure of the questionnaire.\n// 3. Response items are linked to their corresponding questionnaire items by `linkId`.\n// 4. Response items will always have a `linkId` that matches the `linkId` of the questionnaire item they correspond to.\n// 5. Response items will also always have an `id` that is unique within the response, which can be used to track changes to individual items.\n// 6. Pagination is enabled by default, so current state items will only include items for the current page.\n// 7. If Pagination is disabled, all items will be included in the current state items.\n\nexport interface UseQuestionnaireFormProps {\n readonly questionnaire: Questionnaire | Reference<Questionnaire>;\n readonly defaultValue?: QuestionnaireResponse | Reference<QuestionnaireResponse>;\n readonly subject?: Reference;\n readonly encounter?: Reference<Encounter>;\n readonly source?: QuestionnaireResponse['source'];\n readonly disablePagination?: boolean;\n readonly onChange?: (response: QuestionnaireResponse) => void;\n}\n\nexport interface QuestionnaireFormPage {\n readonly linkId: string;\n readonly title: string;\n readonly group: QuestionnaireItem & { type: 'group' };\n}\n\nexport interface QuestionnaireFormLoadingState {\n /** Currently loading data such as the Questionnaire or the QuestionnaireResponse default value */\n readonly loading: true;\n}\n\nexport interface QuestionnaireFormLoadedState {\n /** Not loading */\n readonly loading: false;\n\n /** The loaded questionnaire */\n questionnaire: Questionnaire;\n\n /** The current draft questionnaire response */\n questionnaireResponse: QuestionnaireResponse;\n\n /** Optional questionnaire subject */\n subject?: Reference;\n\n /** Optional questionnaire encounter */\n encounter?: Reference<Encounter>;\n\n /** The top level items for the current page */\n items: QuestionnaireItem[];\n\n /** The response items for the current page */\n responseItems: QuestionnaireResponseItem[];\n\n /**\n * Adds a new group item to the current context.\n * @param context - The current context of the questionnaire response items.\n * @param item - The questionnaire item that is being added to the group.\n */\n onAddGroup: (context: QuestionnaireResponseItem[], item: QuestionnaireItem) => void;\n\n /**\n * Adds an answer to a repeating item.\n * @param context - The current context of the questionnaire response items.\n * @param item - The questionnaire item that is being answered.\n */\n onAddAnswer: (context: QuestionnaireResponseItem[], item: QuestionnaireItem) => void;\n\n /**\n * Changes an answer value.\n * @param context - The current context of the questionnaire response items.\n * @param item - The questionnaire item that is being answered.\n * @param answer - The answer(s) provided by the user for the questionnaire item.\n */\n onChangeAnswer: (\n context: QuestionnaireResponseItem[],\n item: QuestionnaireItem,\n answer: QuestionnaireResponseItemAnswer[]\n ) => void;\n\n /**\n * Sets or updates the signature for the questionnaire response.\n * @param signature - The signature to set, or undefined to clear the signature.\n */\n onChangeSignature: (signature: Signature | undefined) => void;\n}\n\nexport interface QuestionnaireFormSinglePageState extends QuestionnaireFormLoadedState {\n readonly pagination: false;\n}\n\nexport interface QuestionnaireFormPaginationState extends QuestionnaireFormLoadedState {\n readonly pagination: true;\n pages: QuestionnaireFormPage[];\n activePage: number;\n onNextPage: () => void;\n onPrevPage: () => void;\n}\n\nexport type QuestionnaireFormState =\n | QuestionnaireFormLoadingState\n | QuestionnaireFormSinglePageState\n | QuestionnaireFormPaginationState;\n\nexport function useQuestionnaireForm(props: UseQuestionnaireFormProps): Readonly<QuestionnaireFormState> {\n const questionnaire = useResource(props.questionnaire);\n const defaultResponse = useResource(props.defaultValue);\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n\n const state = useRef<Partial<QuestionnaireFormPaginationState>>({\n activePage: 0,\n });\n\n // If the questionnaire is loaded, we will set the current questionnaire and pages.\n if (!state.current.questionnaire && questionnaire) {\n state.current.questionnaire = questionnaire;\n state.current.pages = props.disablePagination ? undefined : getPages(questionnaire);\n }\n\n // If we are expecting a questionnaire response, and it is loaded, then use it.\n if (questionnaire && props.defaultValue && defaultResponse && !state.current.questionnaireResponse) {\n state.current.questionnaireResponse = buildInitialResponse(questionnaire, defaultResponse);\n emitChange();\n }\n\n // If we are not expecting a questionnaire response, we will create a new one.\n if (questionnaire && !props.defaultValue && !state.current.questionnaireResponse) {\n state.current.questionnaireResponse = buildInitialResponse(questionnaire);\n emitChange();\n }\n\n if (!state.current.questionnaire || !state.current.questionnaireResponse) {\n return { loading: true };\n }\n\n function getResponseItemByContext(\n context: QuestionnaireResponseItem[]\n ): QuestionnaireResponse | QuestionnaireResponseItem | undefined;\n function getResponseItemByContext(\n context: QuestionnaireResponseItem[],\n item?: QuestionnaireItem\n ): QuestionnaireResponseItem | undefined;\n function getResponseItemByContext(\n context: QuestionnaireResponseItem[],\n item?: QuestionnaireItem\n ): QuestionnaireResponse | QuestionnaireResponseItem | undefined {\n let currentItem: QuestionnaireResponse | QuestionnaireResponseItem | undefined =\n state.current.questionnaireResponse;\n for (const contextElement of context) {\n currentItem = currentItem?.item?.find((i) =>\n contextElement.id ? i.id === contextElement.id : i.linkId === contextElement.linkId\n );\n }\n if (item) {\n currentItem = currentItem?.item?.find((i) => i.linkId === item.linkId);\n }\n return currentItem;\n }\n\n function onNextPage(): void {\n state.current.activePage = (state.current.activePage ?? 0) + 1;\n forceUpdate();\n }\n\n function onPrevPage(): void {\n state.current.activePage = (state.current.activePage ?? 0) - 1;\n forceUpdate();\n }\n\n function onAddGroup(context: QuestionnaireResponseItem[], item: QuestionnaireItem): void {\n const responseItem = getResponseItemByContext(context);\n if (responseItem) {\n responseItem.item ??= [];\n responseItem.item.push(buildInitialResponseItem(item));\n emitChange();\n }\n }\n\n function onAddAnswer(context: QuestionnaireResponseItem[], item: QuestionnaireItem): void {\n const currentItem = getResponseItemByContext(context, item);\n if (currentItem) {\n currentItem.answer ??= [];\n currentItem.answer.push({});\n emitChange();\n }\n }\n\n function onChangeAnswer(\n context: QuestionnaireResponseItem[],\n item: QuestionnaireItem,\n answer: QuestionnaireResponseItemAnswer[]\n ): void {\n const currentItem = getResponseItemByContext(context, item);\n if (currentItem) {\n currentItem.answer = answer;\n emitChange();\n }\n }\n\n function onChangeSignature(signature: Signature | undefined): void {\n const currentResponse = state.current.questionnaireResponse;\n if (!currentResponse) {\n return;\n }\n if (signature) {\n currentResponse.extension = currentResponse.extension ?? [];\n currentResponse.extension = currentResponse.extension.filter(\n (ext) => ext.url !== QUESTIONNAIRE_SIGNATURE_RESPONSE_URL\n );\n currentResponse.extension.push({\n url: QUESTIONNAIRE_SIGNATURE_RESPONSE_URL,\n valueSignature: signature,\n });\n } else {\n currentResponse.extension = currentResponse.extension?.filter(\n (ext) => ext.url !== QUESTIONNAIRE_SIGNATURE_RESPONSE_URL\n );\n }\n emitChange();\n }\n\n function updateCalculatedExpressions(): void {\n const questionnaire = state.current.questionnaire;\n if (questionnaire?.item) {\n const response = state.current.questionnaireResponse as QuestionnaireResponse;\n evaluateCalculatedExpressionsInQuestionnaire(questionnaire.item, response);\n }\n }\n\n function emitChange(): void {\n const currentResponse = state.current.questionnaireResponse;\n const currentQuestionnaire = state.current.questionnaire;\n if (!currentResponse || !currentQuestionnaire) {\n return;\n }\n updateCalculatedExpressions();\n forceUpdate();\n props.onChange?.(removeDisabledItems(currentQuestionnaire, currentResponse));\n }\n\n return {\n loading: false,\n pagination: !!state.current.pages,\n questionnaire: state.current.questionnaire,\n questionnaireResponse: removeDisabledItems(state.current.questionnaire, state.current.questionnaireResponse),\n subject: props.subject,\n encounter: props.encounter,\n activePage: state.current.activePage,\n pages: state.current.pages,\n items: getItemsForPage(state.current.questionnaire, state.current.pages, state.current.activePage),\n responseItems: getResponseItemsForPage(\n state.current.questionnaireResponse,\n state.current.pages,\n state.current.activePage\n ),\n onNextPage,\n onPrevPage,\n onAddGroup,\n onAddAnswer,\n onChangeAnswer,\n onChangeSignature,\n } as QuestionnaireFormSinglePageState | QuestionnaireFormPaginationState;\n}\n\nfunction getPages(questionnaire: Questionnaire): QuestionnaireFormPage[] | undefined {\n if (!questionnaire?.item) {\n return undefined;\n }\n const extension = getExtension(questionnaire?.item?.[0], QUESTIONNAIRE_ITEM_CONTROL_URL);\n if (extension?.valueCodeableConcept?.coding?.[0]?.code !== 'page') {\n return undefined;\n }\n\n return questionnaire.item.map((item, index) => {\n return {\n linkId: item.linkId,\n title: item.text ?? `Page ${index + 1}`,\n group: item as QuestionnaireItem & { type: 'group' },\n };\n });\n}\n\nfunction getItemsForPage(\n questionnaire: Questionnaire,\n pages: QuestionnaireFormPage[] | undefined,\n activePage = 0\n): QuestionnaireItem[] {\n if (pages && questionnaire?.item?.[activePage]) {\n return [questionnaire.item[activePage]];\n }\n return questionnaire.item ?? [];\n}\n\nfunction getResponseItemsForPage(\n questionnaireResponse: QuestionnaireResponse,\n pages: QuestionnaireFormPage[] | undefined,\n activePage = 0\n): QuestionnaireResponseItem[] {\n if (pages && questionnaireResponse?.item?.[activePage]) {\n return [questionnaireResponse.item[activePage]];\n }\n return questionnaireResponse.item ?? [];\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient, WithId } from '@medplum/core';\nimport { deepEquals, isReference, isResource, normalizeOperationOutcome } from '@medplum/core';\nimport type { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value - The resource or reference to resource.\n * @param setOutcome - Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | Partial<T> | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): WithId<T> | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<WithId<T> | undefined>(() => {\n return getInitialResource(medplum, value);\n });\n\n const setResourceIfChanged = useCallback(\n (r: WithId<T> | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource]\n );\n\n useEffect(() => {\n let subscribed = true;\n\n const newValue = getInitialResource(medplum, value);\n if (!newValue && isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n } else {\n setResourceIfChanged(newValue);\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum - The medplum client.\n * @param value - The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | Partial<T> | undefined\n): WithId<T> | undefined {\n if (value) {\n if (isResource(value)) {\n return value as WithId<T>;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedValue } from '@medplum/core';\nimport {\n EMPTY,\n HTTP_HL7_ORG,\n PropertyType,\n append,\n capitalize,\n deepClone,\n evalFhirPathTyped,\n getExtension,\n getReferenceString,\n getTypedPropertyValueWithoutSchema,\n normalizeErrorString,\n splitN,\n toJsBoolean,\n toTypedValue,\n typedValueToString,\n} from '@medplum/core';\nimport type {\n Encounter,\n Questionnaire,\n QuestionnaireItem,\n QuestionnaireItemAnswerOption,\n QuestionnaireItemEnableWhen,\n QuestionnaireItemInitial,\n QuestionnaireResponse,\n QuestionnaireResponseItem,\n QuestionnaireResponseItemAnswer,\n Reference,\n ResourceType,\n} from '@medplum/fhirtypes';\n\nexport const QuestionnaireItemType = {\n group: 'group',\n display: 'display',\n question: 'question',\n boolean: 'boolean',\n decimal: 'decimal',\n integer: 'integer',\n date: 'date',\n dateTime: 'dateTime',\n time: 'time',\n string: 'string',\n text: 'text',\n url: 'url',\n choice: 'choice',\n openChoice: 'open-choice',\n attachment: 'attachment',\n reference: 'reference',\n quantity: 'quantity',\n} as const;\nexport type QuestionnaireItemType = (typeof QuestionnaireItemType)[keyof typeof QuestionnaireItemType];\n\nexport const QUESTIONNAIRE_ITEM_CONTROL_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-itemControl`;\nexport const QUESTIONNAIRE_REFERENCE_FILTER_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-referenceFilter`;\nexport const QUESTIONNAIRE_REFERENCE_RESOURCE_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-referenceResource`;\nexport const QUESTIONNAIRE_VALIDATION_ERROR_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-validationError`;\nexport const QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL = `${HTTP_HL7_ORG}/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression`;\nexport const QUESTIONNAIRE_CALCULATED_EXPRESSION_URL = `${HTTP_HL7_ORG}/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression`;\nexport const QUESTIONNAIRE_SIGNATURE_REQUIRED_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-signatureRequired`;\nexport const QUESTIONNAIRE_SIGNATURE_RESPONSE_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaireresponse-signature`;\nexport const QUESTIONNAIRE_HIDDEN_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-hidden`;\n\n/**\n * Returns true if the item is a choice question.\n * @param item - The questionnaire item to check.\n * @returns True if the item is a choice question, false otherwise.\n */\nexport function isChoiceQuestion(item: QuestionnaireItem): boolean {\n return item.type === 'choice' || item.type === 'open-choice';\n}\n\n/**\n * Returns a copy of the questionnaire response with response items for disabled\n * questionnaire items removed.\n *\n * Per the FHIR R4 spec, a QuestionnaireResponse should not include answers for items\n * whose `enableWhen` evaluates to false or whose `questionnaire-hidden` extension is\n * true. The form preserves answers in local state so that re-enabling an item\n * restores its previous value, so callers must strip disabled items before\n * persisting or transmitting the response.\n *\n * @param questionnaire - The questionnaire defining the items.\n * @param response - The current questionnaire response.\n * @returns A new questionnaire response with disabled items removed.\n */\nexport function removeDisabledItems(\n questionnaire: Questionnaire,\n response: QuestionnaireResponse\n): QuestionnaireResponse {\n return {\n ...response,\n item: filterEnabledResponseItems(questionnaire.item, response.item, response),\n };\n}\n\nfunction filterEnabledResponseItems(\n items: QuestionnaireItem[] | undefined,\n responseItems: QuestionnaireResponseItem[] | undefined,\n response: QuestionnaireResponse\n): QuestionnaireResponseItem[] | undefined {\n if (!responseItems) {\n return responseItems;\n }\n const result: QuestionnaireResponseItem[] = [];\n for (const responseItem of responseItems) {\n const item = items?.find((i) => i.linkId === responseItem.linkId);\n if (item && !isQuestionEnabled(item, response)) {\n continue;\n }\n if (item?.item && responseItem.item) {\n result.push({\n ...responseItem,\n item: filterEnabledResponseItems(item.item, responseItem.item, response),\n });\n } else {\n result.push(responseItem);\n }\n }\n return result;\n}\n\n/**\n * Returns true if the questionnaire item is enabled based on the enableWhen conditions or expression.\n * @param item - The questionnaire item to check.\n * @param questionnaireResponse - The questionnaire response to check against.\n * @returns True if the question is enabled, false otherwise.\n */\nexport function isQuestionEnabled(\n item: QuestionnaireItem,\n questionnaireResponse: QuestionnaireResponse | undefined\n): boolean {\n // Check for questionnaire-hidden extension first - if present and true, the item is permanently hidden\n const hiddenExtension = getExtension(item, QUESTIONNAIRE_HIDDEN_URL);\n if (hiddenExtension?.valueBoolean === true) {\n return false;\n }\n\n const extensionResult = isQuestionEnabledViaExtension(item, questionnaireResponse);\n if (extensionResult !== undefined) {\n return extensionResult;\n }\n return isQuestionEnabledViaEnabledWhen(item, questionnaireResponse);\n}\n\n/**\n * Returns true if the questionnaire item is enabled via an extension expression.\n *\n * An expression that returns a boolean value for whether to enable the item.\n * If the expression does not resolve to a boolean, it is considered an error in the design of the Questionnaire.\n * Form renderer behavior is undefined.\n * Some tools may attempt to force the value to be a boolean (e.g. is it a non-empty collection, non-null, non-zero - if so, then true).\n *\n * See: https://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-enableWhenExpression.html\n *\n * @param item - The questionnaire item to check.\n * @param questionnaireResponse - The questionnaire response to check against.\n * @returns True if the question is enabled via an extension expression, false otherwise.\n */\nfunction isQuestionEnabledViaExtension(\n item: QuestionnaireItem,\n questionnaireResponse: QuestionnaireResponse | undefined\n): boolean | undefined {\n const extension = getExtension(item, QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL);\n if (questionnaireResponse && extension) {\n const expression = extension.valueExpression?.expression;\n if (expression) {\n const value = toTypedValue(questionnaireResponse);\n const result = evalFhirPathTyped(expression, [value], { '%resource': value });\n return toJsBoolean(result);\n }\n }\n return undefined;\n}\n\n/**\n * Returns true if the questionnaire item is enabled based on the enableWhen conditions.\n *\n * See: https://hl7.org/fhir/R4/questionnaire-definitions.html#Questionnaire.item.enableWhen\n * See: https://hl7.org/fhir/R4/questionnaire-definitions.html#Questionnaire.item.enableBehavior\n *\n * @param item - The questionnaire item to check.\n * @param questionnaireResponse - The questionnaire response to check against.\n * @returns True if the question is enabled based on the enableWhen conditions, false otherwise.\n */\nfunction isQuestionEnabledViaEnabledWhen(\n item: QuestionnaireItem,\n questionnaireResponse: QuestionnaireResponse | undefined\n): boolean {\n if (!item.enableWhen) {\n return true;\n }\n\n const enableBehavior = item.enableBehavior ?? 'any';\n for (const enableWhen of item.enableWhen) {\n const actualAnswers = getByLinkId(questionnaireResponse?.item, enableWhen.question);\n\n if (enableWhen.operator === 'exists' && !enableWhen.answerBoolean && !actualAnswers?.length) {\n if (enableBehavior === 'any') {\n return true;\n } else {\n continue;\n }\n }\n const { anyMatch, allMatch } = checkAnswers(enableWhen, actualAnswers, enableBehavior);\n\n if (enableBehavior === 'any' && anyMatch) {\n return true;\n }\n if (enableBehavior === 'all' && !allMatch) {\n return false;\n }\n }\n\n return enableBehavior !== 'any';\n}\n\n/**\n * Evaluates the calculated expressions in a questionnaire.\n * Updates response item answers in place with the calculated values.\n *\n * See: https://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-calculatedExpression.html\n *\n * @param items - The questionnaire items to evaluate.\n * @param response - The questionnaire response to evaluate against.\n * @param responseItems - The response items to update.\n */\nexport function evaluateCalculatedExpressionsInQuestionnaire(\n items: QuestionnaireItem[],\n response: QuestionnaireResponse,\n responseItems: QuestionnaireResponseItem[] | undefined = response.item\n): void {\n for (const item of items) {\n const responseItem = responseItems?.find((r) => r.linkId === item.linkId);\n if (responseItem) {\n evaluateQuestionnaireItemCalculatedExpressions(response, item, responseItem);\n if (item.item && responseItem.item) {\n // If the item has nested items, evaluate their calculated expressions as well\n evaluateCalculatedExpressionsInQuestionnaire(item.item, response, responseItem.item);\n }\n }\n }\n}\n\nfunction evaluateQuestionnaireItemCalculatedExpressions(\n response: QuestionnaireResponse,\n item: QuestionnaireItem,\n responseItem: QuestionnaireResponseItem\n): void {\n try {\n const calculatedValue = evaluateCalculatedExpression(item, response);\n if (!calculatedValue) {\n return;\n }\n const answer = typedValueToResponseItem(item, calculatedValue);\n if (!answer) {\n return;\n }\n responseItem.answer = [answer];\n } catch (error) {\n responseItem.extension = [\n {\n url: QUESTIONNAIRE_VALIDATION_ERROR_URL,\n valueString: `Expression evaluation failed: ${normalizeErrorString(error)}`,\n },\n ];\n }\n}\n\nconst questionnaireItemTypesAllowedPropertyTypes: Record<string, string[]> = {\n [QuestionnaireItemType.boolean]: [PropertyType.boolean],\n [QuestionnaireItemType.date]: [PropertyType.date],\n [QuestionnaireItemType.dateTime]: [PropertyType.dateTime],\n [QuestionnaireItemType.time]: [PropertyType.time],\n [QuestionnaireItemType.url]: [PropertyType.string, PropertyType.uri, PropertyType.url],\n [QuestionnaireItemType.attachment]: [PropertyType.Attachment],\n [QuestionnaireItemType.reference]: [PropertyType.Reference],\n [QuestionnaireItemType.quantity]: [PropertyType.Quantity],\n [QuestionnaireItemType.decimal]: [PropertyType.decimal, PropertyType.integer],\n [QuestionnaireItemType.integer]: [PropertyType.decimal, PropertyType.integer],\n} as const;\n\nexport function typedValueToResponseItem(\n item: QuestionnaireItem,\n value: TypedValue\n): QuestionnaireResponseItemAnswer | undefined {\n if (!item.type) {\n return undefined;\n }\n if (item.type === QuestionnaireItemType.choice || item.type === QuestionnaireItemType.openChoice) {\n // Choice and open-choice items can have multiple answer options\n return { [`value${capitalize(value.type)}`]: value.value };\n }\n if (item.type === QuestionnaireItemType.string || item.type === QuestionnaireItemType.text) {\n // Always coerce string values to valueString\n if (typeof value.value === 'string') {\n return { valueString: value.value };\n }\n return undefined;\n }\n const allowedPropertyTypes = questionnaireItemTypesAllowedPropertyTypes[item.type];\n if (allowedPropertyTypes?.includes(value.type)) {\n // Use the questionnaire item type to determine the response item type\n return { [`value${capitalize(item.type)}`]: value.value };\n }\n return undefined;\n}\n\nfunction evaluateCalculatedExpression(\n item: QuestionnaireItem,\n response: QuestionnaireResponse | undefined\n): TypedValue | undefined {\n if (!response) {\n return undefined;\n }\n\n const extension = getExtension(item, QUESTIONNAIRE_CALCULATED_EXPRESSION_URL);\n if (extension) {\n const expression = extension.valueExpression?.expression;\n if (expression) {\n const value = toTypedValue(response);\n const result = evalFhirPathTyped(expression, [value], { '%resource': value });\n return result.length !== 0 ? result[0] : undefined;\n }\n }\n return undefined;\n}\n\nexport function getNewMultiSelectValues(\n selected: string[],\n propertyName: string,\n item: QuestionnaireItem\n): QuestionnaireResponseItemAnswer[] {\n const result: QuestionnaireResponseItemAnswer[] = [];\n\n for (const selectedStr of selected) {\n const option = item.answerOption?.find(\n (candidate) => typedValueToString(getItemAnswerOptionValue(candidate)) === selectedStr\n );\n if (option) {\n const optionValue = getItemAnswerOptionValue(option);\n if (optionValue) {\n result.push({ [propertyName]: optionValue.value });\n }\n }\n }\n\n return result;\n}\n\nfunction getByLinkId(\n responseItems: QuestionnaireResponseItem[] | undefined,\n linkId: string\n): QuestionnaireResponseItemAnswer[] | undefined {\n for (const response of responseItems ?? EMPTY) {\n if (response.linkId === linkId) {\n return response.answer;\n }\n if (response.item) {\n const nestedAnswer = getByLinkId(response.item, linkId);\n if (nestedAnswer) {\n return nestedAnswer;\n }\n }\n }\n return undefined;\n}\n\nfunction evaluateMatch(actualAnswer: TypedValue | undefined, expectedAnswer: TypedValue, operator?: string): boolean {\n // We handle exists separately since its so different in terms of comparisons than the other mathematical operators\n if (operator === 'exists') {\n // if actualAnswer is not undefined, then exists: true passes\n // if actualAnswer is undefined, then exists: false passes\n return !!actualAnswer === expectedAnswer.value;\n } else if (!actualAnswer) {\n return false;\n } else {\n // `=` and `!=` should be treated as the FHIRPath `~` and `!~`\n // All other operators should be unmodified\n const fhirPathOperator = operator === '=' || operator === '!=' ? operator?.replace('=', '~') : operator;\n const [{ value }] = evalFhirPathTyped(`%actualAnswer ${fhirPathOperator} %expectedAnswer`, [actualAnswer], {\n '%actualAnswer': actualAnswer,\n '%expectedAnswer': expectedAnswer,\n });\n return value;\n }\n}\n\nfunction checkAnswers(\n enableWhen: QuestionnaireItemEnableWhen,\n answers: QuestionnaireResponseItemAnswer[] | undefined,\n enableBehavior: 'any' | 'all'\n): { anyMatch: boolean; allMatch: boolean } {\n const actualAnswers = answers || [];\n const expectedAnswer = getItemEnableWhenValueAnswer(enableWhen);\n\n let anyMatch = false;\n let allMatch = true;\n\n for (const actualAnswerValue of actualAnswers) {\n const actualAnswer = getResponseItemAnswerValue(actualAnswerValue);\n const { operator } = enableWhen;\n const match = evaluateMatch(actualAnswer, expectedAnswer, operator);\n if (match) {\n anyMatch = true;\n } else {\n allMatch = false;\n }\n\n if (enableBehavior === 'any' && anyMatch) {\n break;\n }\n }\n\n return { anyMatch, allMatch };\n}\n\nexport function getQuestionnaireItemReferenceTargetTypes(item: QuestionnaireItem): ResourceType[] | undefined {\n const extension = getExtension(item, QUESTIONNAIRE_REFERENCE_RESOURCE_URL);\n if (!extension) {\n return undefined;\n }\n if (extension.valueCode !== undefined) {\n return [extension.valueCode] as ResourceType[];\n }\n if (extension.valueCodeableConcept) {\n return extension.valueCodeableConcept?.coding?.map((c) => c.code) as ResourceType[];\n }\n return undefined;\n}\n\nexport function setQuestionnaireItemReferenceTargetTypes(\n item: QuestionnaireItem,\n targetTypes: ResourceType[] | undefined\n): QuestionnaireItem {\n const result = deepClone(item);\n let extension = getExtension(result, QUESTIONNAIRE_REFERENCE_RESOURCE_URL);\n\n if (!targetTypes || targetTypes.length === 0) {\n if (extension) {\n result.extension = result.extension?.filter((e) => e !== extension);\n }\n return result;\n }\n\n if (!extension) {\n result.extension ??= [];\n extension = { url: QUESTIONNAIRE_REFERENCE_RESOURCE_URL };\n result.extension.push(extension);\n }\n\n if (targetTypes.length === 1) {\n extension.valueCode = targetTypes[0];\n delete extension.valueCodeableConcept;\n } else {\n extension.valueCodeableConcept = { coding: targetTypes.map((t) => ({ code: t })) };\n delete extension.valueCode;\n }\n\n return result;\n}\n\n/**\n * Returns the reference filter for the given questionnaire item.\n * @see https://build.fhir.org/ig/HL7/fhir-extensions/StructureDefinition-questionnaire-referenceFilter-definitions.html\n * @param item - The questionnaire item to get the reference filter for.\n * @param subject - Optional subject reference.\n * @param encounter - Optional encounter reference.\n * @returns The reference filter as a map of key/value pairs.\n */\nexport function getQuestionnaireItemReferenceFilter(\n item: QuestionnaireItem,\n subject: Reference | undefined,\n encounter: Reference<Encounter> | undefined\n): Record<string, string> | undefined {\n const extension = getExtension(item, QUESTIONNAIRE_REFERENCE_FILTER_URL);\n if (!extension?.valueString) {\n return undefined;\n }\n\n // Replace variables\n let filter = extension.valueString;\n if (subject?.reference) {\n filter = filter.replaceAll('$subj', subject.reference);\n }\n if (encounter?.reference) {\n filter = filter.replaceAll('$encounter', encounter.reference);\n }\n\n // Parse the valueString into a map\n const result: Record<string, string> = {};\n const parts = filter.split('&');\n for (const part of parts) {\n const [key, value] = splitN(part, '=', 2);\n result[key] = value;\n }\n return result;\n}\n\nexport function buildInitialResponse(\n questionnaire: Questionnaire,\n questionnaireResponse?: QuestionnaireResponse\n): QuestionnaireResponse {\n const response: QuestionnaireResponse = {\n resourceType: 'QuestionnaireResponse',\n questionnaire: questionnaire.url ?? getReferenceString(questionnaire),\n item: buildInitialResponseItems(questionnaire.item, questionnaireResponse?.item),\n status: 'in-progress',\n };\n\n return response;\n}\n\nfunction buildInitialResponseItems(\n items: QuestionnaireItem[] | undefined,\n responseItems: QuestionnaireResponseItem[] | undefined\n): QuestionnaireResponseItem[] | undefined {\n let result: QuestionnaireResponseItem[] | undefined;\n for (const item of items ?? EMPTY) {\n if (item.type === QuestionnaireItemType.display) {\n // Display items do not have response items, so we skip them.\n continue;\n }\n\n const existingResponseItems = responseItems?.filter((responseItem) => responseItem.linkId === item.linkId);\n if (existingResponseItems?.length) {\n for (const existingResponseItem of existingResponseItems) {\n // Update existing response item\n existingResponseItem.id = existingResponseItem.id ?? generateId();\n existingResponseItem.text = existingResponseItem.text ?? item.text;\n existingResponseItem.item = buildInitialResponseItems(item.item, existingResponseItem.item);\n existingResponseItem.answer = buildInitialResponseAnswer(item, existingResponseItem);\n result = append(result, existingResponseItem);\n }\n } else {\n // Add new response item\n result = append(result, buildInitialResponseItem(item));\n }\n }\n\n return result;\n}\n\nexport function buildInitialResponseItem(item: QuestionnaireItem): QuestionnaireResponseItem {\n return {\n id: generateId(),\n linkId: item.linkId,\n text: item.text,\n item: buildInitialResponseItems(item.item, undefined),\n answer: buildInitialResponseAnswer(item),\n };\n}\n\nlet nextId = 1;\nfunction generateId(): string {\n return 'id-' + nextId++;\n}\n\nfunction buildInitialResponseAnswer(\n item: QuestionnaireItem,\n responseItem?: QuestionnaireResponseItem\n): QuestionnaireResponseItemAnswer[] | undefined {\n if (item.type === QuestionnaireItemType.display || item.type === QuestionnaireItemType.group) {\n return undefined;\n }\n\n if (responseItem?.answer && responseItem.answer.length > 0) {\n // If the response item already has answers, return them as is.\n return responseItem.answer;\n }\n\n if (item.initial && item.initial.length > 0) {\n // If the item has initial values, return them as answers.\n // This works because QuestionnaireItemInitial and QuestionnaireResponseItemAnswer\n // have the same properties.\n return item.initial.map((initial) => ({ ...initial }));\n }\n\n if (item.answerOption) {\n return item.answerOption\n .filter((option) => option.initialSelected)\n .map((option) => ({ ...option, initialSelected: undefined }));\n }\n\n // Otherwise, return undefined to indicate no initial answers.\n return undefined;\n}\n\nexport function getItemInitialValue(initial: QuestionnaireItemInitial | undefined): TypedValue {\n return getTypedPropertyValueWithoutSchema(\n { type: 'QuestionnaireItemInitial', value: initial },\n 'value'\n ) as TypedValue;\n}\n\nexport function getItemAnswerOptionValue(option: QuestionnaireItemAnswerOption): TypedValue {\n return getTypedPropertyValueWithoutSchema(\n { type: 'QuestionnaireItemAnswerOption', value: option },\n 'value'\n ) as TypedValue;\n}\n\nexport function getItemEnableWhenValueAnswer(enableWhen: QuestionnaireItemEnableWhen): TypedValue {\n return getTypedPropertyValueWithoutSchema(\n { type: 'QuestionnaireItemEnableWhen', value: enableWhen },\n 'answer'\n ) as TypedValue;\n}\n\nexport function getResponseItemAnswerValue(answer: QuestionnaireResponseItemAnswer): TypedValue | undefined {\n return getTypedPropertyValueWithoutSchema({ type: 'QuestionnaireResponseItemAnswer', value: answer }, 'value') as\n | TypedValue\n | undefined;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { QueryTypes, ResourceArray, WithId } from '@medplum/core';\nimport { allOk, normalizeOperationOutcome } from '@medplum/core';\nimport type { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\nimport { useDebouncedValue } from '../useDebouncedValue/useDebouncedValue';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\nexport type SearchOptions = { debounceMs?: number; enabled?: boolean };\n\nconst DEFAULT_DEBOUNCE_MS = 250;\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @param options - Optional options for configuring the search.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes,\n options?: SearchOptions\n): [Bundle<WithId<ExtractResource<K>>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<WithId<ExtractResource<K>>>>('search', resourceType, query, options);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @param options - Optional options for configuring the search.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes,\n options?: SearchOptions\n): [WithId<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, WithId<ExtractResource<K>>>('searchOne', resourceType, query, options);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @param options - Optional options for configuring the search.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes,\n options?: SearchOptions\n): [ResourceArray<WithId<ExtractResource<K>>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<WithId<ExtractResource<K>>>>('searchResources', resourceType, query, options);\n}\n\nfunction useSearchImpl<K extends ResourceType, SearchReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined,\n options?: SearchOptions\n): [SearchReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [loading, setLoading] = useState(false);\n const [result, setResult] = useState<SearchReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n const searchKey = medplum.fhirSearchUrl(resourceType, query).toString();\n const searchValue = useMemo(\n () => ({\n resourceType,\n query,\n }),\n // This is safe because the missing dependencies are encoded into `searchKey`\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [searchKey]\n );\n\n const enabled = options?.enabled ?? true;\n const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n const [debouncedSearchValue] = useDebouncedValue(searchValue, debounceMs, { leading: true });\n\n useEffect(() => {\n if (!enabled) {\n return () => {};\n }\n\n setLoading(true);\n\n let active = true;\n medplum[searchFn](debouncedSearchValue.resourceType, debouncedSearchValue.query)\n .then((res) => {\n if (active) {\n setLoading(false);\n setResult(res as SearchReturnType);\n setOutcome(allOk);\n }\n })\n .catch((err) => {\n if (active) {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n }\n });\n\n return () => {\n active = false;\n };\n }, [medplum, searchFn, debouncedSearchValue, enabled]);\n\n return [result, loading, outcome];\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\n/*\n This hook was forked from: https://github.com/mantinedev/mantine/blob/fbcee929e0b11782092f48c1e7af2a1d1c878823/packages/%40mantine/hooks/src/use-debounced-value/use-debounced-value.ts\n and has the following license:\n\n MIT License\n\n Copyright (c) 2021 Vitaly Rtishchev\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n*/\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport type UseDebouncedValueOptions = {\n /** Whether the first update to `value` should be immediate or not */\n leading?: boolean;\n};\n\n/**\n * This hook allows users to debounce an incoming value by a specified number of milliseconds.\n *\n * Users can also specify whether the first update to `value` in a sequence of rapid updates should be immediate, by specifying `leading: true` in the options.\n * The default value for `leading` is `false`.\n *\n * The return value is a tuple containing the debounced value at `arr[0]` and a function to cancel the pending debounced value change at `arr[1]`.\n *\n * @param value - The value to debounce.\n * @param waitMs - How long in milliseconds should.\n * @param options - Optional options for configuring the debounce.\n * @returns An array tuple of `[debouncedValue, cancelFn]`.\n */\nexport function useDebouncedValue<T = any>(\n value: T,\n waitMs: number,\n options: UseDebouncedValueOptions = { leading: false }\n): [T, () => void] {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const mountedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n const cooldownRef = useRef(false);\n\n const cancel = useCallback(() => window.clearTimeout(timeoutRef.current), []);\n\n useEffect(() => {\n if (mountedRef.current) {\n if (!cooldownRef.current && options.leading) {\n cooldownRef.current = true;\n setDebouncedValue(value);\n // After waitMs, reset the cooldown\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n }, waitMs);\n } else {\n cancel();\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n setDebouncedValue(value);\n }, waitMs);\n }\n }\n }, [value, options.leading, waitMs, cancel]);\n\n useEffect(() => {\n mountedRef.current = true;\n return cancel;\n }, [cancel]);\n\n return [debouncedValue, cancel] as const;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { isOperationOutcome } from '@medplum/core';\nimport { useCallback } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * Vendor-neutral React hook that syncs a Medplum `PlanDefinition` (type=order-set)\n * to the configured e-prescribing vendor via the `$sync-orderset` custom FHIR operation\n * (`POST /fhir/R4/PlanDefinition/$sync-orderset`).\n *\n * Silently no-ops when the operation is not deployed (i.e. no e-prescribing vendor\n * is configured for the project), so callers do not need to guard against missing\n * integrations.\n *\n * @returns A stable `syncOrderSet(planDefinitionId)` callback.\n */\nexport function useSyncOrderSet(): (planDefinitionId: string) => Promise<void> {\n const medplum = useMedplum();\n\n return useCallback(\n async (planDefinitionId: string): Promise<void> => {\n try {\n await medplum.post(medplum.fhirUrl('PlanDefinition', '$sync-orderset'), { planDefinitionId });\n } catch (err: unknown) {\n // If the operation isn't deployed, silently skip \u2014 project has no e-prescribing vendor configured.\n if (isOperationOutcome(err) && err.issue?.some((i) => i.code === 'not-found')) {\n return;\n }\n throw err;\n }\n },\n [medplum]\n );\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { getReferenceString } from '@medplum/core';\nimport type { Communication } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UseThreadInboxOptions {\n query: string;\n threadId: string | undefined;\n}\n\nexport interface UseThreadInboxReturn {\n loading: boolean;\n error: Error | null;\n // Tuple: [Parent Thread, Last Message in Thread (optional)]\n threadMessages: [Communication, Communication | undefined][];\n selectedThread: Communication | undefined;\n total: number | undefined;\n addThreadMessage: (message: Communication) => void;\n handleThreadStatusChange: (newStatus: Communication['status']) => void;\n refreshThreadMessages: () => Promise<void>;\n}\n\n/*\nuseThreadInbox is a hook that fetches all communications and returns the thread messages and selected thread.\nAll comunications returned do not have a partOf field.\nIt also provides a function to update the status of the selected thread.\n\n@param query - The query to fetch all communications.\n@param threadId - The id of the thread to select.\n@returns The thread messages and selected thread.\n@returns A function to update the status of the selected thread.\n*/\nexport function useThreadInbox({ query, threadId }: UseThreadInboxOptions): UseThreadInboxReturn {\n const medplum = useMedplum();\n const [loading, setLoading] = useState(true);\n const [threadMessages, setThreadMessages] = useState<[Communication, Communication | undefined][]>([]);\n const [selectedThread, setSelectedThread] = useState<Communication | undefined>(undefined);\n const [error, setError] = useState<Error | null>(null);\n const [total, setTotal] = useState<number | undefined>(undefined);\n\n const fetchAllCommunications = useCallback(async (): Promise<void> => {\n const searchParams = new URLSearchParams(query);\n searchParams.append('identifier:not', 'http://medplum.com/ai-message|');\n searchParams.append('part-of:missing', 'true');\n searchParams.append('_has:Communication:part-of:_id:not', 'null');\n\n const bundle = await medplum.search('Communication', searchParams.toString(), { cache: 'no-cache' });\n const parents =\n bundle.entry\n ?.map((entry) => entry.resource as Communication)\n .filter((r): r is Communication => r !== undefined) || [];\n\n if (bundle.total !== undefined) {\n setTotal(bundle.total);\n }\n\n if (parents.length === 0) {\n setThreadMessages([]);\n return;\n }\n\n const queryParts = parents.map((parent) => {\n const safeId = parent.id?.replaceAll('-', '') || '';\n const alias = `thread_${safeId}`;\n const ref = getReferenceString(parent);\n\n return `\n ${alias}: CommunicationList(\n part_of: \"${ref}\"\n _sort: \"-sent\"\n _count: 1\n ) {\n id\n meta {\n lastUpdated\n }\n partOf {\n reference\n }\n sender {\n display\n reference\n }\n payload {\n contentString\n }\n sent\n status\n }\n `;\n });\n\n const fullQuery = `\n query {\n ${queryParts.join('\\n')}\n }\n `;\n\n const response = await medplum.graphql(fullQuery);\n\n const threadsWithReplies = parents\n .map((parent) => {\n const safeId = parent.id?.replaceAll('-', '') || '';\n const alias = `thread_${safeId}`;\n const childList = response.data[alias] as Communication[] | undefined;\n const lastMessage = childList && childList.length > 0 ? childList[0] : undefined;\n return [parent, lastMessage];\n })\n .filter((thread): thread is [Communication, Communication] => thread[1] !== undefined);\n\n setThreadMessages(threadsWithReplies);\n }, [medplum, query]);\n\n useEffect(() => {\n setLoading(true);\n fetchAllCommunications()\n .catch((err: Error) => {\n setError(err);\n })\n .finally(() => {\n setLoading(false);\n });\n }, [fetchAllCommunications]);\n\n useEffect(() => {\n const fetchThread = async (): Promise<void> => {\n if (!threadId) {\n setSelectedThread(undefined);\n return;\n }\n\n const thread = threadMessages.find((t) => t[0].id === threadId);\n if (thread) {\n setSelectedThread(thread[0]);\n return;\n }\n\n const communication: Communication = await medplum.readResource('Communication', threadId);\n if (communication.partOf === undefined) {\n setSelectedThread(communication);\n } else {\n const parentRef = communication.partOf[0].reference;\n if (parentRef) {\n const parent = await medplum.readReference({ reference: parentRef });\n setSelectedThread(parent as Communication);\n }\n }\n };\n\n fetchThread().catch((err: Error) => {\n setError(err);\n });\n }, [threadId, threadMessages, medplum]);\n\n const handleThreadStatusChange = (newStatus: Communication['status']): void => {\n if (!selectedThread) {\n return;\n }\n const doUpdate = async (): Promise<void> => {\n const updatedThread = await medplum.updateResource({ ...selectedThread, status: newStatus });\n setSelectedThread(updatedThread);\n setThreadMessages((prev) =>\n prev.map(([parent, lastMsg]) => (parent.id === updatedThread.id ? [updatedThread, lastMsg] : [parent, lastMsg]))\n );\n };\n doUpdate().catch((err: Error) => setError(err));\n };\n\n const addThreadMessage = (message: Communication): void => {\n const doAdd = async (): Promise<void> => {\n await fetchAllCommunications();\n setThreadMessages((prev) => [[message, undefined], ...prev]);\n };\n doAdd().catch((err: Error) => setError(err));\n };\n\n return {\n loading,\n error,\n threadMessages,\n selectedThread,\n total,\n addThreadMessage,\n handleThreadStatusChange,\n refreshThreadMessages: fetchAllCommunications,\n };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n// Adapted from https://github.com/codyebberson/medplum-ai-realtime/blob/main/src/hooks/useWhisper.ts\nimport { ReconnectingWebSocket } from '@medplum/core';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport type WhisperStatus =\n | 'idle'\n | 'requesting_microphone'\n | 'connecting'\n | 'connected'\n | 'listening'\n | 'speech_started'\n | 'speech_stopped'\n | 'disconnected'\n | 'error';\n\nexport type TranscriptItem = {\n text: string;\n timestamp: string;\n};\n\nexport type UseWhisperOptions = {\n language?: string;\n model?: string;\n onTranscript?: (text: string) => void;\n};\n\nexport type UseWhisperResult = {\n status: WhisperStatus;\n error: unknown;\n transcripts: TranscriptItem[];\n start: () => Promise<void>;\n stop: () => void;\n isListening: boolean;\n};\n\nexport function useWhisper({\n language = 'en',\n model = 'gpt-4o-transcribe',\n onTranscript,\n}: UseWhisperOptions): UseWhisperResult {\n const medplum = useMedplum();\n const onTranscriptRef = useRef(onTranscript);\n useEffect(() => {\n onTranscriptRef.current = onTranscript;\n }, [onTranscript]);\n const [status, setStatus] = useState<WhisperStatus>('idle');\n const [error, setError] = useState<unknown>(undefined);\n const [transcripts, setTranscripts] = useState<TranscriptItem[]>([]);\n\n const websocketRef = useRef<ReconnectingWebSocket | undefined>(undefined);\n const audioStreamRef = useRef<MediaStream | undefined>(undefined);\n const audioContextRef = useRef<AudioContext | undefined>(undefined);\n const audioProcessorRef = useRef<ScriptProcessorNode | undefined>(undefined);\n\n const stop = useCallback(() => {\n audioProcessorRef.current?.disconnect();\n audioProcessorRef.current = undefined;\n\n audioContextRef.current?.close().catch(() => undefined);\n audioContextRef.current = undefined;\n\n audioStreamRef.current?.getTracks().forEach((track) => track.stop());\n audioStreamRef.current = undefined;\n\n websocketRef.current?.close();\n websocketRef.current = undefined;\n\n setStatus('disconnected');\n }, []);\n\n const setupSession = useCallback(() => {\n websocketRef.current?.send(\n JSON.stringify({\n type: 'session.update',\n session: {\n type: 'transcription',\n audio: {\n input: {\n format: {\n type: 'audio/pcm',\n rate: 24000,\n },\n transcription: {\n model,\n language,\n },\n turn_detection: {\n type: 'server_vad',\n threshold: 0.5,\n prefix_padding_ms: 300,\n silence_duration_ms: 200,\n },\n noise_reduction: {\n type: 'near_field',\n },\n },\n },\n },\n })\n );\n }, [language, model]);\n\n const startAudioCapture = useCallback(() => {\n const audioStream = audioStreamRef.current;\n const websocket = websocketRef.current;\n\n if (!audioStream || !websocket) {\n return;\n }\n\n const audioContext = new AudioContext({ sampleRate: 24000 });\n const source = audioContext.createMediaStreamSource(audioStream);\n const processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (event) => {\n if (websocket.readyState !== WebSocket.OPEN) {\n console.warn('WebSocket is not open. Unable to send audio data.');\n return;\n }\n\n const inputBuffer = event.inputBuffer.getChannelData(0);\n const pcm16Buffer = convertToPCM16(inputBuffer);\n const base64Audio = btoa(String.fromCharCode(...pcm16Buffer));\n\n websocket.send(\n JSON.stringify({\n type: 'input_audio_buffer.append',\n audio: base64Audio,\n })\n );\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n\n audioContextRef.current = audioContext;\n audioProcessorRef.current = processor;\n\n setStatus('listening');\n }, []);\n\n const handleMessage = useCallback(\n (message: any) => {\n switch (message.type) {\n case 'session.created':\n setupSession();\n break;\n\n case 'session.updated':\n startAudioCapture();\n break;\n\n case 'input_audio_buffer.speech_started':\n setStatus('speech_started');\n break;\n\n case 'input_audio_buffer.speech_stopped':\n setStatus('speech_stopped');\n break;\n\n case 'conversation.item.input_audio_transcription.completed':\n case 'input_audio_transcription.completed':\n if (message.transcript) {\n const item = {\n text: message.transcript,\n timestamp: new Date().toISOString(),\n };\n\n setTranscripts((prev) => [...prev, item]);\n onTranscriptRef.current?.(message.transcript);\n }\n break;\n\n case 'ai-realtime:connected':\n console.debug('[useWhisper] upstream connected');\n break;\n\n case 'ai-realtime:error':\n case 'error':\n console.error('[useWhisper] error event', message);\n setError(message);\n setStatus('error');\n break;\n\n default:\n console.debug('[useWhisper] unhandled message', message.type, message);\n break;\n }\n },\n [setupSession, startAudioCapture]\n );\n\n const acquireMicrophone = useCallback(async (): Promise<MediaStream> => {\n setStatus('requesting_microphone');\n const audioStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: 24000,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n });\n audioStreamRef.current = audioStream;\n return audioStream;\n }, []);\n\n const openWebSocket = useCallback((): ReconnectingWebSocket => {\n setStatus('connecting');\n const url = buildWebSocketUrl(medplum.getBaseUrl());\n console.debug('[useWhisper] connecting to', url);\n const websocket = new ReconnectingWebSocket(url);\n websocketRef.current = websocket;\n\n websocket.onopen = () => setStatus('connected');\n websocket.onmessage = (event) => handleMessage(JSON.parse(event.data));\n websocket.onerror = (err) => {\n setError(err);\n setStatus('error');\n stop();\n };\n websocket.onclose = () => setStatus('disconnected');\n\n websocket.send(\n JSON.stringify({\n type: 'ai-realtime:connect',\n accessToken: medplum.getAccessToken(),\n })\n );\n return websocket;\n }, [medplum, handleMessage, stop]);\n\n const start = useCallback(async () => {\n try {\n setError(undefined);\n await acquireMicrophone();\n openWebSocket();\n } catch (err) {\n setError(err);\n setStatus('error');\n stop();\n }\n }, [acquireMicrophone, openWebSocket, stop]);\n\n useEffect(() => {\n return () => stop();\n }, [stop]);\n\n return {\n status,\n error,\n transcripts,\n start,\n stop,\n isListening: status === 'listening' || status === 'speech_started' || status === 'speech_stopped',\n };\n}\n\nexport function convertToPCM16(float32Array: Float32Array): Uint8Array {\n const pcm16Array = new Int16Array(float32Array.length);\n\n for (let i = 0; i < float32Array.length; i++) {\n const sample = Math.max(-1, Math.min(1, float32Array[i]));\n pcm16Array[i] = sample * 0x7fff;\n }\n\n return new Uint8Array(pcm16Array.buffer);\n}\n\nfunction buildWebSocketUrl(baseUrl: string): string {\n const url = new URL('ws/ai-realtime', baseUrl);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n return url.toString();\n}\n"],
5
- "mappings": "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",
6
- "names": ["index_exports", "__export", "MedplumProvider", "QUESTIONNAIRE_CALCULATED_EXPRESSION_URL", "QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL", "QUESTIONNAIRE_HIDDEN_URL", "QUESTIONNAIRE_ITEM_CONTROL_URL", "QUESTIONNAIRE_REFERENCE_FILTER_URL", "QUESTIONNAIRE_REFERENCE_RESOURCE_URL", "QUESTIONNAIRE_SIGNATURE_REQUIRED_URL", "QUESTIONNAIRE_SIGNATURE_RESPONSE_URL", "QUESTIONNAIRE_VALIDATION_ERROR_URL", "QuestionnaireItemType", "buildInitialResponse", "buildInitialResponseItem", "convertToPCM16", "evaluateCalculatedExpressionsInQuestionnaire", "getItemAnswerOptionValue", "getItemEnableWhenValueAnswer", "getItemInitialValue", "getNewMultiSelectValues", "getQuestionnaireItemReferenceFilter", "getQuestionnaireItemReferenceTargetTypes", "getResponseItemAnswerValue", "isChoiceQuestion", "isQuestionEnabled", "reactContext", "removeDisabledItems", "setQuestionnaireItemReferenceTargetTypes", "typedValueToResponseItem", "useCachedBinaryUrl", "useMedicationIFrame", "useMedicationOrder", "useMedicationOrderSet", "useMedplum", "useMedplumContext", "useMedplumNavigate", "useMedplumProfile", "useNotificationCount", "usePatientSummaryData", "usePharmacySearch", "usePrevious", "useQuestionnaireForm", "useResource", "useSearch", "useSearchOne", "useSearchResources", "useSubscription", "useSyncOrderSet", "useThreadInbox", "useWhisper", "__toCommonJS", "import_react", "import_react", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "import_jsx_runtime", "EVENTS_TO_TRACK", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "eventListener", "s", "event", "medplumContext", "reactContext", "path", "import_react", "urls", "useCachedBinaryUrl", "binaryUrl", "binaryResourceUrl", "binaryUrlSearchParams", "binaryUrlExpires", "cachedUrl", "expires", "import_react", "useMedicationIFrame", "syncBotIdentifier", "iframeBotIdentifier", "options", "medplum", "useMedplum", "patientId", "onPatientSyncSuccess", "onIframeSuccess", "onError", "iframeUrl", "setIframeUrl", "onPatientSyncSuccessRef", "onIframeSuccessRef", "onErrorRef", "cancelled", "result", "err", "import_core", "import_react", "useMedicationOrder", "medplum", "useMedplum", "searchMedications", "params", "url", "body", "response", "e", "r", "orderMedication", "input", "import_core", "import_react", "useMedicationOrderSet", "options", "medplum", "useMedplum", "patientId", "planDefinitionId", "vendorOrderSetId", "appId", "url", "setUrl", "loading", "setLoading", "error", "setError", "optionsRef", "buildRequest", "o", "hasPd", "hasVendorId", "callOperation", "req", "operationUrl", "body", "response", "runIdRef", "refresh", "myRunId", "next", "err", "cancelled", "import_react", "import_core", "import_react", "SUBSCRIPTION_DEBOUNCE_MS", "useSubscription", "criteria", "callback", "options", "medplum", "useMedplum", "effectiveCriteria", "useMedplumProfile", "emitter", "setEmitter", "memoizedSubProps", "setMemoizedSubProps", "listeningRef", "unsubTimerRef", "prevCriteriaRef", "prevMemoizedSubPropsRef", "callbackRef", "onWebSocketOpenRef", "onWebSocketCloseRef", "onSubscriptionConnectRef", "onSubscriptionDisconnectRef", "onErrorRef", "shouldSubscribe", "emitterCallback", "event", "onWebSocketOpen", "onWebSocketClose", "onSubscriptionConnect", "onSubscriptionDisconnect", "onError", "useNotificationCount", "options", "medplum", "useMedplum", "resourceType", "countCriteria", "subscriptionCriteria", "count", "setCount", "updateCount", "cache", "result", "useSubscription", "import_core", "import_react", "buildSearchKey", "search", "param", "query", "queryStr", "entries", "a", "b", "sorted", "v", "buildSectionsFingerprint", "sections", "s", "searchKeys", "usePatientSummaryData", "patient", "medplum", "useMedplum", "sectionData", "setSectionData", "loading", "setLoading", "error", "setError", "sectionsFingerprint", "stableSections", "patientId", "stale", "ref", "searchMeta", "uniqueSearches", "searchKeyToIndex", "sectionSearchMapping", "section", "mapping", "deduplicationKey", "idx", "promises", "patientParam", "baseQuery", "value", "key", "settledResults", "data", "sectionResult", "searchIdx", "resultKey", "settled", "failures", "r", "f", "firstError", "err", "import_core", "import_react", "usePharmacySearch", "searchBotIdentifier", "addPharmacyBotIdentifier", "medplum", "useMedplum", "searchPharmacies", "params", "response", "addToFavorites", "import_react", "usePrevious", "value", "ref", "import_core", "import_react", "import_core", "import_react", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "getInitialResource", "setResourceIfChanged", "r", "subscribed", "newValue", "err", "import_core", "QuestionnaireItemType", "QUESTIONNAIRE_ITEM_CONTROL_URL", "QUESTIONNAIRE_REFERENCE_FILTER_URL", "QUESTIONNAIRE_REFERENCE_RESOURCE_URL", "QUESTIONNAIRE_VALIDATION_ERROR_URL", "QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL", "QUESTIONNAIRE_CALCULATED_EXPRESSION_URL", "QUESTIONNAIRE_SIGNATURE_REQUIRED_URL", "QUESTIONNAIRE_SIGNATURE_RESPONSE_URL", "QUESTIONNAIRE_HIDDEN_URL", "isChoiceQuestion", "item", "removeDisabledItems", "questionnaire", "response", "filterEnabledResponseItems", "items", "responseItems", "result", "responseItem", "i", "isQuestionEnabled", "questionnaireResponse", "extensionResult", "isQuestionEnabledViaExtension", "isQuestionEnabledViaEnabledWhen", "extension", "expression", "value", "enableBehavior", "enableWhen", "actualAnswers", "getByLinkId", "anyMatch", "allMatch", "checkAnswers", "evaluateCalculatedExpressionsInQuestionnaire", "r", "evaluateQuestionnaireItemCalculatedExpressions", "calculatedValue", "evaluateCalculatedExpression", "answer", "typedValueToResponseItem", "error", "questionnaireItemTypesAllowedPropertyTypes", "getNewMultiSelectValues", "selected", "propertyName", "selectedStr", "option", "candidate", "getItemAnswerOptionValue", "optionValue", "linkId", "nestedAnswer", "evaluateMatch", "actualAnswer", "expectedAnswer", "operator", "fhirPathOperator", "answers", "getItemEnableWhenValueAnswer", "actualAnswerValue", "getResponseItemAnswerValue", "getQuestionnaireItemReferenceTargetTypes", "c", "setQuestionnaireItemReferenceTargetTypes", "targetTypes", "e", "t", "getQuestionnaireItemReferenceFilter", "subject", "encounter", "filter", "parts", "part", "key", "buildInitialResponse", "buildInitialResponseItems", "existingResponseItems", "existingResponseItem", "generateId", "buildInitialResponseAnswer", "buildInitialResponseItem", "nextId", "initial", "getItemInitialValue", "useQuestionnaireForm", "props", "questionnaire", "useResource", "defaultResponse", "forceUpdate", "x", "state", "getPages", "buildInitialResponse", "emitChange", "getResponseItemByContext", "context", "item", "currentItem", "contextElement", "i", "onNextPage", "onPrevPage", "onAddGroup", "responseItem", "buildInitialResponseItem", "onAddAnswer", "onChangeAnswer", "answer", "onChangeSignature", "signature", "currentResponse", "ext", "QUESTIONNAIRE_SIGNATURE_RESPONSE_URL", "updateCalculatedExpressions", "response", "evaluateCalculatedExpressionsInQuestionnaire", "currentQuestionnaire", "removeDisabledItems", "getItemsForPage", "getResponseItemsForPage", "QUESTIONNAIRE_ITEM_CONTROL_URL", "index", "pages", "activePage", "questionnaireResponse", "import_core", "import_react", "import_react", "useDebouncedValue", "value", "waitMs", "options", "debouncedValue", "setDebouncedValue", "mountedRef", "timeoutRef", "cooldownRef", "cancel", "DEFAULT_DEBOUNCE_MS", "useSearch", "resourceType", "query", "options", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "searchKey", "searchValue", "enabled", "debounceMs", "debouncedSearchValue", "useDebouncedValue", "active", "res", "err", "import_core", "import_react", "useSyncOrderSet", "medplum", "useMedplum", "planDefinitionId", "err", "i", "import_core", "import_react", "useThreadInbox", "query", "threadId", "medplum", "useMedplum", "loading", "setLoading", "threadMessages", "setThreadMessages", "selectedThread", "setSelectedThread", "error", "setError", "total", "setTotal", "fetchAllCommunications", "searchParams", "bundle", "parents", "entry", "r", "fullQuery", "parent", "alias", "ref", "response", "threadsWithReplies", "childList", "lastMessage", "thread", "err", "t", "communication", "parentRef", "message", "prev", "newStatus", "updatedThread", "lastMsg", "import_core", "import_react", "useWhisper", "language", "model", "onTranscript", "medplum", "useMedplum", "onTranscriptRef", "status", "setStatus", "error", "setError", "transcripts", "setTranscripts", "websocketRef", "audioStreamRef", "audioContextRef", "audioProcessorRef", "stop", "track", "setupSession", "startAudioCapture", "audioStream", "websocket", "audioContext", "source", "processor", "event", "inputBuffer", "pcm16Buffer", "convertToPCM16", "base64Audio", "handleMessage", "message", "item", "prev", "acquireMicrophone", "openWebSocket", "url", "buildWebSocketUrl", "err", "start", "float32Array", "pcm16Array", "i", "sample", "baseUrl"]
4
+ "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nexport * from './MedplumProvider/MedplumProvider';\nexport * from './MedplumProvider/MedplumProvider.context';\nexport * from './useCachedBinaryUrl/useCachedBinaryUrl';\nexport * from './useMedicationIFrame/useMedicationIFrame';\nexport * from './useMedicationOrder/useMedicationOrder';\nexport * from './useMedicationOrderSet/useMedicationOrderSet';\nexport * from './useNotificationCount/useNotificationCount';\nexport * from './usePatientSummaryData/usePatientSummaryData';\nexport * from './usePharmacySearch/usePharmacySearch';\nexport * from './usePrevious/usePrevious';\nexport * from './useQuestionnaireForm/useQuestionnaireForm';\nexport * from './useQuestionnaireForm/utils';\nexport * from './useResource/useResource';\nexport * from './useSearch/useSearch';\nexport * from './useSubscription/useSubscription';\nexport * from './useSyncOrderSet/useSyncOrderSet';\nexport * from './useThreadInbox/useThreadInbox';\nexport * from './useWhisper/useWhisper';\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport type { JSX, ReactNode } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\nimport type { MedplumNavigateFunction } from './MedplumProvider.context';\nimport { reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n readonly medplum: MedplumClient;\n readonly navigate?: MedplumNavigateFunction;\n readonly children: ReactNode;\n}\n\nconst EVENTS_TO_TRACK = [\n 'change',\n 'storageInitialized',\n 'storageInitFailed',\n 'profileRefreshing',\n 'profileRefreshed',\n] satisfies (keyof MedplumClientEventMap)[];\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props - The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState((s) => ({\n ...s,\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n }));\n }\n\n for (const event of EVENTS_TO_TRACK) {\n medplum.addEventListener(event, eventListener);\n }\n return () => {\n for (const event of EVENTS_TO_TRACK) {\n medplum.removeEventListener(event, eventListener);\n }\n };\n }, [medplum]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path - The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MedplumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MedplumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MedplumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useMemo } from 'react';\n\n// Maintain a cache of urls to avoid unnecessary re-download of attachments\n// The following is a workaround for the fact that each request to a resource containing a Binary data reference\n// returns a NEW signed S3 URL for each bypassing the native browser caching mechanism\n// resulting in unnecessary bandwidth consumption.\n// https://www.medplum.com/docs/fhir-datastore/binary-data#consuming-a-fhir-binary-in-an-application\n// https://github.com/medplum/medplum/issues/3815\n\n// The S3 presigned URLs expire after 1 hour with the default configuration and hard refreshes are not uncommon even in SPAs so this\n// could be a good way to get additional cache hits\n// This would require additional logic for initialization, saving, and purging of expired keys\nconst urls = new Map<string, string>();\n\nexport const useCachedBinaryUrl = (binaryUrl: string | undefined): string | undefined => {\n return useMemo(() => {\n if (!binaryUrl) {\n return undefined;\n }\n\n const binaryResourceUrl = binaryUrl.split('?')[0];\n if (!binaryResourceUrl) {\n return binaryUrl;\n }\n\n // Check if the binaryUrl is a presigned S3 URL\n let binaryUrlSearchParams: URLSearchParams;\n try {\n binaryUrlSearchParams = new URLSearchParams(new URL(binaryUrl).search);\n } catch (_err) {\n return binaryUrl;\n }\n\n if (!binaryUrlSearchParams.has('Key-Pair-Id') || !binaryUrlSearchParams.has('Signature')) {\n return binaryUrl;\n }\n\n // https://stackoverflow.com/questions/23929145/how-to-test-if-a-given-time-stamp-is-in-seconds-or-milliseconds\n const binaryUrlExpires = binaryUrlSearchParams.get('Expires');\n if (!binaryUrlExpires || binaryUrlExpires.length > 13) {\n // Expires is expected to be in seconds, not milliseconds\n return binaryUrl;\n }\n\n const cachedUrl = urls.get(binaryResourceUrl);\n if (cachedUrl) {\n const searchParams = new URLSearchParams(new URL(cachedUrl).search);\n\n // This is fairly brittle as it relies on the current structure of the Medplum returned URL\n const expires = searchParams.get('Expires');\n\n // `expires` is in seconds, Date.now() is in ms\n // Add padding to mitigate expiration between time of check and time of use\n if (expires && Number.parseInt(expires, 10) * 1000 - 5_000 > Date.now()) {\n return cachedUrl;\n }\n }\n\n urls.set(binaryResourceUrl, binaryUrl);\n return binaryUrl;\n }, [binaryUrl]);\n};\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Identifier } from '@medplum/fhirtypes';\nimport { useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface MedicationIFrameOptions {\n readonly patientId?: string;\n readonly onPatientSyncSuccess?: () => void;\n readonly onIframeSuccess?: (url: string) => void;\n readonly onError?: (err: unknown) => void;\n}\n\n/**\n * Generic React hook that syncs a patient to a medication-order vendor and\n * returns the chart iframe URL.\n *\n * Executes the patient-sync bot first (if patientId is provided), then\n * the iframe bot to obtain the prescribing UI URL.\n *\n * Uses an effect cleanup flag so React 18 Strict Mode double-mount does not\n * trigger duplicate bot executions.\n *\n * @param syncBotIdentifier - Bot identifier for the patient sync bot.\n * @param iframeBotIdentifier - Bot identifier for the iframe URL bot.\n * @param options - Configuration and callback options.\n * @returns The medication-order iframe URL, or undefined while loading.\n */\nexport function useMedicationIFrame(\n syncBotIdentifier: Identifier,\n iframeBotIdentifier: Identifier,\n options: MedicationIFrameOptions\n): string | undefined {\n const medplum = useMedplum();\n const { patientId, onPatientSyncSuccess, onIframeSuccess, onError } = options;\n const [iframeUrl, setIframeUrl] = useState<string | undefined>(undefined);\n\n const onPatientSyncSuccessRef = useRef(onPatientSyncSuccess);\n const onIframeSuccessRef = useRef(onIframeSuccess);\n const onErrorRef = useRef(onError);\n\n useEffect(() => {\n onPatientSyncSuccessRef.current = onPatientSyncSuccess;\n onIframeSuccessRef.current = onIframeSuccess;\n onErrorRef.current = onError;\n }, [onPatientSyncSuccess, onIframeSuccess, onError]);\n\n useEffect(() => {\n let cancelled = false;\n\n const run = async (): Promise<void> => {\n try {\n if (patientId) {\n await medplum.executeBot(syncBotIdentifier, { patientId });\n if (cancelled) {\n return;\n }\n onPatientSyncSuccessRef.current?.();\n }\n const result = await medplum.executeBot(iframeBotIdentifier, { patientId });\n if (cancelled) {\n return;\n }\n if (result.url) {\n setIframeUrl(result.url);\n onIframeSuccessRef.current?.(result.url);\n }\n } catch (err: unknown) {\n if (!cancelled) {\n onErrorRef.current?.(err);\n }\n }\n };\n\n run().catch(() => {\n // Handled via onErrorRef when !cancelled\n });\n\n return (): void => {\n cancelled = true;\n };\n }, [medplum, syncBotIdentifier, iframeBotIdentifier, patientId]);\n\n return iframeUrl;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { MedicationOrderRequest, MedicationOrderResponse, MedicationSearchParams } from '@medplum/core';\nimport {\n INVALID_MEDICATION_ORDER_RESPONSE,\n INVALID_MEDICATION_SEARCH_RESPONSE,\n isResource,\n medicationOrderRequestToParameters,\n medicationSearchParamsToParameters,\n parametersToMedicationOrderResponse,\n} from '@medplum/core';\nimport type { Bundle, Medication, Parameters } from '@medplum/fhirtypes';\nimport { useCallback } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UseMedicationOrderReturn {\n searchMedications: (params: MedicationSearchParams) => Promise<Medication[]>;\n orderMedication: (input: MedicationOrderRequest) => Promise<MedicationOrderResponse>;\n}\n\n/**\n * Vendor-neutral hook for e-prescribing drug search and order-medication via\n * **FHIR custom operations** (no Bot identifiers required).\n *\n * Hits two project-scoped operations whose backing Bot is chosen at deploy time\n * via an `OperationDefinition` resource carrying the\n * `operationDefinition-implementation` extension \u2014 see\n * [bot operations docs](https://www.medplum.com/docs/bots/custom-fhir-operations).\n * The server's `tryCustomOperation` dispatch handles the OD \u2192 Bot lookup, so\n * projects can swap vendors (ScriptSure today, DoseSpot tomorrow) by deploying\n * a different bot under the same operation code.\n *\n * - `searchMedications`: `POST /fhir/R4/Medication/$drug-search` \u2014 expects the\n * bot to return a `Bundle<Medication>` (single-Resource `return` shortcut on\n * the OperationDefinition).\n * - `orderMedication`: `POST /fhir/R4/MedicationRequest/$order-medication` \u2014\n * expects the bot to return a `Parameters` envelope with named primitives;\n * `parametersToMedicationOrderResponse` decodes it.\n *\n * @returns Callbacks to search medications and submit an order request.\n */\nexport function useMedicationOrder(): UseMedicationOrderReturn {\n const medplum = useMedplum();\n\n const searchMedications = useCallback(\n async (params: MedicationSearchParams): Promise<Medication[]> => {\n const url = medplum.fhirUrl('Medication', '$drug-search');\n const body = medicationSearchParamsToParameters(params);\n const response = await medplum.post(url, body);\n if (!isResource<Bundle<Medication>>(response, 'Bundle')) {\n throw new Error(INVALID_MEDICATION_SEARCH_RESPONSE);\n }\n return (response.entry ?? [])\n .map((e) => e.resource)\n .filter((r): r is Medication => isResource<Medication>(r, 'Medication'));\n },\n [medplum]\n );\n\n const orderMedication = useCallback(\n async (input: MedicationOrderRequest): Promise<MedicationOrderResponse> => {\n const url = medplum.fhirUrl('MedicationRequest', '$order-medication');\n const body = medicationOrderRequestToParameters(input);\n const response = await medplum.post(url, body);\n if (!isResource<Parameters>(response, 'Parameters')) {\n // Server returned something other than a Parameters envelope. Surface the\n // existing INVALID_MEDICATION_ORDER_RESPONSE so callers (e.g. the Provider\n // App's draft-MR cleanup branch) keep their single error-handling path.\n throw new Error(INVALID_MEDICATION_ORDER_RESPONSE);\n }\n return parametersToMedicationOrderResponse(response);\n },\n [medplum]\n );\n\n return { searchMedications, orderMedication };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedicationOrderSetRequest } from '@medplum/core';\nimport {\n INVALID_MEDICATION_ORDER_SET_RESPONSE,\n isResource,\n medicationOrderSetRequestToParameters,\n parametersToMedicationOrderSetResponse,\n} from '@medplum/core';\nimport type { Parameters } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UseMedicationOrderSetOptions {\n /** Patient to prescribe against. Hook stays idle (no operation call) until set. */\n readonly patientId: string | undefined;\n /** Medplum PlanDefinition id (vendor-neutral). Bot resolves it to the vendor's order set id. */\n readonly planDefinitionId?: string;\n /** Vendor-side order set id, when picked directly (escape hatch when no synced PD exists yet). */\n readonly vendorOrderSetId?: number | string;\n readonly appId?: string;\n}\n\nexport interface UseMedicationOrderSetReturn {\n /** Most recent successful URL from the order-set operation, or undefined while loading / on error. */\n readonly url: string | undefined;\n /** True while a request is in flight. */\n readonly loading: boolean;\n /** Last error from the operation call, or undefined. */\n readonly error: unknown;\n /**\n * Force a re-fetch using the current options. Useful when wiring\n * `PrescriptionIFrameModal.onRefreshLaunchUrl` so the session token in\n * the returned widget URL is fresh on every modal open.\n */\n readonly refresh: () => Promise<string | undefined>;\n}\n\n/**\n * Vendor-neutral React hook that calls the `$order-set-url` custom FHIR\n * operation and exposes the resulting iframe URL plus refresh/loading/error\n * state.\n *\n * Hits the project-scoped operation whose backing bot is chosen at deploy time\n * via an `OperationDefinition` resource carrying the\n * `operationDefinition-implementation` extension \u2014 see\n * [bot operations docs](https://www.medplum.com/docs/bots/custom-fhir-operations).\n * The server's `tryCustomOperation` dispatch handles the OD \u2192 Bot lookup, so\n * projects can swap vendors (ScriptSure today, DoseSpot tomorrow) by deploying\n * a different bot under the same operation code.\n *\n * - URL: `POST /fhir/R4/PlanDefinition/$order-set-url`\n * - Body: `Parameters` with `patientId` + (`planDefinitionId` XOR `vendorOrderSetId`) + optional `appId`.\n * - Returns: `Parameters` whose `launchUrl` is exposed as `url` on the hook.\n *\n * The hook is a \"build a URL\" hook (mirrors {@link useMedicationIFrame}); it\n * does not stamp Medplum resources or create vendor-side resources, so\n * `refresh` is safe to call repeatedly (the operation is naturally idempotent).\n *\n * Re-runs whenever the input options change. In-flight calls are cancelled on\n * input change and on unmount via a per-effect `cancelled` flag, so React 18\n * Strict Mode double-mount does not surface stale URLs.\n *\n * @param options - Patient, picker (PD or vendor id), and optional appId.\n * @returns `{ url, loading, error, refresh }`.\n */\nexport function useMedicationOrderSet(options: UseMedicationOrderSetOptions): UseMedicationOrderSetReturn {\n const medplum = useMedplum();\n const { patientId, planDefinitionId, vendorOrderSetId, appId } = options;\n\n const [url, setUrl] = useState<string | undefined>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>(undefined);\n\n // Latest options snapshot so `refresh` always uses current values without\n // re-creating the callback when only the inputs change.\n const optionsRef = useRef({ patientId, planDefinitionId, vendorOrderSetId, appId });\n optionsRef.current = { patientId, planDefinitionId, vendorOrderSetId, appId };\n\n const buildRequest = (): MedicationOrderSetRequest | undefined => {\n const o = optionsRef.current;\n if (!o.patientId) {\n return undefined;\n }\n const hasPd = Boolean(o.planDefinitionId);\n const hasVendorId = o.vendorOrderSetId !== undefined && o.vendorOrderSetId !== null && o.vendorOrderSetId !== '';\n if (!hasPd && !hasVendorId) {\n return undefined;\n }\n const req: MedicationOrderSetRequest = {\n patientId: o.patientId,\n planDefinitionId: hasPd ? o.planDefinitionId : undefined,\n vendorOrderSetId: hasVendorId ? o.vendorOrderSetId : undefined,\n appId: o.appId,\n };\n return req;\n };\n\n const callOperation = useCallback(\n async (req: MedicationOrderSetRequest): Promise<string | undefined> => {\n const operationUrl = medplum.fhirUrl('PlanDefinition', '$order-set-url');\n const body = medicationOrderSetRequestToParameters(req);\n const response = await medplum.post(operationUrl, body);\n if (!isResource<Parameters>(response, 'Parameters')) {\n throw new Error(INVALID_MEDICATION_ORDER_SET_RESPONSE);\n }\n const decoded = parametersToMedicationOrderSetResponse(response);\n return decoded.launchUrl;\n },\n [medplum]\n );\n\n // `refresh()` and the input-driven effect share a monotonically-incrementing\n // run id (`runIdRef`). Every fetch \u2014 whether kicked off by the effect or by\n // a `refresh()` call \u2014 captures its own `myRunId` at start, and only writes\n // state if `runIdRef.current === myRunId` when the fetch resolves. The\n // effect's per-run `cancelled` flag also short-circuits on unmount / deps\n // change. This gives `refresh()` the same cancellation semantics as the\n // effect without the act+await deadlock that a resolver-queue approach\n // creates (a refresh promise that depends on a nonce-triggered effect\n // re-render cannot resolve while React's `act` is awaiting the refresh).\n // (PR https://github.com/medplum/medplum/pull/8999#discussion_r3277116116)\n const runIdRef = useRef(0);\n\n const refresh = useCallback(async (): Promise<string | undefined> => {\n const req = buildRequest();\n if (!req) {\n return undefined;\n }\n runIdRef.current += 1;\n const myRunId = runIdRef.current;\n setLoading(true);\n setError(undefined);\n try {\n const next = await callOperation(req);\n if (runIdRef.current !== myRunId) {\n // A newer run started while we were in flight \u2014 drop the result so\n // neither the state nor the returned promise can clobber the newer\n // fetch's URL.\n return undefined;\n }\n setUrl(next);\n return next;\n } catch (err: unknown) {\n if (runIdRef.current === myRunId) {\n setError(err);\n setUrl(undefined);\n }\n return undefined;\n } finally {\n if (runIdRef.current === myRunId) {\n setLoading(false);\n }\n }\n }, [callOperation]);\n\n useEffect(() => {\n let cancelled = false;\n const req = buildRequest();\n if (!req) {\n // Inputs incomplete \u2014 clear any stale URL so the consumer doesn't\n // keep showing a URL that no longer matches the current selection.\n runIdRef.current += 1;\n setUrl(undefined);\n setLoading(false);\n setError(undefined);\n return undefined;\n }\n\n runIdRef.current += 1;\n const myRunId = runIdRef.current;\n setLoading(true);\n setError(undefined);\n\n callOperation(req)\n .then((next) => {\n if (cancelled || runIdRef.current !== myRunId) {\n return;\n }\n setUrl(next);\n })\n .catch((err: unknown) => {\n if (cancelled || runIdRef.current !== myRunId) {\n return;\n }\n setError(err);\n setUrl(undefined);\n })\n .finally(() => {\n if (!cancelled && runIdRef.current === myRunId) {\n setLoading(false);\n }\n });\n\n return (): void => {\n cancelled = true;\n };\n }, [callOperation, patientId, planDefinitionId, vendorOrderSetId, appId]);\n\n return { url, loading, error, refresh };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { ResourceType } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\nimport { useSubscription } from '../useSubscription/useSubscription';\n\nexport interface UseNotificationCountOptions {\n readonly resourceType: ResourceType;\n readonly countCriteria: string;\n readonly subscriptionCriteria: string;\n}\n\n/**\n * Returns a live notification count for a given resource type.\n *\n * Uses `medplum.search()` for the initial count (with default cache policy) and\n * subscribes to real-time updates via `useSubscription()`, re-fetching with\n * `cache: 'reload'` whenever a matching event arrives.\n *\n * @param options - The resource type, count search criteria, and subscription criteria.\n * @returns The current notification count.\n */\nexport function useNotificationCount(options: UseNotificationCountOptions): number {\n const medplum = useMedplum();\n const { resourceType, countCriteria, subscriptionCriteria } = options;\n const [count, setCount] = useState(0);\n\n const updateCount = useCallback(\n (cache: 'default' | 'reload') => {\n medplum\n .search(resourceType, countCriteria, { cache })\n .then((result) => setCount(result.total as number))\n .catch(console.error);\n },\n [medplum, resourceType, countCriteria]\n );\n\n // Initial count\n useEffect(() => {\n updateCount('default');\n }, [updateCount]);\n\n // Subscribe to the criteria\n useSubscription(subscriptionCriteria, () => {\n updateCount('reload');\n });\n\n return count;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { SubscriptionEmitter, SubscriptionEventMap } from '@medplum/core';\nimport { deepEquals } from '@medplum/core';\nimport type { Bundle, Subscription } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum, useMedplumProfile } from '../MedplumProvider/MedplumProvider.context';\n\nconst SUBSCRIPTION_DEBOUNCE_MS = 3000;\n\nexport type UseSubscriptionOptions = {\n subscriptionProps?: Partial<Subscription>;\n onWebSocketOpen?: () => void;\n onWebSocketClose?: () => void;\n onSubscriptionConnect?: (subscriptionId: string) => void;\n onSubscriptionDisconnect?: (subscriptionId: string) => void;\n onError?: (err: Error) => void;\n};\n\n/**\n * Creates an in-memory `Subscription` resource with the given criteria on the Medplum server and calls the given callback when an event notification is triggered by a resource interaction over a WebSocket connection.\n *\n * Subscriptions created with this hook are lightweight, share a single WebSocket connection, and are automatically untracked and cleaned up when the containing component is no longer mounted.\n *\n * @param criteria - The FHIR search criteria to subscribe to.\n * @param callback - The callback to call when a notification event `Bundle` for this `Subscription` is received.\n * @param options - Optional options used to configure the created `Subscription`. See {@link UseSubscriptionOptions}\n *\n * --------------------------------------------------------------------------------------------------------------------------------\n *\n * `options` contains the following properties, all of which are optional:\n * - `subscriptionProps` - Allows the caller to pass a `Partial<Subscription>` to use as part of the creation\n * of the `Subscription` resource for this subscription. It enables the user namely to pass things like the `extension` property and to create\n * the `Subscription` with extensions such the {@link https://www.medplum.com/docs/subscriptions/subscription-extensions#interactions | Supported Interaction} extension which would enable to listen for `create` or `update` only events.\n * - `onWebsocketOpen` - Called when the WebSocket connection is established with Medplum server.\n * - `onWebsocketClose` - Called when the WebSocket connection disconnects.\n * - `onSubscriptionConnect` - Called when the corresponding subscription starts to receive updates after the subscription has been initialized and connected to.\n * - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.\n * - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.\n */\nexport function useSubscription(\n criteria: string | undefined,\n callback: (bundle: Bundle) => void,\n options?: UseSubscriptionOptions\n): void {\n const medplum = useMedplum();\n const profile = useMedplumProfile();\n // When the user is not authenticated, the subscription becomes a no-op. Subscribing would\n // create a `Subscription` resource and request a WebSocket binding token, both of which 401\n // without a profile. Treating criteria as `undefined` skips all subscription work; when the\n // user authenticates, `profile` changes and the effect re-runs to subscribe.\n const effectiveCriteria = profile ? criteria : undefined;\n const [emitter, setEmitter] = useState<SubscriptionEmitter>();\n // We don't memoize the entire options object since it contains callbacks and if the callbacks change identity, we don't want to trigger a resubscribe to criteria\n const [memoizedSubProps, setMemoizedSubProps] = useState(options?.subscriptionProps);\n\n const listeningRef = useRef(false);\n const unsubTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const prevCriteriaRef = useRef<string | undefined>(undefined);\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>(undefined);\n\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef(options?.onWebSocketOpen);\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef(options?.onWebSocketClose);\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef(options?.onSubscriptionConnect);\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef(options?.onSubscriptionDisconnect);\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef(options?.onError);\n onErrorRef.current = options?.onError;\n\n useEffect(() => {\n // Deep equals checks referential equality first\n if (!deepEquals(options?.subscriptionProps, memoizedSubProps)) {\n setMemoizedSubProps(options?.subscriptionProps);\n }\n }, [memoizedSubProps, options]);\n\n useEffect(() => {\n if (unsubTimerRef.current) {\n clearTimeout(unsubTimerRef.current);\n unsubTimerRef.current = undefined;\n }\n\n let shouldSubscribe = false;\n if (\n prevCriteriaRef.current !== effectiveCriteria ||\n !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)\n ) {\n shouldSubscribe = true;\n }\n\n if (shouldSubscribe && prevCriteriaRef.current) {\n medplum.unsubscribeFromCriteria(prevCriteriaRef.current, prevMemoizedSubPropsRef.current);\n }\n\n // Set prev criteria and options to latest after checking them\n prevCriteriaRef.current = effectiveCriteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe && effectiveCriteria) {\n setEmitter(medplum.subscribeToCriteria(effectiveCriteria, memoizedSubProps));\n } else if (!effectiveCriteria) {\n setEmitter(undefined);\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n if (effectiveCriteria) {\n medplum.unsubscribeFromCriteria(effectiveCriteria, memoizedSubProps);\n }\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, effectiveCriteria, memoizedSubProps]);\n\n const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => {\n callbackRef.current?.(event.payload);\n }, []);\n\n const onWebSocketOpen = useCallback(() => {\n onWebSocketOpenRef.current?.();\n }, []);\n\n const onWebSocketClose = useCallback(() => {\n onWebSocketCloseRef.current?.();\n }, []);\n\n const onSubscriptionConnect = useCallback((event: SubscriptionEventMap['connect']) => {\n onSubscriptionConnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onSubscriptionDisconnect = useCallback((event: SubscriptionEventMap['disconnect']) => {\n onSubscriptionDisconnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onError = useCallback((event: SubscriptionEventMap['error']) => {\n onErrorRef.current?.(event.payload);\n }, []);\n\n useEffect(() => {\n if (!emitter) {\n return () => undefined;\n }\n if (!listeningRef.current) {\n emitter.addEventListener('message', emitterCallback);\n emitter.addEventListener('open', onWebSocketOpen);\n emitter.addEventListener('close', onWebSocketClose);\n emitter.addEventListener('connect', onSubscriptionConnect);\n emitter.addEventListener('disconnect', onSubscriptionDisconnect);\n emitter.addEventListener('error', onError);\n listeningRef.current = true;\n }\n return () => {\n listeningRef.current = false;\n emitter.removeEventListener('message', emitterCallback);\n emitter.removeEventListener('open', onWebSocketOpen);\n emitter.removeEventListener('close', onWebSocketClose);\n emitter.removeEventListener('connect', onSubscriptionConnect);\n emitter.removeEventListener('disconnect', onSubscriptionDisconnect);\n emitter.removeEventListener('error', onError);\n };\n }, [\n emitter,\n emitterCallback,\n onWebSocketOpen,\n onWebSocketClose,\n onSubscriptionConnect,\n onSubscriptionDisconnect,\n onError,\n ]);\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { QueryTypes } from '@medplum/core';\nimport { resolveId } from '@medplum/core';\nimport type { Patient, Reference, Resource, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/** Descriptor for a single FHIR search that a section needs. */\nexport interface FhirSearchDescriptor {\n /** Unique key used to access this search's results via `SectionResults[key]`. */\n readonly key: string;\n readonly resourceType: ResourceType;\n /** Which search param references the patient. Defaults to 'subject'. Examples: 'patient', 'beneficiary'. */\n readonly patientParam?: string;\n /**\n * Additional search params \u2014 same format as the 2nd arg to medplum.searchResources().\n * When using a string, do not include _count or _sort; they are appended automatically.\n */\n readonly query?: QueryTypes;\n}\n\n/** Named map of FHIR results for a section: `results[searchKey]` returns the Resource[] for that search. */\nexport type SectionResults = Record<string, Resource[]>;\n\nexport interface PatientSummaryData {\n /** One SectionResults map per section, indexed to match the sections array. */\n readonly sectionData: SectionResults[];\n readonly loading: boolean;\n readonly error: Error | undefined;\n}\n\n/**\n * Build a deduplication key for a search descriptor.\n * Searches with the same key are executed only once and their results shared.\n * @param search - The search descriptor to build a key for.\n * @returns A string key that uniquely identifies the search configuration.\n */\nfunction buildSearchKey(search: FhirSearchDescriptor): string {\n const param = search.patientParam ?? 'subject';\n const query = search.query;\n\n let queryStr = '';\n if (query !== undefined && query !== null) {\n if (typeof query === 'string') {\n queryStr = query;\n } else if (query instanceof URLSearchParams) {\n const entries = Array.from(query.entries()).sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));\n queryStr = JSON.stringify(entries);\n } else if (Array.isArray(query)) {\n const sorted = [...query].sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));\n queryStr = JSON.stringify(sorted);\n } else {\n const sorted = Object.entries(query)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => a.localeCompare(b));\n queryStr = JSON.stringify(sorted);\n }\n }\n\n return `${search.resourceType}:${param}:${queryStr}`;\n}\n\n/**\n * Build a stable fingerprint from sections' search configurations.\n * Used to avoid re-fetching when sections change by reference but not by content.\n * @param sections - The section configs to fingerprint.\n * @returns A string fingerprint representing the search configuration.\n */\nfunction buildSectionsFingerprint(\n sections: { readonly key: string; readonly searches?: FhirSearchDescriptor[] }[]\n): string {\n return sections\n .map((s) => {\n const searchKeys = s.searches ? s.searches.map(buildSearchKey).join(',') : '';\n return `${s.key}:[${searchKeys}]`;\n })\n .join('|');\n}\n\n/**\n * Hook that collects all FHIR searches from section configs, deduplicates them,\n * executes them in parallel, and routes results back to each section.\n * Uses Promise.allSettled so a single failing search does not block all sections \u2014\n * sections whose searches fail gracefully receive empty arrays.\n * @param patient - The patient or patient reference to fetch data for.\n * @param sections - The section configs defining which searches to execute.\n * @returns Section data, loading state, and any error.\n */\nexport function usePatientSummaryData(\n patient: Patient | Reference<Patient>,\n sections: { readonly key: string; readonly searches?: FhirSearchDescriptor[] }[]\n): PatientSummaryData {\n const medplum = useMedplum();\n const [sectionData, setSectionData] = useState<SectionResults[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>();\n\n // Stabilize sections reference: only change when the search configuration actually changes.\n const sectionsFingerprint = buildSectionsFingerprint(sections);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally keyed on fingerprint for content-based stability\n const stableSections = useMemo(() => sections, [sectionsFingerprint]);\n\n // Memoize the patient ID to avoid re-fetching on reference changes\n const patientId = useMemo(() => resolveId(patient), [patient]);\n\n useEffect(() => {\n if (!patientId) {\n return undefined;\n }\n\n let stale = false;\n const ref = `Patient/${patientId}`;\n const searchMeta = { _count: 100, _sort: '-_lastUpdated' };\n\n // Collect unique searches and build a mapping from deduplication key to index\n const uniqueSearches: FhirSearchDescriptor[] = [];\n const searchKeyToIndex = new Map<string, number>();\n\n // For each section, for each search, record the deduplication index and result key\n const sectionSearchMapping: { searchIdx: number; resultKey: string }[][] = [];\n\n for (const section of stableSections) {\n const mapping: { searchIdx: number; resultKey: string }[] = [];\n if (section.searches) {\n for (const search of section.searches) {\n const deduplicationKey = buildSearchKey(search);\n let idx = searchKeyToIndex.get(deduplicationKey);\n if (idx === undefined) {\n idx = uniqueSearches.length;\n searchKeyToIndex.set(deduplicationKey, idx);\n uniqueSearches.push(search);\n }\n mapping.push({ searchIdx: idx, resultKey: search.key });\n }\n }\n sectionSearchMapping.push(mapping);\n }\n\n if (uniqueSearches.length === 0) {\n // No searches needed \u2014 fill empty results\n setSectionData(stableSections.map(() => ({})));\n setLoading(false);\n return undefined;\n }\n\n // Execute all unique searches in parallel\n const promises = uniqueSearches.map((search) => {\n const patientParam = search.patientParam ?? 'subject';\n const baseQuery: Record<string, string | number | boolean> = {\n [patientParam]: ref,\n };\n\n if (search.query) {\n if (typeof search.query === 'string') {\n // String query \u2014 _count and _sort are appended automatically; do not include them in the query string.\n return medplum.searchResources(\n search.resourceType,\n `${patientParam}=${ref}&${search.query}&_count=100&_sort=-_lastUpdated`\n );\n } else if (search.query instanceof URLSearchParams) {\n search.query.forEach((value, key) => {\n baseQuery[key] = value;\n });\n } else if (Array.isArray(search.query)) {\n for (const [key, value] of search.query) {\n baseQuery[key] = value;\n }\n } else {\n for (const [key, value] of Object.entries(search.query)) {\n if (value !== undefined) {\n baseQuery[key] = value;\n }\n }\n }\n }\n\n return medplum.searchResources(search.resourceType, { ...searchMeta, ...baseQuery });\n });\n\n setLoading(true);\n setError(undefined);\n\n // allSettled ensures a single failing search does not block the rest \u2014 each section\n // renders with whatever data is available; failed searches produce empty arrays.\n Promise.allSettled(promises)\n .then((settledResults) => {\n if (stale) {\n return;\n }\n\n // Route results back to sections using named keys\n const data: SectionResults[] = sectionSearchMapping.map((mapping) => {\n const sectionResult: SectionResults = {};\n for (const { searchIdx, resultKey } of mapping) {\n const settled = settledResults[searchIdx];\n sectionResult[resultKey] = settled.status === 'fulfilled' ? settled.value : [];\n }\n return sectionResult;\n });\n\n setSectionData(data);\n setLoading(false);\n\n // Surface the first error so the UI can indicate a partial load failure\n const failures = settledResults.filter((r): r is PromiseRejectedResult => r.status === 'rejected');\n if (failures.length > 0) {\n console.error(\n 'Some patient summary searches failed:',\n failures.map((f) => f.reason)\n );\n const firstError = failures[0].reason;\n setError(firstError instanceof Error ? firstError : new Error(String(firstError)));\n }\n })\n .catch((err: unknown) => {\n if (!stale) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n });\n\n return () => {\n stale = true;\n };\n }, [medplum, patientId, stableSections]);\n\n return { sectionData, loading, error };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AddFavoriteParams, AddPharmacyResponse, PharmacySearchParams } from '@medplum/core';\nimport { isAddPharmacyResponse, isOrganizationArray } from '@medplum/core';\nimport type { Identifier, Organization } from '@medplum/fhirtypes';\nimport { useCallback } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UsePharmacySearchReturn {\n searchPharmacies: (params: PharmacySearchParams) => Promise<Organization[]>;\n addToFavorites: (params: AddFavoriteParams) => Promise<AddPharmacyResponse>;\n}\n\n/**\n * Generic React hook that provides pharmacy search and add-to-favorites\n * functionality for any e-prescribing integration.\n *\n * Encapsulates calls to a search-pharmacy bot and an add-patient-pharmacy bot,\n * and can be composed with the generic `PharmacyDialog` component from `@medplum/react`.\n *\n * @param searchBotIdentifier - Bot identifier for the pharmacy search bot.\n * @param addPharmacyBotIdentifier - Bot identifier for the add-patient-pharmacy bot.\n * @returns An object with `searchPharmacies` and `addToFavorites` callbacks.\n */\nexport function usePharmacySearch(\n searchBotIdentifier: Identifier,\n addPharmacyBotIdentifier: Identifier\n): UsePharmacySearchReturn {\n const medplum = useMedplum();\n\n const searchPharmacies = useCallback(\n async (params: PharmacySearchParams): Promise<Organization[]> => {\n const response = await medplum.executeBot(searchBotIdentifier, params);\n\n if (!isOrganizationArray(response)) {\n throw new Error('Invalid response from pharmacy search');\n }\n\n return response;\n },\n [medplum, searchBotIdentifier]\n );\n\n const addToFavorites = useCallback(\n async (params: AddFavoriteParams): Promise<AddPharmacyResponse> => {\n const response = await medplum.executeBot(addPharmacyBotIdentifier, {\n patientId: params.patientId,\n pharmacy: params.pharmacy,\n setAsPrimary: params.setAsPrimary,\n });\n\n if (!isAddPharmacyResponse(response)) {\n throw new Error('Invalid response from add pharmacy bot');\n }\n\n return response;\n },\n [medplum, addPharmacyBotIdentifier]\n );\n\n return { searchPharmacies, addToFavorites };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useEffect, useRef } from 'react';\n\n/**\n * React Hook to keep track of the passed-in value from the previous render of the containing component.\n * @param value - The value to track.\n * @returns The value passed in from the previous render.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>(undefined);\n useEffect(() => {\n ref.current = value;\n });\n return ref.current;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { getExtension } from '@medplum/core';\nimport type {\n Encounter,\n Questionnaire,\n QuestionnaireItem,\n QuestionnaireResponse,\n QuestionnaireResponseItem,\n QuestionnaireResponseItemAnswer,\n Reference,\n Signature,\n} from '@medplum/fhirtypes';\nimport { useReducer, useRef } from 'react';\nimport { useResource } from '../useResource/useResource';\nimport {\n buildInitialResponse,\n buildInitialResponseItem,\n evaluateCalculatedExpressionsInQuestionnaire,\n QUESTIONNAIRE_ITEM_CONTROL_URL,\n QUESTIONNAIRE_SIGNATURE_RESPONSE_URL,\n removeDisabledItems,\n} from './utils';\n\n// React Hook for Questionnaire Form\n\n// Why is this hard?\n// 1. It needs to handle both initial loading of a questionnaire and updating the response as the user interacts with it.\n// 2. It needs to support pagination and navigation through the questionnaire.\n// 3. It needs to handle complex items like groups and repeatable items.\n\n// Conventions we use:\n// 1. We use `QuestionnaireResponse` to track the user's answers.\n// 2. We use `QuestionnaireItem` to define the structure of the questionnaire.\n// 3. Response items are linked to their corresponding questionnaire items by `linkId`.\n// 4. Response items will always have a `linkId` that matches the `linkId` of the questionnaire item they correspond to.\n// 5. Response items will also always have an `id` that is unique within the response, which can be used to track changes to individual items.\n// 6. Pagination is enabled by default, so current state items will only include items for the current page.\n// 7. If Pagination is disabled, all items will be included in the current state items.\n\nexport interface UseQuestionnaireFormProps {\n readonly questionnaire: Questionnaire | Reference<Questionnaire>;\n readonly defaultValue?: QuestionnaireResponse | Reference<QuestionnaireResponse>;\n readonly subject?: Reference;\n readonly encounter?: Reference<Encounter>;\n readonly source?: QuestionnaireResponse['source'];\n readonly disablePagination?: boolean;\n readonly onChange?: (response: QuestionnaireResponse) => void;\n}\n\nexport interface QuestionnaireFormPage {\n readonly linkId: string;\n readonly title: string;\n readonly group: QuestionnaireItem & { type: 'group' };\n}\n\nexport interface QuestionnaireFormLoadingState {\n /** Currently loading data such as the Questionnaire or the QuestionnaireResponse default value */\n readonly loading: true;\n}\n\nexport interface QuestionnaireFormLoadedState {\n /** Not loading */\n readonly loading: false;\n\n /** The loaded questionnaire */\n questionnaire: Questionnaire;\n\n /** The current draft questionnaire response */\n questionnaireResponse: QuestionnaireResponse;\n\n /** Optional questionnaire subject */\n subject?: Reference;\n\n /** Optional questionnaire encounter */\n encounter?: Reference<Encounter>;\n\n /** The top level items for the current page */\n items: QuestionnaireItem[];\n\n /** The response items for the current page */\n responseItems: QuestionnaireResponseItem[];\n\n /**\n * Adds a new group item to the current context.\n * @param context - The current context of the questionnaire response items.\n * @param item - The questionnaire item that is being added to the group.\n */\n onAddGroup: (context: QuestionnaireResponseItem[], item: QuestionnaireItem) => void;\n\n /**\n * Adds an answer to a repeating item.\n * @param context - The current context of the questionnaire response items.\n * @param item - The questionnaire item that is being answered.\n */\n onAddAnswer: (context: QuestionnaireResponseItem[], item: QuestionnaireItem) => void;\n\n /**\n * Changes an answer value.\n * @param context - The current context of the questionnaire response items.\n * @param item - The questionnaire item that is being answered.\n * @param answer - The answer(s) provided by the user for the questionnaire item.\n */\n onChangeAnswer: (\n context: QuestionnaireResponseItem[],\n item: QuestionnaireItem,\n answer: QuestionnaireResponseItemAnswer[]\n ) => void;\n\n /**\n * Sets or updates the signature for the questionnaire response.\n * @param signature - The signature to set, or undefined to clear the signature.\n */\n onChangeSignature: (signature: Signature | undefined) => void;\n}\n\nexport interface QuestionnaireFormSinglePageState extends QuestionnaireFormLoadedState {\n readonly pagination: false;\n}\n\nexport interface QuestionnaireFormPaginationState extends QuestionnaireFormLoadedState {\n readonly pagination: true;\n pages: QuestionnaireFormPage[];\n activePage: number;\n onNextPage: () => void;\n onPrevPage: () => void;\n}\n\nexport type QuestionnaireFormState =\n | QuestionnaireFormLoadingState\n | QuestionnaireFormSinglePageState\n | QuestionnaireFormPaginationState;\n\nexport function useQuestionnaireForm(props: UseQuestionnaireFormProps): Readonly<QuestionnaireFormState> {\n const questionnaire = useResource(props.questionnaire);\n const defaultResponse = useResource(props.defaultValue);\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n\n const state = useRef<Partial<QuestionnaireFormPaginationState>>({\n activePage: 0,\n });\n\n // If the questionnaire is loaded, we will set the current questionnaire and pages.\n if (!state.current.questionnaire && questionnaire) {\n state.current.questionnaire = questionnaire;\n state.current.pages = props.disablePagination ? undefined : getPages(questionnaire);\n }\n\n // If we are expecting a questionnaire response, and it is loaded, then use it.\n if (questionnaire && props.defaultValue && defaultResponse && !state.current.questionnaireResponse) {\n state.current.questionnaireResponse = buildInitialResponse(questionnaire, defaultResponse);\n emitChange();\n }\n\n // If we are not expecting a questionnaire response, we will create a new one.\n if (questionnaire && !props.defaultValue && !state.current.questionnaireResponse) {\n state.current.questionnaireResponse = buildInitialResponse(questionnaire);\n emitChange();\n }\n\n if (!state.current.questionnaire || !state.current.questionnaireResponse) {\n return { loading: true };\n }\n\n function getResponseItemByContext(\n context: QuestionnaireResponseItem[]\n ): QuestionnaireResponse | QuestionnaireResponseItem | undefined;\n function getResponseItemByContext(\n context: QuestionnaireResponseItem[],\n item?: QuestionnaireItem\n ): QuestionnaireResponseItem | undefined;\n function getResponseItemByContext(\n context: QuestionnaireResponseItem[],\n item?: QuestionnaireItem\n ): QuestionnaireResponse | QuestionnaireResponseItem | undefined {\n let currentItem: QuestionnaireResponse | QuestionnaireResponseItem | undefined =\n state.current.questionnaireResponse;\n for (const contextElement of context) {\n currentItem = currentItem?.item?.find((i) =>\n contextElement.id ? i.id === contextElement.id : i.linkId === contextElement.linkId\n );\n }\n if (item) {\n currentItem = currentItem?.item?.find((i) => i.linkId === item.linkId);\n }\n return currentItem;\n }\n\n function onNextPage(): void {\n state.current.activePage = (state.current.activePage ?? 0) + 1;\n forceUpdate();\n }\n\n function onPrevPage(): void {\n state.current.activePage = (state.current.activePage ?? 0) - 1;\n forceUpdate();\n }\n\n function onAddGroup(context: QuestionnaireResponseItem[], item: QuestionnaireItem): void {\n const responseItem = getResponseItemByContext(context);\n if (responseItem) {\n responseItem.item ??= [];\n responseItem.item.push(buildInitialResponseItem(item));\n emitChange();\n }\n }\n\n function onAddAnswer(context: QuestionnaireResponseItem[], item: QuestionnaireItem): void {\n const currentItem = getResponseItemByContext(context, item);\n if (currentItem) {\n currentItem.answer ??= [];\n currentItem.answer.push({});\n emitChange();\n }\n }\n\n function onChangeAnswer(\n context: QuestionnaireResponseItem[],\n item: QuestionnaireItem,\n answer: QuestionnaireResponseItemAnswer[]\n ): void {\n const currentItem = getResponseItemByContext(context, item);\n if (currentItem) {\n currentItem.answer = answer;\n emitChange();\n }\n }\n\n function onChangeSignature(signature: Signature | undefined): void {\n const currentResponse = state.current.questionnaireResponse;\n if (!currentResponse) {\n return;\n }\n if (signature) {\n currentResponse.extension = currentResponse.extension ?? [];\n currentResponse.extension = currentResponse.extension.filter(\n (ext) => ext.url !== QUESTIONNAIRE_SIGNATURE_RESPONSE_URL\n );\n currentResponse.extension.push({\n url: QUESTIONNAIRE_SIGNATURE_RESPONSE_URL,\n valueSignature: signature,\n });\n } else {\n currentResponse.extension = currentResponse.extension?.filter(\n (ext) => ext.url !== QUESTIONNAIRE_SIGNATURE_RESPONSE_URL\n );\n }\n emitChange();\n }\n\n function updateCalculatedExpressions(): void {\n const questionnaire = state.current.questionnaire;\n if (questionnaire?.item) {\n const response = state.current.questionnaireResponse as QuestionnaireResponse;\n evaluateCalculatedExpressionsInQuestionnaire(questionnaire.item, response);\n }\n }\n\n function emitChange(): void {\n const currentResponse = state.current.questionnaireResponse;\n const currentQuestionnaire = state.current.questionnaire;\n if (!currentResponse || !currentQuestionnaire) {\n return;\n }\n updateCalculatedExpressions();\n forceUpdate();\n props.onChange?.(removeDisabledItems(currentQuestionnaire, currentResponse));\n }\n\n return {\n loading: false,\n pagination: !!state.current.pages,\n questionnaire: state.current.questionnaire,\n questionnaireResponse: removeDisabledItems(state.current.questionnaire, state.current.questionnaireResponse),\n subject: props.subject,\n encounter: props.encounter,\n activePage: state.current.activePage,\n pages: state.current.pages,\n items: getItemsForPage(state.current.questionnaire, state.current.pages, state.current.activePage),\n responseItems: getResponseItemsForPage(\n state.current.questionnaireResponse,\n state.current.pages,\n state.current.activePage\n ),\n onNextPage,\n onPrevPage,\n onAddGroup,\n onAddAnswer,\n onChangeAnswer,\n onChangeSignature,\n } as QuestionnaireFormSinglePageState | QuestionnaireFormPaginationState;\n}\n\nfunction getPages(questionnaire: Questionnaire): QuestionnaireFormPage[] | undefined {\n if (!questionnaire?.item) {\n return undefined;\n }\n const extension = getExtension(questionnaire?.item?.[0], QUESTIONNAIRE_ITEM_CONTROL_URL);\n if (extension?.valueCodeableConcept?.coding?.[0]?.code !== 'page') {\n return undefined;\n }\n\n return questionnaire.item.map((item, index) => {\n return {\n linkId: item.linkId,\n title: item.text ?? `Page ${index + 1}`,\n group: item as QuestionnaireItem & { type: 'group' },\n };\n });\n}\n\nfunction getItemsForPage(\n questionnaire: Questionnaire,\n pages: QuestionnaireFormPage[] | undefined,\n activePage = 0\n): QuestionnaireItem[] {\n if (pages && questionnaire?.item?.[activePage]) {\n return [questionnaire.item[activePage]];\n }\n return questionnaire.item ?? [];\n}\n\nfunction getResponseItemsForPage(\n questionnaireResponse: QuestionnaireResponse,\n pages: QuestionnaireFormPage[] | undefined,\n activePage = 0\n): QuestionnaireResponseItem[] {\n if (pages && questionnaireResponse?.item?.[activePage]) {\n return [questionnaireResponse.item[activePage]];\n }\n return questionnaireResponse.item ?? [];\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient, WithId } from '@medplum/core';\nimport { deepEquals, isReference, isResource, normalizeOperationOutcome } from '@medplum/core';\nimport type { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value - The resource or reference to resource.\n * @param setOutcome - Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | Partial<T> | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): WithId<T> | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<WithId<T> | undefined>(() => {\n return getInitialResource(medplum, value);\n });\n\n const setResourceIfChanged = useCallback(\n (r: WithId<T> | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource]\n );\n\n useEffect(() => {\n let subscribed = true;\n\n const newValue = getInitialResource(medplum, value);\n if (!newValue && isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n } else {\n setResourceIfChanged(newValue);\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum - The medplum client.\n * @param value - The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | Partial<T> | undefined\n): WithId<T> | undefined {\n if (value) {\n if (isResource(value)) {\n return value as WithId<T>;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedValue } from '@medplum/core';\nimport {\n EMPTY,\n HTTP_HL7_ORG,\n PropertyType,\n append,\n capitalize,\n deepClone,\n evalFhirPathTyped,\n getExtension,\n getReferenceString,\n getTypedPropertyValueWithoutSchema,\n normalizeErrorString,\n splitN,\n toJsBoolean,\n toTypedValue,\n typedValueToString,\n} from '@medplum/core';\nimport type {\n Encounter,\n Questionnaire,\n QuestionnaireItem,\n QuestionnaireItemAnswerOption,\n QuestionnaireItemEnableWhen,\n QuestionnaireItemInitial,\n QuestionnaireResponse,\n QuestionnaireResponseItem,\n QuestionnaireResponseItemAnswer,\n Reference,\n ResourceType,\n} from '@medplum/fhirtypes';\n\nexport const QuestionnaireItemType = {\n group: 'group',\n display: 'display',\n question: 'question',\n boolean: 'boolean',\n decimal: 'decimal',\n integer: 'integer',\n date: 'date',\n dateTime: 'dateTime',\n time: 'time',\n string: 'string',\n text: 'text',\n url: 'url',\n choice: 'choice',\n openChoice: 'open-choice',\n attachment: 'attachment',\n reference: 'reference',\n quantity: 'quantity',\n} as const;\nexport type QuestionnaireItemType = (typeof QuestionnaireItemType)[keyof typeof QuestionnaireItemType];\n\nexport const QUESTIONNAIRE_ITEM_CONTROL_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-itemControl`;\nexport const QUESTIONNAIRE_REFERENCE_FILTER_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-referenceFilter`;\nexport const QUESTIONNAIRE_REFERENCE_RESOURCE_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-referenceResource`;\nexport const QUESTIONNAIRE_VALIDATION_ERROR_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-validationError`;\nexport const QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL = `${HTTP_HL7_ORG}/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression`;\nexport const QUESTIONNAIRE_CALCULATED_EXPRESSION_URL = `${HTTP_HL7_ORG}/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression`;\nexport const QUESTIONNAIRE_SIGNATURE_REQUIRED_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-signatureRequired`;\nexport const QUESTIONNAIRE_SIGNATURE_RESPONSE_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaireresponse-signature`;\nexport const QUESTIONNAIRE_HIDDEN_URL = `${HTTP_HL7_ORG}/fhir/StructureDefinition/questionnaire-hidden`;\n\n/**\n * Returns true if the item is a choice question.\n * @param item - The questionnaire item to check.\n * @returns True if the item is a choice question, false otherwise.\n */\nexport function isChoiceQuestion(item: QuestionnaireItem): boolean {\n return item.type === 'choice' || item.type === 'open-choice';\n}\n\n/**\n * Returns a copy of the questionnaire response with response items for disabled\n * questionnaire items removed.\n *\n * Per the FHIR R4 spec, a QuestionnaireResponse should not include answers for items\n * whose `enableWhen` evaluates to false or whose `questionnaire-hidden` extension is\n * true. The form preserves answers in local state so that re-enabling an item\n * restores its previous value, so callers must strip disabled items before\n * persisting or transmitting the response.\n *\n * @param questionnaire - The questionnaire defining the items.\n * @param response - The current questionnaire response.\n * @returns A new questionnaire response with disabled items removed.\n */\nexport function removeDisabledItems(\n questionnaire: Questionnaire,\n response: QuestionnaireResponse\n): QuestionnaireResponse {\n return {\n ...response,\n item: filterEnabledResponseItems(questionnaire.item, response.item, response),\n };\n}\n\nfunction filterEnabledResponseItems(\n items: QuestionnaireItem[] | undefined,\n responseItems: QuestionnaireResponseItem[] | undefined,\n response: QuestionnaireResponse\n): QuestionnaireResponseItem[] | undefined {\n if (!responseItems) {\n return responseItems;\n }\n const result: QuestionnaireResponseItem[] = [];\n for (const responseItem of responseItems) {\n const item = items?.find((i) => i.linkId === responseItem.linkId);\n if (item && !isQuestionEnabled(item, response)) {\n continue;\n }\n if (item?.item && responseItem.item) {\n result.push({\n ...responseItem,\n item: filterEnabledResponseItems(item.item, responseItem.item, response),\n });\n } else {\n result.push(responseItem);\n }\n }\n return result;\n}\n\n/**\n * Returns true if the questionnaire item is enabled based on the enableWhen conditions or expression.\n * @param item - The questionnaire item to check.\n * @param questionnaireResponse - The questionnaire response to check against.\n * @returns True if the question is enabled, false otherwise.\n */\nexport function isQuestionEnabled(\n item: QuestionnaireItem,\n questionnaireResponse: QuestionnaireResponse | undefined\n): boolean {\n // Check for questionnaire-hidden extension first - if present and true, the item is permanently hidden\n const hiddenExtension = getExtension(item, QUESTIONNAIRE_HIDDEN_URL);\n if (hiddenExtension?.valueBoolean === true) {\n return false;\n }\n\n const extensionResult = isQuestionEnabledViaExtension(item, questionnaireResponse);\n if (extensionResult !== undefined) {\n return extensionResult;\n }\n return isQuestionEnabledViaEnabledWhen(item, questionnaireResponse);\n}\n\n/**\n * Returns true if the questionnaire item is enabled via an extension expression.\n *\n * An expression that returns a boolean value for whether to enable the item.\n * If the expression does not resolve to a boolean, it is considered an error in the design of the Questionnaire.\n * Form renderer behavior is undefined.\n * Some tools may attempt to force the value to be a boolean (e.g. is it a non-empty collection, non-null, non-zero - if so, then true).\n *\n * See: https://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-enableWhenExpression.html\n *\n * @param item - The questionnaire item to check.\n * @param questionnaireResponse - The questionnaire response to check against.\n * @returns True if the question is enabled via an extension expression, false otherwise.\n */\nfunction isQuestionEnabledViaExtension(\n item: QuestionnaireItem,\n questionnaireResponse: QuestionnaireResponse | undefined\n): boolean | undefined {\n const extension = getExtension(item, QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL);\n if (questionnaireResponse && extension) {\n const expression = extension.valueExpression?.expression;\n if (expression) {\n const value = toTypedValue(questionnaireResponse);\n const result = evalFhirPathTyped(expression, [value], { '%resource': value });\n return toJsBoolean(result);\n }\n }\n return undefined;\n}\n\n/**\n * Returns true if the questionnaire item is enabled based on the enableWhen conditions.\n *\n * See: https://hl7.org/fhir/R4/questionnaire-definitions.html#Questionnaire.item.enableWhen\n * See: https://hl7.org/fhir/R4/questionnaire-definitions.html#Questionnaire.item.enableBehavior\n *\n * @param item - The questionnaire item to check.\n * @param questionnaireResponse - The questionnaire response to check against.\n * @returns True if the question is enabled based on the enableWhen conditions, false otherwise.\n */\nfunction isQuestionEnabledViaEnabledWhen(\n item: QuestionnaireItem,\n questionnaireResponse: QuestionnaireResponse | undefined\n): boolean {\n if (!item.enableWhen) {\n return true;\n }\n\n const enableBehavior = item.enableBehavior ?? 'any';\n for (const enableWhen of item.enableWhen) {\n const actualAnswers = getByLinkId(questionnaireResponse?.item, enableWhen.question);\n\n if (enableWhen.operator === 'exists' && !enableWhen.answerBoolean && !actualAnswers?.length) {\n if (enableBehavior === 'any') {\n return true;\n } else {\n continue;\n }\n }\n const { anyMatch, allMatch } = checkAnswers(enableWhen, actualAnswers, enableBehavior);\n\n if (enableBehavior === 'any' && anyMatch) {\n return true;\n }\n if (enableBehavior === 'all' && !allMatch) {\n return false;\n }\n }\n\n return enableBehavior !== 'any';\n}\n\n/**\n * Evaluates the calculated expressions in a questionnaire.\n * Updates response item answers in place with the calculated values.\n *\n * See: https://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-calculatedExpression.html\n *\n * @param items - The questionnaire items to evaluate.\n * @param response - The questionnaire response to evaluate against.\n * @param responseItems - The response items to update.\n */\nexport function evaluateCalculatedExpressionsInQuestionnaire(\n items: QuestionnaireItem[],\n response: QuestionnaireResponse,\n responseItems: QuestionnaireResponseItem[] | undefined = response.item\n): void {\n for (const item of items) {\n const responseItem = responseItems?.find((r) => r.linkId === item.linkId);\n if (responseItem) {\n evaluateQuestionnaireItemCalculatedExpressions(response, item, responseItem);\n if (item.item && responseItem.item) {\n // If the item has nested items, evaluate their calculated expressions as well\n evaluateCalculatedExpressionsInQuestionnaire(item.item, response, responseItem.item);\n }\n }\n }\n}\n\nfunction evaluateQuestionnaireItemCalculatedExpressions(\n response: QuestionnaireResponse,\n item: QuestionnaireItem,\n responseItem: QuestionnaireResponseItem\n): void {\n try {\n const calculatedValue = evaluateCalculatedExpression(item, response);\n if (!calculatedValue) {\n return;\n }\n const answer = typedValueToResponseItem(item, calculatedValue);\n if (!answer) {\n return;\n }\n responseItem.answer = [answer];\n } catch (error) {\n responseItem.extension = [\n {\n url: QUESTIONNAIRE_VALIDATION_ERROR_URL,\n valueString: `Expression evaluation failed: ${normalizeErrorString(error)}`,\n },\n ];\n }\n}\n\nconst questionnaireItemTypesAllowedPropertyTypes: Record<string, string[]> = {\n [QuestionnaireItemType.boolean]: [PropertyType.boolean],\n [QuestionnaireItemType.date]: [PropertyType.date],\n [QuestionnaireItemType.dateTime]: [PropertyType.dateTime],\n [QuestionnaireItemType.time]: [PropertyType.time],\n [QuestionnaireItemType.url]: [PropertyType.string, PropertyType.uri, PropertyType.url],\n [QuestionnaireItemType.attachment]: [PropertyType.Attachment],\n [QuestionnaireItemType.reference]: [PropertyType.Reference],\n [QuestionnaireItemType.quantity]: [PropertyType.Quantity],\n [QuestionnaireItemType.decimal]: [PropertyType.decimal, PropertyType.integer],\n [QuestionnaireItemType.integer]: [PropertyType.decimal, PropertyType.integer],\n} as const;\n\nexport function typedValueToResponseItem(\n item: QuestionnaireItem,\n value: TypedValue\n): QuestionnaireResponseItemAnswer | undefined {\n if (!item.type) {\n return undefined;\n }\n if (item.type === QuestionnaireItemType.choice || item.type === QuestionnaireItemType.openChoice) {\n // Choice and open-choice items can have multiple answer options\n return { [`value${capitalize(value.type)}`]: value.value };\n }\n if (item.type === QuestionnaireItemType.string || item.type === QuestionnaireItemType.text) {\n // Always coerce string values to valueString\n if (typeof value.value === 'string') {\n return { valueString: value.value };\n }\n return undefined;\n }\n const allowedPropertyTypes = questionnaireItemTypesAllowedPropertyTypes[item.type];\n if (allowedPropertyTypes?.includes(value.type)) {\n // Use the questionnaire item type to determine the response item type\n return { [`value${capitalize(item.type)}`]: value.value };\n }\n return undefined;\n}\n\nfunction evaluateCalculatedExpression(\n item: QuestionnaireItem,\n response: QuestionnaireResponse | undefined\n): TypedValue | undefined {\n if (!response) {\n return undefined;\n }\n\n const extension = getExtension(item, QUESTIONNAIRE_CALCULATED_EXPRESSION_URL);\n if (extension) {\n const expression = extension.valueExpression?.expression;\n if (expression) {\n const value = toTypedValue(response);\n const result = evalFhirPathTyped(expression, [value], { '%resource': value });\n return result.length !== 0 ? result[0] : undefined;\n }\n }\n return undefined;\n}\n\nexport function getNewMultiSelectValues(\n selected: string[],\n propertyName: string,\n item: QuestionnaireItem\n): QuestionnaireResponseItemAnswer[] {\n const result: QuestionnaireResponseItemAnswer[] = [];\n\n for (const selectedStr of selected) {\n const option = item.answerOption?.find(\n (candidate) => typedValueToString(getItemAnswerOptionValue(candidate)) === selectedStr\n );\n if (option) {\n const optionValue = getItemAnswerOptionValue(option);\n if (optionValue) {\n result.push({ [propertyName]: optionValue.value });\n }\n }\n }\n\n return result;\n}\n\nfunction getByLinkId(\n responseItems: QuestionnaireResponseItem[] | undefined,\n linkId: string\n): QuestionnaireResponseItemAnswer[] | undefined {\n for (const response of responseItems ?? EMPTY) {\n if (response.linkId === linkId) {\n return response.answer;\n }\n if (response.item) {\n const nestedAnswer = getByLinkId(response.item, linkId);\n if (nestedAnswer) {\n return nestedAnswer;\n }\n }\n }\n return undefined;\n}\n\nfunction evaluateMatch(actualAnswer: TypedValue | undefined, expectedAnswer: TypedValue, operator?: string): boolean {\n // We handle exists separately since its so different in terms of comparisons than the other mathematical operators\n if (operator === 'exists') {\n // if actualAnswer is not undefined, then exists: true passes\n // if actualAnswer is undefined, then exists: false passes\n return !!actualAnswer === expectedAnswer.value;\n } else if (!actualAnswer) {\n return false;\n } else {\n // `=` and `!=` should be treated as the FHIRPath `~` and `!~`\n // All other operators should be unmodified\n const fhirPathOperator = operator === '=' || operator === '!=' ? operator?.replace('=', '~') : operator;\n const [{ value }] = evalFhirPathTyped(`%actualAnswer ${fhirPathOperator} %expectedAnswer`, [actualAnswer], {\n '%actualAnswer': actualAnswer,\n '%expectedAnswer': expectedAnswer,\n });\n return value;\n }\n}\n\nfunction checkAnswers(\n enableWhen: QuestionnaireItemEnableWhen,\n answers: QuestionnaireResponseItemAnswer[] | undefined,\n enableBehavior: 'any' | 'all'\n): { anyMatch: boolean; allMatch: boolean } {\n const actualAnswers = answers || [];\n const expectedAnswer = getItemEnableWhenValueAnswer(enableWhen);\n\n let anyMatch = false;\n let allMatch = true;\n\n for (const actualAnswerValue of actualAnswers) {\n const actualAnswer = getResponseItemAnswerValue(actualAnswerValue);\n const { operator } = enableWhen;\n const match = evaluateMatch(actualAnswer, expectedAnswer, operator);\n if (match) {\n anyMatch = true;\n } else {\n allMatch = false;\n }\n\n if (enableBehavior === 'any' && anyMatch) {\n break;\n }\n }\n\n return { anyMatch, allMatch };\n}\n\nexport function getQuestionnaireItemReferenceTargetTypes(item: QuestionnaireItem): ResourceType[] | undefined {\n const extension = getExtension(item, QUESTIONNAIRE_REFERENCE_RESOURCE_URL);\n if (!extension) {\n return undefined;\n }\n if (extension.valueCode !== undefined) {\n return [extension.valueCode] as ResourceType[];\n }\n if (extension.valueCodeableConcept) {\n return extension.valueCodeableConcept?.coding?.map((c) => c.code) as ResourceType[];\n }\n return undefined;\n}\n\nexport function setQuestionnaireItemReferenceTargetTypes(\n item: QuestionnaireItem,\n targetTypes: ResourceType[] | undefined\n): QuestionnaireItem {\n const result = deepClone(item);\n let extension = getExtension(result, QUESTIONNAIRE_REFERENCE_RESOURCE_URL);\n\n if (!targetTypes || targetTypes.length === 0) {\n if (extension) {\n result.extension = result.extension?.filter((e) => e !== extension);\n }\n return result;\n }\n\n if (!extension) {\n result.extension ??= [];\n extension = { url: QUESTIONNAIRE_REFERENCE_RESOURCE_URL };\n result.extension.push(extension);\n }\n\n if (targetTypes.length === 1) {\n extension.valueCode = targetTypes[0];\n delete extension.valueCodeableConcept;\n } else {\n extension.valueCodeableConcept = { coding: targetTypes.map((t) => ({ code: t })) };\n delete extension.valueCode;\n }\n\n return result;\n}\n\n/**\n * Returns the reference filter for the given questionnaire item.\n * @see https://build.fhir.org/ig/HL7/fhir-extensions/StructureDefinition-questionnaire-referenceFilter-definitions.html\n * @param item - The questionnaire item to get the reference filter for.\n * @param subject - Optional subject reference.\n * @param encounter - Optional encounter reference.\n * @returns The reference filter as a map of key/value pairs.\n */\nexport function getQuestionnaireItemReferenceFilter(\n item: QuestionnaireItem,\n subject: Reference | undefined,\n encounter: Reference<Encounter> | undefined\n): Record<string, string> | undefined {\n const extension = getExtension(item, QUESTIONNAIRE_REFERENCE_FILTER_URL);\n if (!extension?.valueString) {\n return undefined;\n }\n\n // Replace variables\n let filter = extension.valueString;\n if (subject?.reference) {\n filter = filter.replaceAll('$subj', subject.reference);\n }\n if (encounter?.reference) {\n filter = filter.replaceAll('$encounter', encounter.reference);\n }\n\n // Parse the valueString into a map\n const result: Record<string, string> = {};\n const parts = filter.split('&');\n for (const part of parts) {\n const [key, value] = splitN(part, '=', 2);\n result[key] = value;\n }\n return result;\n}\n\nexport function buildInitialResponse(\n questionnaire: Questionnaire,\n questionnaireResponse?: QuestionnaireResponse\n): QuestionnaireResponse {\n const response: QuestionnaireResponse = {\n resourceType: 'QuestionnaireResponse',\n questionnaire: questionnaire.url ?? getReferenceString(questionnaire),\n item: buildInitialResponseItems(questionnaire.item, questionnaireResponse?.item),\n status: 'in-progress',\n };\n\n return response;\n}\n\nfunction buildInitialResponseItems(\n items: QuestionnaireItem[] | undefined,\n responseItems: QuestionnaireResponseItem[] | undefined\n): QuestionnaireResponseItem[] | undefined {\n let result: QuestionnaireResponseItem[] | undefined;\n for (const item of items ?? EMPTY) {\n if (item.type === QuestionnaireItemType.display) {\n // Display items do not have response items, so we skip them.\n continue;\n }\n\n const existingResponseItems = responseItems?.filter((responseItem) => responseItem.linkId === item.linkId);\n if (existingResponseItems?.length) {\n for (const existingResponseItem of existingResponseItems) {\n // Update existing response item\n existingResponseItem.id = existingResponseItem.id ?? generateId();\n existingResponseItem.text = existingResponseItem.text ?? item.text;\n existingResponseItem.item = buildInitialResponseItems(item.item, existingResponseItem.item);\n existingResponseItem.answer = buildInitialResponseAnswer(item, existingResponseItem);\n result = append(result, existingResponseItem);\n }\n } else {\n // Add new response item\n result = append(result, buildInitialResponseItem(item));\n }\n }\n\n return result;\n}\n\nexport function buildInitialResponseItem(item: QuestionnaireItem): QuestionnaireResponseItem {\n return {\n id: generateId(),\n linkId: item.linkId,\n text: item.text,\n item: buildInitialResponseItems(item.item, undefined),\n answer: buildInitialResponseAnswer(item),\n };\n}\n\nlet nextId = 1;\nfunction generateId(): string {\n return 'id-' + nextId++;\n}\n\nfunction buildInitialResponseAnswer(\n item: QuestionnaireItem,\n responseItem?: QuestionnaireResponseItem\n): QuestionnaireResponseItemAnswer[] | undefined {\n if (item.type === QuestionnaireItemType.display || item.type === QuestionnaireItemType.group) {\n return undefined;\n }\n\n if (responseItem?.answer && responseItem.answer.length > 0) {\n // If the response item already has answers, return them as is.\n return responseItem.answer;\n }\n\n if (item.initial && item.initial.length > 0) {\n // If the item has initial values, return them as answers.\n // This works because QuestionnaireItemInitial and QuestionnaireResponseItemAnswer\n // have the same properties.\n return item.initial.map((initial) => ({ ...initial }));\n }\n\n if (item.answerOption) {\n return item.answerOption\n .filter((option) => option.initialSelected)\n .map((option) => ({ ...option, initialSelected: undefined }));\n }\n\n // Otherwise, return undefined to indicate no initial answers.\n return undefined;\n}\n\nexport function getItemInitialValue(initial: QuestionnaireItemInitial | undefined): TypedValue {\n return getTypedPropertyValueWithoutSchema(\n { type: 'QuestionnaireItemInitial', value: initial },\n 'value'\n ) as TypedValue;\n}\n\nexport function getItemAnswerOptionValue(option: QuestionnaireItemAnswerOption): TypedValue {\n return getTypedPropertyValueWithoutSchema(\n { type: 'QuestionnaireItemAnswerOption', value: option },\n 'value'\n ) as TypedValue;\n}\n\nexport function getItemEnableWhenValueAnswer(enableWhen: QuestionnaireItemEnableWhen): TypedValue {\n return getTypedPropertyValueWithoutSchema(\n { type: 'QuestionnaireItemEnableWhen', value: enableWhen },\n 'answer'\n ) as TypedValue;\n}\n\nexport function getResponseItemAnswerValue(answer: QuestionnaireResponseItemAnswer): TypedValue | undefined {\n return getTypedPropertyValueWithoutSchema({ type: 'QuestionnaireResponseItemAnswer', value: answer }, 'value') as\n | TypedValue\n | undefined;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { QueryTypes, ResourceArray, WithId } from '@medplum/core';\nimport { allOk, normalizeOperationOutcome } from '@medplum/core';\nimport type { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\nimport { useDebouncedValue } from '../useDebouncedValue/useDebouncedValue';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\nexport type SearchOptions = { debounceMs?: number; enabled?: boolean };\n\nconst DEFAULT_DEBOUNCE_MS = 250;\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @param options - Optional options for configuring the search.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes,\n options?: SearchOptions\n): [Bundle<WithId<ExtractResource<K>>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<WithId<ExtractResource<K>>>>('search', resourceType, query, options);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @param options - Optional options for configuring the search.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes,\n options?: SearchOptions\n): [WithId<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, WithId<ExtractResource<K>>>('searchOne', resourceType, query, options);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @param options - Optional options for configuring the search.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes,\n options?: SearchOptions\n): [ResourceArray<WithId<ExtractResource<K>>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<WithId<ExtractResource<K>>>>('searchResources', resourceType, query, options);\n}\n\nfunction useSearchImpl<K extends ResourceType, SearchReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined,\n options?: SearchOptions\n): [SearchReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [loading, setLoading] = useState(false);\n const [result, setResult] = useState<SearchReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n const searchKey = medplum.fhirSearchUrl(resourceType, query).toString();\n const searchValue = useMemo(\n () => ({\n resourceType,\n query,\n }),\n // This is safe because the missing dependencies are encoded into `searchKey`\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [searchKey]\n );\n\n const enabled = options?.enabled ?? true;\n const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n const [debouncedSearchValue] = useDebouncedValue(searchValue, debounceMs, { leading: true });\n\n useEffect(() => {\n if (!enabled) {\n return () => {};\n }\n\n setLoading(true);\n\n let active = true;\n medplum[searchFn](debouncedSearchValue.resourceType, debouncedSearchValue.query)\n .then((res) => {\n if (active) {\n setLoading(false);\n setResult(res as SearchReturnType);\n setOutcome(allOk);\n }\n })\n .catch((err) => {\n if (active) {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n }\n });\n\n return () => {\n active = false;\n };\n }, [medplum, searchFn, debouncedSearchValue, enabled]);\n\n return [result, loading, outcome];\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\n/*\n This hook was forked from: https://github.com/mantinedev/mantine/blob/fbcee929e0b11782092f48c1e7af2a1d1c878823/packages/%40mantine/hooks/src/use-debounced-value/use-debounced-value.ts\n and has the following license:\n\n MIT License\n\n Copyright (c) 2021 Vitaly Rtishchev\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n*/\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport type UseDebouncedValueOptions = {\n /** Whether the first update to `value` should be immediate or not */\n leading?: boolean;\n};\n\n/**\n * This hook allows users to debounce an incoming value by a specified number of milliseconds.\n *\n * Users can also specify whether the first update to `value` in a sequence of rapid updates should be immediate, by specifying `leading: true` in the options.\n * The default value for `leading` is `false`.\n *\n * The return value is a tuple containing the debounced value at `arr[0]` and a function to cancel the pending debounced value change at `arr[1]`.\n *\n * @param value - The value to debounce.\n * @param waitMs - How long in milliseconds should.\n * @param options - Optional options for configuring the debounce.\n * @returns An array tuple of `[debouncedValue, cancelFn]`.\n */\nexport function useDebouncedValue<T = any>(\n value: T,\n waitMs: number,\n options: UseDebouncedValueOptions = { leading: false }\n): [T, () => void] {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const mountedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n const cooldownRef = useRef(false);\n\n const cancel = useCallback(() => window.clearTimeout(timeoutRef.current), []);\n\n useEffect(() => {\n if (mountedRef.current) {\n if (!cooldownRef.current && options.leading) {\n cooldownRef.current = true;\n setDebouncedValue(value);\n // After waitMs, reset the cooldown\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n }, waitMs);\n } else {\n cancel();\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n setDebouncedValue(value);\n }, waitMs);\n }\n }\n }, [value, options.leading, waitMs, cancel]);\n\n useEffect(() => {\n mountedRef.current = true;\n return cancel;\n }, [cancel]);\n\n return [debouncedValue, cancel] as const;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { OperationOutcomeError } from '@medplum/core';\nimport { useCallback } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * Vendor-neutral React hook that syncs a Medplum `PlanDefinition` (type=order-set)\n * to the configured e-prescribing vendor via the `$sync-orderset` custom FHIR operation\n * (`POST /fhir/R4/PlanDefinition/$sync-orderset`).\n *\n * Silently no-ops when the operation is not deployed (i.e. no e-prescribing vendor\n * is configured for the project), so callers do not need to guard against missing\n * integrations.\n *\n * @returns A stable `syncOrderSet(planDefinitionId)` callback.\n */\nexport function useSyncOrderSet(): (planDefinitionId: string) => Promise<void> {\n const medplum = useMedplum();\n\n return useCallback(\n async (planDefinitionId: string): Promise<void> => {\n try {\n await medplum.post(medplum.fhirUrl('PlanDefinition', '$sync-orderset'), { planDefinitionId });\n } catch (err: unknown) {\n // If the operation isn't deployed, silently skip \u2014 project has no e-prescribing vendor configured.\n if (err instanceof OperationOutcomeError && err.outcome.issue?.some((i) => i.code === 'not-found')) {\n return;\n }\n throw err;\n }\n },\n [medplum]\n );\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { getReferenceString } from '@medplum/core';\nimport type { Communication } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport interface UseThreadInboxOptions {\n query: string;\n threadId: string | undefined;\n}\n\nexport interface UseThreadInboxReturn {\n loading: boolean;\n error: Error | null;\n // Tuple: [Parent Thread, Last Message in Thread (optional)]\n threadMessages: [Communication, Communication | undefined][];\n selectedThread: Communication | undefined;\n total: number | undefined;\n addThreadMessage: (message: Communication) => void;\n handleThreadStatusChange: (newStatus: Communication['status']) => void;\n refreshThreadMessages: () => Promise<void>;\n}\n\n/*\nuseThreadInbox is a hook that fetches all communications and returns the thread messages and selected thread.\nAll comunications returned do not have a partOf field.\nIt also provides a function to update the status of the selected thread.\n\n@param query - The query to fetch all communications.\n@param threadId - The id of the thread to select.\n@returns The thread messages and selected thread.\n@returns A function to update the status of the selected thread.\n*/\nexport function useThreadInbox({ query, threadId }: UseThreadInboxOptions): UseThreadInboxReturn {\n const medplum = useMedplum();\n const [loading, setLoading] = useState(true);\n const [threadMessages, setThreadMessages] = useState<[Communication, Communication | undefined][]>([]);\n const [selectedThread, setSelectedThread] = useState<Communication | undefined>(undefined);\n const [error, setError] = useState<Error | null>(null);\n const [total, setTotal] = useState<number | undefined>(undefined);\n\n const fetchAllCommunications = useCallback(async (): Promise<void> => {\n const searchParams = new URLSearchParams(query);\n searchParams.append('identifier:not', 'http://medplum.com/ai-message|');\n searchParams.append('part-of:missing', 'true');\n searchParams.append('_has:Communication:part-of:_id:not', 'null');\n\n const bundle = await medplum.search('Communication', searchParams.toString(), { cache: 'no-cache' });\n const parents =\n bundle.entry\n ?.map((entry) => entry.resource as Communication)\n .filter((r): r is Communication => r !== undefined) || [];\n\n if (bundle.total !== undefined) {\n setTotal(bundle.total);\n }\n\n if (parents.length === 0) {\n setThreadMessages([]);\n return;\n }\n\n const queryParts = parents.map((parent) => {\n const safeId = parent.id?.replaceAll('-', '') || '';\n const alias = `thread_${safeId}`;\n const ref = getReferenceString(parent);\n\n return `\n ${alias}: CommunicationList(\n part_of: \"${ref}\"\n _sort: \"-sent\"\n _count: 1\n ) {\n id\n meta {\n lastUpdated\n }\n partOf {\n reference\n }\n sender {\n display\n reference\n }\n payload {\n contentString\n }\n sent\n status\n }\n `;\n });\n\n const fullQuery = `\n query {\n ${queryParts.join('\\n')}\n }\n `;\n\n const response = await medplum.graphql(fullQuery);\n\n const threadsWithReplies = parents\n .map((parent) => {\n const safeId = parent.id?.replaceAll('-', '') || '';\n const alias = `thread_${safeId}`;\n const childList = response.data[alias] as Communication[] | undefined;\n const lastMessage = childList && childList.length > 0 ? childList[0] : undefined;\n return [parent, lastMessage];\n })\n .filter((thread): thread is [Communication, Communication] => thread[1] !== undefined);\n\n setThreadMessages(threadsWithReplies);\n }, [medplum, query]);\n\n useEffect(() => {\n setLoading(true);\n fetchAllCommunications()\n .catch((err: Error) => {\n setError(err);\n })\n .finally(() => {\n setLoading(false);\n });\n }, [fetchAllCommunications]);\n\n useEffect(() => {\n const fetchThread = async (): Promise<void> => {\n if (!threadId) {\n setSelectedThread(undefined);\n return;\n }\n\n const thread = threadMessages.find((t) => t[0].id === threadId);\n if (thread) {\n setSelectedThread(thread[0]);\n return;\n }\n\n const communication: Communication = await medplum.readResource('Communication', threadId);\n if (communication.partOf === undefined) {\n setSelectedThread(communication);\n } else {\n const parentRef = communication.partOf[0].reference;\n if (parentRef) {\n const parent = await medplum.readReference({ reference: parentRef });\n setSelectedThread(parent as Communication);\n }\n }\n };\n\n fetchThread().catch((err: Error) => {\n setError(err);\n });\n }, [threadId, threadMessages, medplum]);\n\n const handleThreadStatusChange = (newStatus: Communication['status']): void => {\n if (!selectedThread) {\n return;\n }\n const doUpdate = async (): Promise<void> => {\n const updatedThread = await medplum.updateResource({ ...selectedThread, status: newStatus });\n setSelectedThread(updatedThread);\n setThreadMessages((prev) =>\n prev.map(([parent, lastMsg]) => (parent.id === updatedThread.id ? [updatedThread, lastMsg] : [parent, lastMsg]))\n );\n };\n doUpdate().catch((err: Error) => setError(err));\n };\n\n const addThreadMessage = (message: Communication): void => {\n const doAdd = async (): Promise<void> => {\n await fetchAllCommunications();\n setThreadMessages((prev) => [[message, undefined], ...prev]);\n };\n doAdd().catch((err: Error) => setError(err));\n };\n\n return {\n loading,\n error,\n threadMessages,\n selectedThread,\n total,\n addThreadMessage,\n handleThreadStatusChange,\n refreshThreadMessages: fetchAllCommunications,\n };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n// Adapted from https://github.com/codyebberson/medplum-ai-realtime/blob/main/src/hooks/useWhisper.ts\nimport { ReconnectingWebSocket, sleep } from '@medplum/core';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nexport type WhisperStatus =\n | 'idle'\n | 'requesting_microphone'\n | 'connecting'\n | 'connected'\n | 'listening'\n | 'speech_started'\n | 'speech_stopped'\n | 'disconnected'\n | 'error';\n\nexport type TranscriptItem = {\n text: string;\n timestamp: string;\n};\n\nexport type UseWhisperOptions = {\n language?: string;\n model?: string;\n onTranscript?: (text: string) => void;\n /**\n * How long to keep the WebSocket warm after stop() before fully closing it, in milliseconds.\n * Defaults to 120000 (2 minutes). Set to 0 or a non-finite value to keep the socket warm\n * until unmount (the previous behavior).\n */\n idleTimeoutMs?: number;\n};\n\n// Fully close a warm-but-unused socket after this long; the timer resets whenever capture\n// (re)starts so an active or about-to-resume session is never torn down.\nconst DEFAULT_IDLE_TIMEOUT_MS = 120_000; // 2 minutes\n\nexport type UseWhisperResult = {\n status: WhisperStatus;\n error: unknown;\n transcripts: TranscriptItem[];\n start: () => Promise<void>;\n stop: () => void;\n isListening: boolean;\n};\n\nexport function useWhisper({\n language = 'en',\n model = 'gpt-4o-transcribe',\n onTranscript,\n idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,\n}: UseWhisperOptions): UseWhisperResult {\n const medplum = useMedplum();\n const onTranscriptRef = useRef(onTranscript);\n useEffect(() => {\n onTranscriptRef.current = onTranscript;\n }, [onTranscript]);\n const [status, setStatus] = useState<WhisperStatus>('idle');\n const [error, setError] = useState<unknown>(undefined);\n const [transcripts, setTranscripts] = useState<TranscriptItem[]>([]);\n\n const websocketRef = useRef<ReconnectingWebSocket | undefined>(undefined);\n const audioStreamRef = useRef<MediaStream | undefined>(undefined);\n const audioContextRef = useRef<AudioContext | undefined>(undefined);\n const audioProcessorRef = useRef<AudioWorkletNode | undefined>(undefined);\n const startingCaptureRef = useRef(false);\n const sessionReadyRef = useRef(false);\n const capturingRef = useRef(false);\n\n // Stop capturing audio and release the microphone, but leave the WebSocket + OpenAI\n // session warm so the next start() can reuse them. This is the user-facing `stop`.\n const stopCapture = useCallback(() => {\n capturingRef.current = false;\n\n audioProcessorRef.current?.disconnect();\n audioProcessorRef.current = undefined;\n\n audioContextRef.current?.close().catch(() => undefined);\n audioContextRef.current = undefined;\n\n audioStreamRef.current?.getTracks().forEach((track) => track.stop());\n audioStreamRef.current = undefined;\n\n setStatus(websocketRef.current ? 'idle' : 'disconnected');\n }, []);\n\n // Fully tear down the connection in addition to stopping capture. Used on unmount.\n const closeConnection = useCallback(() => {\n stopCapture();\n\n websocketRef.current?.close();\n websocketRef.current = undefined;\n\n sessionReadyRef.current = false;\n\n setStatus('disconnected');\n }, [stopCapture]);\n\n const setupSession = useCallback(() => {\n websocketRef.current?.send(\n JSON.stringify({\n type: 'session.update',\n session: {\n type: 'transcription',\n audio: {\n input: {\n format: {\n type: 'audio/pcm',\n rate: 24000,\n },\n transcription: {\n model,\n language,\n },\n turn_detection: {\n type: 'server_vad',\n threshold: 0.5,\n prefix_padding_ms: 300,\n silence_duration_ms: 200,\n },\n noise_reduction: {\n type: 'near_field',\n },\n },\n },\n },\n })\n );\n }, [language, model]);\n\n const startAudioCapture = useCallback(async () => {\n if (audioProcessorRef.current || startingCaptureRef.current) {\n return; // already capturing, or a start is already in flight\n }\n if (!audioStreamRef.current || !websocketRef.current) {\n return;\n }\n\n startingCaptureRef.current = true;\n try {\n const audioContext = new AudioContext({ sampleRate: 24000 });\n // AudioWorklet runs the PCM batching on the dedicated audio thread (the deprecated\n // ScriptProcessorNode ran it on the main thread). The processor module is loaded from a\n // Blob URL so the hook stays self-contained and needs no separately bundled worklet file.\n await audioContext.audioWorklet.addModule(getPcmWorkletUrl());\n\n // Re-validate after the async module load: capture may have been stopped, or the mic /\n // socket swapped out, while the worklet was loading.\n const audioStream = audioStreamRef.current;\n const websocket = websocketRef.current;\n if (!capturingRef.current || !audioStream || !websocket) {\n await audioContext.close().catch(() => undefined);\n return;\n }\n\n const source = audioContext.createMediaStreamSource(audioStream);\n const processor = new AudioWorkletNode(audioContext, PCM_WORKLET_NAME);\n\n processor.port.onmessage = (event: MessageEvent<Float32Array>) => {\n if (websocket.readyState !== WebSocket.OPEN) {\n console.warn('WebSocket is not open. Unable to send audio data.');\n return;\n }\n\n const pcm16Buffer = convertToPCM16(event.data);\n const base64Audio = btoa(String.fromCharCode(...pcm16Buffer));\n\n websocket.send(\n JSON.stringify({\n type: 'input_audio_buffer.append',\n audio: base64Audio,\n })\n );\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n\n audioContextRef.current = audioContext;\n audioProcessorRef.current = processor;\n\n setStatus('listening');\n } catch (err) {\n setError(err);\n setStatus('error');\n stopCapture();\n } finally {\n startingCaptureRef.current = false;\n }\n }, [stopCapture]);\n\n // Capture can only start once the user intends to capture, the session is ready, and the\n // mic is acquired \u2014 these now arrive in arbitrary order (parallel start, warm reuse,\n // background reconnect).\n const maybeStartAudioCapture = useCallback(() => {\n if (!capturingRef.current) {\n return; // user is not dictating\n }\n if (!sessionReadyRef.current) {\n return; // session.updated not received yet\n }\n if (!audioStreamRef.current) {\n return; // mic not acquired yet\n }\n if (audioProcessorRef.current) {\n return; // already capturing\n }\n // Discard any stale partial buffer left over from a previous utterance on this session.\n websocketRef.current?.send(JSON.stringify({ type: 'input_audio_buffer.clear' }));\n // startAudioCapture catches its own errors and never rejects; .catch is belt-and-suspenders.\n startAudioCapture().catch(() => undefined);\n }, [startAudioCapture]);\n\n const handleMessage = useCallback(\n (message: any) => {\n switch (message.type) {\n case 'session.created':\n setupSession();\n break;\n\n case 'session.updated':\n sessionReadyRef.current = true;\n maybeStartAudioCapture();\n break;\n\n case 'input_audio_buffer.speech_started':\n setStatus('speech_started');\n break;\n\n case 'input_audio_buffer.speech_stopped':\n setStatus('speech_stopped');\n break;\n\n case 'conversation.item.input_audio_transcription.completed':\n case 'input_audio_transcription.completed':\n if (message.transcript) {\n const item = {\n text: message.transcript,\n timestamp: new Date().toISOString(),\n };\n\n setTranscripts((prev) => [...prev, item]);\n onTranscriptRef.current?.(message.transcript);\n }\n break;\n\n case 'ai-realtime:connected':\n console.debug('[useWhisper] upstream connected');\n break;\n\n case 'ai-realtime:error':\n case 'error':\n console.error('[useWhisper] error event', message);\n setError(message);\n setStatus('error');\n break;\n\n default:\n console.debug('[useWhisper] unhandled message', message.type, message);\n break;\n }\n },\n [setupSession, maybeStartAudioCapture]\n );\n\n const acquireMicrophone = useCallback(async (): Promise<MediaStream> => {\n setStatus('requesting_microphone');\n const audioStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: 24000,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n });\n audioStreamRef.current = audioStream;\n return audioStream;\n }, []);\n\n const openWebSocket = useCallback((): ReconnectingWebSocket => {\n setStatus('connecting');\n const url = buildWebSocketUrl(medplum.getBaseUrl());\n console.debug('[useWhisper] connecting to', url);\n const websocket = new ReconnectingWebSocket(url);\n websocketRef.current = websocket;\n\n websocket.onopen = () => {\n sessionReadyRef.current = false;\n if (capturingRef.current) {\n setStatus('connected');\n } else {\n setStatus('idle');\n }\n websocket.send(\n JSON.stringify({\n type: 'ai-realtime:connect',\n accessToken: medplum.getAccessToken(),\n })\n );\n };\n websocket.onmessage = (event) => handleMessage(JSON.parse(event.data));\n websocket.onerror = (err) => {\n // While idle, let ReconnectingWebSocket retry silently rather than surfacing an error.\n if (!capturingRef.current) {\n return;\n }\n setError(err);\n closeConnection();\n setStatus('error');\n };\n\n websocket.onclose = () => {\n sessionReadyRef.current = false;\n if (!websocketRef.current) {\n return; // intentional closeConnection(); status already set\n }\n setStatus(capturingRef.current ? 'connecting' : 'idle');\n };\n\n return websocket;\n }, [medplum, handleMessage, closeConnection]);\n\n // Reuse the warm connection if one already exists; otherwise open a new one.\n const ensureConnected = useCallback((): ReconnectingWebSocket => {\n return websocketRef.current ?? openWebSocket();\n }, [openWebSocket]);\n\n const start = useCallback(async () => {\n try {\n setError(undefined);\n capturingRef.current = true;\n // Reuse a warm connection if present; otherwise open one. Acquire the mic concurrently\n // so its latency overlaps any handshake.\n ensureConnected();\n const micPromise = acquireMicrophone(); // status -> requesting_microphone\n await micPromise;\n // The session may already be ready (warm reuse) or arrive later via session.updated.\n maybeStartAudioCapture();\n } catch (err) {\n setError(err);\n setStatus('error');\n stopCapture();\n }\n }, [acquireMicrophone, ensureConnected, stopCapture, maybeStartAudioCapture]);\n\n // Fully close a warm-but-idle socket after idleTimeoutMs. 'idle' with a live socket is exactly\n // the warm-but-unused state (stopCapture and the background-reconnect handlers settle there\n // while not capturing). Any status change away from 'idle' \u2014 including start() ->\n // 'requesting_microphone', even on warm reuse \u2014 runs the cleanup and cancels the pending close.\n useEffect(() => {\n if (status !== 'idle' || !websocketRef.current || !Number.isFinite(idleTimeoutMs) || idleTimeoutMs <= 0) {\n return undefined;\n }\n // Use @medplum/core's sleep() as a cancellable timer: aborting its signal on cleanup clears\n // the underlying timeout and rejects, so the close never fires once we leave the idle state.\n const controller = new AbortController();\n sleep(idleTimeoutMs, { signal: controller.signal })\n .then(() => closeConnection())\n .catch(() => undefined); // rejects when aborted on cleanup\n return () => controller.abort();\n }, [status, idleTimeoutMs, closeConnection]);\n\n useEffect(() => {\n return () => closeConnection();\n }, [closeConnection]);\n\n return {\n status,\n error,\n transcripts,\n start,\n stop: stopCapture,\n isListening: status === 'listening' || status === 'speech_started' || status === 'speech_stopped',\n };\n}\n\nexport function convertToPCM16(float32Array: Float32Array): Uint8Array {\n const pcm16Array = new Int16Array(float32Array.length);\n\n for (let i = 0; i < float32Array.length; i++) {\n const sample = Math.max(-1, Math.min(1, float32Array[i]));\n pcm16Array[i] = sample * 0x7fff;\n }\n\n return new Uint8Array(pcm16Array.buffer);\n}\n\nfunction buildWebSocketUrl(baseUrl: string): string {\n const url = new URL('ws/ai-realtime', baseUrl);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n return url.toString();\n}\n\n// Name the processor is registered under inside the AudioWorkletGlobalScope.\nconst PCM_WORKLET_NAME = 'medplum-pcm-worklet';\n\n// Source for the AudioWorklet processor. It runs on the audio rendering thread and replaces the\n// deprecated ScriptProcessorNode. process() receives 128-sample render quanta, so it accumulates\n// them into ~4096-sample batches (about 170ms at 24kHz) \u2014 matching the old ScriptProcessorNode\n// buffer size \u2014 before transferring each batch to the main thread, where it is converted to PCM16\n// and sent over the WebSocket. Defined as a string and loaded via a Blob URL so the hook ships\n// without a separately bundled worklet file.\nconst PCM_WORKLET_SOURCE = `\nclass PcmWorkletProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.chunks = [];\n this.length = 0;\n this.targetLength = 4096;\n }\n\n process(inputs) {\n const channel = inputs[0] && inputs[0][0];\n if (!channel) {\n return true;\n }\n // The render quantum buffer is reused between calls, so copy it.\n this.chunks.push(new Float32Array(channel));\n this.length += channel.length;\n if (this.length >= this.targetLength) {\n const merged = new Float32Array(this.length);\n let offset = 0;\n for (const chunk of this.chunks) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n this.port.postMessage(merged, [merged.buffer]);\n this.chunks = [];\n this.length = 0;\n }\n return true;\n }\n}\n\nregisterProcessor('${PCM_WORKLET_NAME}', PcmWorkletProcessor);\n`;\n\nlet pcmWorkletUrl: string | undefined;\n\n// Lazily create (and cache) a Blob URL for the worklet module. addModule() must be called per\n// AudioContext, but the URL itself can be reused across contexts.\nfunction getPcmWorkletUrl(): string {\n if (!pcmWorkletUrl) {\n const blob = new Blob([PCM_WORKLET_SOURCE], { type: 'application/javascript' });\n pcmWorkletUrl = URL.createObjectURL(blob);\n }\n return pcmWorkletUrl;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["index_exports", "__export", "MedplumProvider", "QUESTIONNAIRE_CALCULATED_EXPRESSION_URL", "QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL", "QUESTIONNAIRE_HIDDEN_URL", "QUESTIONNAIRE_ITEM_CONTROL_URL", "QUESTIONNAIRE_REFERENCE_FILTER_URL", "QUESTIONNAIRE_REFERENCE_RESOURCE_URL", "QUESTIONNAIRE_SIGNATURE_REQUIRED_URL", "QUESTIONNAIRE_SIGNATURE_RESPONSE_URL", "QUESTIONNAIRE_VALIDATION_ERROR_URL", "QuestionnaireItemType", "buildInitialResponse", "buildInitialResponseItem", "convertToPCM16", "evaluateCalculatedExpressionsInQuestionnaire", "getItemAnswerOptionValue", "getItemEnableWhenValueAnswer", "getItemInitialValue", "getNewMultiSelectValues", "getQuestionnaireItemReferenceFilter", "getQuestionnaireItemReferenceTargetTypes", "getResponseItemAnswerValue", "isChoiceQuestion", "isQuestionEnabled", "reactContext", "removeDisabledItems", "setQuestionnaireItemReferenceTargetTypes", "typedValueToResponseItem", "useCachedBinaryUrl", "useMedicationIFrame", "useMedicationOrder", "useMedicationOrderSet", "useMedplum", "useMedplumContext", "useMedplumNavigate", "useMedplumProfile", "useNotificationCount", "usePatientSummaryData", "usePharmacySearch", "usePrevious", "useQuestionnaireForm", "useResource", "useSearch", "useSearchOne", "useSearchResources", "useSubscription", "useSyncOrderSet", "useThreadInbox", "useWhisper", "__toCommonJS", "import_react", "import_react", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "import_jsx_runtime", "EVENTS_TO_TRACK", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "eventListener", "s", "event", "medplumContext", "reactContext", "path", "import_react", "urls", "useCachedBinaryUrl", "binaryUrl", "binaryResourceUrl", "binaryUrlSearchParams", "binaryUrlExpires", "cachedUrl", "expires", "import_react", "useMedicationIFrame", "syncBotIdentifier", "iframeBotIdentifier", "options", "medplum", "useMedplum", "patientId", "onPatientSyncSuccess", "onIframeSuccess", "onError", "iframeUrl", "setIframeUrl", "onPatientSyncSuccessRef", "onIframeSuccessRef", "onErrorRef", "cancelled", "result", "err", "import_core", "import_react", "useMedicationOrder", "medplum", "useMedplum", "searchMedications", "params", "url", "body", "response", "e", "r", "orderMedication", "input", "import_core", "import_react", "useMedicationOrderSet", "options", "medplum", "useMedplum", "patientId", "planDefinitionId", "vendorOrderSetId", "appId", "url", "setUrl", "loading", "setLoading", "error", "setError", "optionsRef", "buildRequest", "o", "hasPd", "hasVendorId", "callOperation", "req", "operationUrl", "body", "response", "runIdRef", "refresh", "myRunId", "next", "err", "cancelled", "import_react", "import_core", "import_react", "SUBSCRIPTION_DEBOUNCE_MS", "useSubscription", "criteria", "callback", "options", "medplum", "useMedplum", "effectiveCriteria", "useMedplumProfile", "emitter", "setEmitter", "memoizedSubProps", "setMemoizedSubProps", "listeningRef", "unsubTimerRef", "prevCriteriaRef", "prevMemoizedSubPropsRef", "callbackRef", "onWebSocketOpenRef", "onWebSocketCloseRef", "onSubscriptionConnectRef", "onSubscriptionDisconnectRef", "onErrorRef", "shouldSubscribe", "emitterCallback", "event", "onWebSocketOpen", "onWebSocketClose", "onSubscriptionConnect", "onSubscriptionDisconnect", "onError", "useNotificationCount", "options", "medplum", "useMedplum", "resourceType", "countCriteria", "subscriptionCriteria", "count", "setCount", "updateCount", "cache", "result", "useSubscription", "import_core", "import_react", "buildSearchKey", "search", "param", "query", "queryStr", "entries", "a", "b", "sorted", "v", "buildSectionsFingerprint", "sections", "s", "searchKeys", "usePatientSummaryData", "patient", "medplum", "useMedplum", "sectionData", "setSectionData", "loading", "setLoading", "error", "setError", "sectionsFingerprint", "stableSections", "patientId", "stale", "ref", "searchMeta", "uniqueSearches", "searchKeyToIndex", "sectionSearchMapping", "section", "mapping", "deduplicationKey", "idx", "promises", "patientParam", "baseQuery", "value", "key", "settledResults", "data", "sectionResult", "searchIdx", "resultKey", "settled", "failures", "r", "f", "firstError", "err", "import_core", "import_react", "usePharmacySearch", "searchBotIdentifier", "addPharmacyBotIdentifier", "medplum", "useMedplum", "searchPharmacies", "params", "response", "addToFavorites", "import_react", "usePrevious", "value", "ref", "import_core", "import_react", "import_core", "import_react", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "getInitialResource", "setResourceIfChanged", "r", "subscribed", "newValue", "err", "import_core", "QuestionnaireItemType", "QUESTIONNAIRE_ITEM_CONTROL_URL", "QUESTIONNAIRE_REFERENCE_FILTER_URL", "QUESTIONNAIRE_REFERENCE_RESOURCE_URL", "QUESTIONNAIRE_VALIDATION_ERROR_URL", "QUESTIONNAIRE_ENABLED_WHEN_EXPRESSION_URL", "QUESTIONNAIRE_CALCULATED_EXPRESSION_URL", "QUESTIONNAIRE_SIGNATURE_REQUIRED_URL", "QUESTIONNAIRE_SIGNATURE_RESPONSE_URL", "QUESTIONNAIRE_HIDDEN_URL", "isChoiceQuestion", "item", "removeDisabledItems", "questionnaire", "response", "filterEnabledResponseItems", "items", "responseItems", "result", "responseItem", "i", "isQuestionEnabled", "questionnaireResponse", "extensionResult", "isQuestionEnabledViaExtension", "isQuestionEnabledViaEnabledWhen", "extension", "expression", "value", "enableBehavior", "enableWhen", "actualAnswers", "getByLinkId", "anyMatch", "allMatch", "checkAnswers", "evaluateCalculatedExpressionsInQuestionnaire", "r", "evaluateQuestionnaireItemCalculatedExpressions", "calculatedValue", "evaluateCalculatedExpression", "answer", "typedValueToResponseItem", "error", "questionnaireItemTypesAllowedPropertyTypes", "getNewMultiSelectValues", "selected", "propertyName", "selectedStr", "option", "candidate", "getItemAnswerOptionValue", "optionValue", "linkId", "nestedAnswer", "evaluateMatch", "actualAnswer", "expectedAnswer", "operator", "fhirPathOperator", "answers", "getItemEnableWhenValueAnswer", "actualAnswerValue", "getResponseItemAnswerValue", "getQuestionnaireItemReferenceTargetTypes", "c", "setQuestionnaireItemReferenceTargetTypes", "targetTypes", "e", "t", "getQuestionnaireItemReferenceFilter", "subject", "encounter", "filter", "parts", "part", "key", "buildInitialResponse", "buildInitialResponseItems", "existingResponseItems", "existingResponseItem", "generateId", "buildInitialResponseAnswer", "buildInitialResponseItem", "nextId", "initial", "getItemInitialValue", "useQuestionnaireForm", "props", "questionnaire", "useResource", "defaultResponse", "forceUpdate", "x", "state", "getPages", "buildInitialResponse", "emitChange", "getResponseItemByContext", "context", "item", "currentItem", "contextElement", "i", "onNextPage", "onPrevPage", "onAddGroup", "responseItem", "buildInitialResponseItem", "onAddAnswer", "onChangeAnswer", "answer", "onChangeSignature", "signature", "currentResponse", "ext", "QUESTIONNAIRE_SIGNATURE_RESPONSE_URL", "updateCalculatedExpressions", "response", "evaluateCalculatedExpressionsInQuestionnaire", "currentQuestionnaire", "removeDisabledItems", "getItemsForPage", "getResponseItemsForPage", "QUESTIONNAIRE_ITEM_CONTROL_URL", "index", "pages", "activePage", "questionnaireResponse", "import_core", "import_react", "import_react", "useDebouncedValue", "value", "waitMs", "options", "debouncedValue", "setDebouncedValue", "mountedRef", "timeoutRef", "cooldownRef", "cancel", "DEFAULT_DEBOUNCE_MS", "useSearch", "resourceType", "query", "options", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "searchKey", "searchValue", "enabled", "debounceMs", "debouncedSearchValue", "useDebouncedValue", "active", "res", "err", "import_core", "import_react", "useSyncOrderSet", "medplum", "useMedplum", "planDefinitionId", "err", "i", "import_core", "import_react", "useThreadInbox", "query", "threadId", "medplum", "useMedplum", "loading", "setLoading", "threadMessages", "setThreadMessages", "selectedThread", "setSelectedThread", "error", "setError", "total", "setTotal", "fetchAllCommunications", "searchParams", "bundle", "parents", "entry", "r", "fullQuery", "parent", "alias", "ref", "response", "threadsWithReplies", "childList", "lastMessage", "thread", "err", "t", "communication", "parentRef", "message", "prev", "newStatus", "updatedThread", "lastMsg", "import_core", "import_react", "DEFAULT_IDLE_TIMEOUT_MS", "useWhisper", "language", "model", "onTranscript", "idleTimeoutMs", "medplum", "useMedplum", "onTranscriptRef", "status", "setStatus", "error", "setError", "transcripts", "setTranscripts", "websocketRef", "audioStreamRef", "audioContextRef", "audioProcessorRef", "startingCaptureRef", "sessionReadyRef", "capturingRef", "stopCapture", "track", "closeConnection", "setupSession", "startAudioCapture", "audioContext", "getPcmWorkletUrl", "audioStream", "websocket", "source", "processor", "PCM_WORKLET_NAME", "event", "pcm16Buffer", "convertToPCM16", "base64Audio", "err", "maybeStartAudioCapture", "handleMessage", "message", "item", "prev", "acquireMicrophone", "openWebSocket", "url", "buildWebSocketUrl", "ensureConnected", "start", "controller", "float32Array", "pcm16Array", "i", "sample", "baseUrl", "PCM_WORKLET_SOURCE", "pcmWorkletUrl", "blob"]
7
7
  }