@medplum/react-hooks 5.1.8 → 5.1.10

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/MedplumProvider/MedplumProvider.tsx", "../../src/MedplumProvider/MedplumProvider.context.ts", "../../src/useCachedBinaryUrl/useCachedBinaryUrl.ts", "../../src/useEPrescribingIFrame/useEPrescribingIFrame.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/useThreadInbox/useThreadInbox.ts"],
4
- "sourcesContent": ["// 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 EPrescribingIFrameOptions {\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 an e-prescribing system and\n * returns the 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 e-prescribing iframe URL, or undefined while loading.\n */\nexport function useEPrescribingIFrame(\n syncBotIdentifier: Identifier,\n iframeBotIdentifier: Identifier,\n options: EPrescribingIFrameOptions\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\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 } 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 [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 (prevCriteriaRef.current !== criteria || !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)) {\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 = criteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe && criteria) {\n setEmitter(medplum.subscribeToCriteria(criteria, memoizedSubProps));\n } else if (!criteria) {\n setEmitter(undefined);\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n if (criteria) {\n medplum.unsubscribeFromCriteria(criteria, memoizedSubProps);\n }\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, criteria, 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 as Resource[]) : [];\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} 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 if (!currentResponse) {\n return;\n }\n updateCalculatedExpressions();\n forceUpdate();\n props.onChange?.(currentResponse);\n }\n\n return {\n loading: false,\n pagination: !!state.current.pages,\n questionnaire: state.current.questionnaire,\n questionnaireResponse: 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 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 { 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 } as any);\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"],
5
- "mappings": "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",
6
- "names": ["useEffect", "useMemo", "useState", "createContext", "useContext", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "jsx", "EVENTS_TO_TRACK", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "useState", "useEffect", "eventListener", "s", "event", "medplumContext", "useMemo", "reactContext", "path", "useMemo", "urls", "useCachedBinaryUrl", "binaryUrl", "binaryResourceUrl", "binaryUrlSearchParams", "binaryUrlExpires", "cachedUrl", "expires", "useEffect", "useRef", "useState", "useEPrescribingIFrame", "syncBotIdentifier", "iframeBotIdentifier", "options", "medplum", "useMedplum", "patientId", "onPatientSyncSuccess", "onIframeSuccess", "onError", "iframeUrl", "setIframeUrl", "useState", "onPatientSyncSuccessRef", "useRef", "onIframeSuccessRef", "onErrorRef", "useEffect", "cancelled", "result", "err", "useCallback", "useEffect", "useState", "deepEquals", "useCallback", "useEffect", "useRef", "useState", "SUBSCRIPTION_DEBOUNCE_MS", "useSubscription", "criteria", "callback", "options", "medplum", "useMedplum", "emitter", "setEmitter", "useState", "memoizedSubProps", "setMemoizedSubProps", "listeningRef", "useRef", "unsubTimerRef", "prevCriteriaRef", "prevMemoizedSubPropsRef", "callbackRef", "onWebSocketOpenRef", "onWebSocketCloseRef", "onSubscriptionConnectRef", "onSubscriptionDisconnectRef", "onErrorRef", "useEffect", "deepEquals", "shouldSubscribe", "emitterCallback", "useCallback", "event", "onWebSocketOpen", "onWebSocketClose", "onSubscriptionConnect", "onSubscriptionDisconnect", "onError", "useNotificationCount", "options", "medplum", "useMedplum", "resourceType", "countCriteria", "subscriptionCriteria", "count", "setCount", "useState", "updateCount", "useCallback", "cache", "result", "useEffect", "useSubscription", "resolveId", "useEffect", "useMemo", "useState", "buildSearchKey", "search", "param", "query", "queryStr", "entries", "a", "b", "sorted", "v", "buildSectionsFingerprint", "sections", "s", "searchKeys", "usePatientSummaryData", "patient", "medplum", "useMedplum", "sectionData", "setSectionData", "useState", "loading", "setLoading", "error", "setError", "sectionsFingerprint", "stableSections", "useMemo", "patientId", "resolveId", "useEffect", "stale", "ref", "searchMeta", "uniqueSearches", "searchKeyToIndex", "sectionSearchMapping", "section", "mapping", "deduplicationKey", "idx", "promises", "patientParam", "baseQuery", "value", "key", "settledResults", "data", "sectionResult", "searchIdx", "resultKey", "settled", "failures", "r", "firstError", "err", "isAddPharmacyResponse", "isOrganizationArray", "useCallback", "usePharmacySearch", "searchBotIdentifier", "addPharmacyBotIdentifier", "medplum", "useMedplum", "searchPharmacies", "useCallback", "params", "response", "isOrganizationArray", "addToFavorites", "isAddPharmacyResponse", "useEffect", "useRef", "usePrevious", "value", "ref", "getExtension", "useReducer", "useRef", "deepEquals", "isReference", "isResource", "normalizeOperationOutcome", "useCallback", "useEffect", "useState", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "useState", "getInitialResource", "setResourceIfChanged", "useCallback", "r", "deepEquals", "useEffect", "subscribed", "newValue", "isReference", "err", "normalizeOperationOutcome", "isResource", "EMPTY", "HTTP_HL7_ORG", "PropertyType", "append", "capitalize", "deepClone", "evalFhirPathTyped", "getExtension", "getReferenceString", "getTypedPropertyValueWithoutSchema", "normalizeErrorString", "splitN", "toJsBoolean", "toTypedValue", "typedValueToString", "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", "isQuestionEnabled", "questionnaireResponse", "extensionResult", "isQuestionEnabledViaExtension", "isQuestionEnabledViaEnabledWhen", "extension", "expression", "value", "result", "enableBehavior", "enableWhen", "actualAnswers", "getByLinkId", "anyMatch", "allMatch", "checkAnswers", "evaluateCalculatedExpressionsInQuestionnaire", "items", "response", "responseItems", "responseItem", "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", "questionnaire", "buildInitialResponseItems", "existingResponseItems", "existingResponseItem", "generateId", "buildInitialResponseAnswer", "buildInitialResponseItem", "nextId", "initial", "getItemInitialValue", "useQuestionnaireForm", "props", "questionnaire", "useResource", "defaultResponse", "forceUpdate", "useReducer", "x", "state", "useRef", "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", "getItemsForPage", "getResponseItemsForPage", "getExtension", "QUESTIONNAIRE_ITEM_CONTROL_URL", "index", "pages", "activePage", "questionnaireResponse", "allOk", "normalizeOperationOutcome", "useEffect", "useMemo", "useState", "useCallback", "useEffect", "useRef", "useState", "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", "useState", "result", "setResult", "outcome", "setOutcome", "searchKey", "searchValue", "useMemo", "enabled", "debounceMs", "debouncedSearchValue", "useDebouncedValue", "useEffect", "active", "res", "allOk", "err", "normalizeOperationOutcome", "getReferenceString", "useCallback", "useEffect", "useState", "useThreadInbox", "query", "threadId", "medplum", "useMedplum", "loading", "setLoading", "useState", "threadMessages", "setThreadMessages", "selectedThread", "setSelectedThread", "error", "setError", "total", "setTotal", "fetchAllCommunications", "useCallback", "searchParams", "bundle", "parents", "entry", "r", "fullQuery", "parent", "alias", "ref", "getReferenceString", "response", "threadsWithReplies", "childList", "lastMessage", "thread", "useEffect", "err", "t", "communication", "parentRef", "message", "prev", "newStatus", "updatedThread", "lastMsg"]
4
+ "sourcesContent": ["// 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 EPrescribingIFrameOptions {\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 an e-prescribing system and\n * returns the 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 e-prescribing iframe URL, or undefined while loading.\n */\nexport function useEPrescribingIFrame(\n syncBotIdentifier: Identifier,\n iframeBotIdentifier: Identifier,\n options: EPrescribingIFrameOptions\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\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 } 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 [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 (prevCriteriaRef.current !== criteria || !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)) {\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 = criteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe && criteria) {\n setEmitter(medplum.subscribeToCriteria(criteria, memoizedSubProps));\n } else if (!criteria) {\n setEmitter(undefined);\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n if (criteria) {\n medplum.unsubscribeFromCriteria(criteria, memoizedSubProps);\n }\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, criteria, 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 { 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"],
5
+ "mappings": "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",
6
+ "names": ["useEffect", "useMemo", "useState", "createContext", "useContext", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "jsx", "EVENTS_TO_TRACK", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "useState", "useEffect", "eventListener", "s", "event", "medplumContext", "useMemo", "reactContext", "path", "useMemo", "urls", "useCachedBinaryUrl", "binaryUrl", "binaryResourceUrl", "binaryUrlSearchParams", "binaryUrlExpires", "cachedUrl", "expires", "useEffect", "useRef", "useState", "useEPrescribingIFrame", "syncBotIdentifier", "iframeBotIdentifier", "options", "medplum", "useMedplum", "patientId", "onPatientSyncSuccess", "onIframeSuccess", "onError", "iframeUrl", "setIframeUrl", "useState", "onPatientSyncSuccessRef", "useRef", "onIframeSuccessRef", "onErrorRef", "useEffect", "cancelled", "result", "err", "useCallback", "useEffect", "useState", "deepEquals", "useCallback", "useEffect", "useRef", "useState", "SUBSCRIPTION_DEBOUNCE_MS", "useSubscription", "criteria", "callback", "options", "medplum", "useMedplum", "emitter", "setEmitter", "useState", "memoizedSubProps", "setMemoizedSubProps", "listeningRef", "useRef", "unsubTimerRef", "prevCriteriaRef", "prevMemoizedSubPropsRef", "callbackRef", "onWebSocketOpenRef", "onWebSocketCloseRef", "onSubscriptionConnectRef", "onSubscriptionDisconnectRef", "onErrorRef", "useEffect", "deepEquals", "shouldSubscribe", "emitterCallback", "useCallback", "event", "onWebSocketOpen", "onWebSocketClose", "onSubscriptionConnect", "onSubscriptionDisconnect", "onError", "useNotificationCount", "options", "medplum", "useMedplum", "resourceType", "countCriteria", "subscriptionCriteria", "count", "setCount", "useState", "updateCount", "useCallback", "cache", "result", "useEffect", "useSubscription", "resolveId", "useEffect", "useMemo", "useState", "buildSearchKey", "search", "param", "query", "queryStr", "entries", "a", "b", "sorted", "v", "buildSectionsFingerprint", "sections", "s", "searchKeys", "usePatientSummaryData", "patient", "medplum", "useMedplum", "sectionData", "setSectionData", "useState", "loading", "setLoading", "error", "setError", "sectionsFingerprint", "stableSections", "useMemo", "patientId", "resolveId", "useEffect", "stale", "ref", "searchMeta", "uniqueSearches", "searchKeyToIndex", "sectionSearchMapping", "section", "mapping", "deduplicationKey", "idx", "promises", "patientParam", "baseQuery", "value", "key", "settledResults", "data", "sectionResult", "searchIdx", "resultKey", "settled", "failures", "r", "firstError", "err", "isAddPharmacyResponse", "isOrganizationArray", "useCallback", "usePharmacySearch", "searchBotIdentifier", "addPharmacyBotIdentifier", "medplum", "useMedplum", "searchPharmacies", "useCallback", "params", "response", "isOrganizationArray", "addToFavorites", "isAddPharmacyResponse", "useEffect", "useRef", "usePrevious", "value", "ref", "getExtension", "useReducer", "useRef", "deepEquals", "isReference", "isResource", "normalizeOperationOutcome", "useCallback", "useEffect", "useState", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "useState", "getInitialResource", "setResourceIfChanged", "useCallback", "r", "deepEquals", "useEffect", "subscribed", "newValue", "isReference", "err", "normalizeOperationOutcome", "isResource", "EMPTY", "HTTP_HL7_ORG", "PropertyType", "append", "capitalize", "deepClone", "evalFhirPathTyped", "getExtension", "getReferenceString", "getTypedPropertyValueWithoutSchema", "normalizeErrorString", "splitN", "toJsBoolean", "toTypedValue", "typedValueToString", "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", "useReducer", "x", "state", "useRef", "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", "getExtension", "QUESTIONNAIRE_ITEM_CONTROL_URL", "index", "pages", "activePage", "questionnaireResponse", "allOk", "normalizeOperationOutcome", "useEffect", "useMemo", "useState", "useCallback", "useEffect", "useRef", "useState", "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", "useState", "result", "setResult", "outcome", "setOutcome", "searchKey", "searchValue", "useMemo", "enabled", "debounceMs", "debouncedSearchValue", "useDebouncedValue", "useEffect", "active", "res", "allOk", "err", "normalizeOperationOutcome", "getReferenceString", "useCallback", "useEffect", "useState", "useThreadInbox", "query", "threadId", "medplum", "useMedplum", "loading", "setLoading", "useState", "threadMessages", "setThreadMessages", "selectedThread", "setSelectedThread", "error", "setError", "total", "setTotal", "fetchAllCommunications", "useCallback", "searchParams", "bundle", "parents", "entry", "r", "fullQuery", "parent", "alias", "ref", "getReferenceString", "response", "threadsWithReplies", "childList", "lastMessage", "thread", "useEffect", "err", "t", "communication", "parentRef", "message", "prev", "newStatus", "updatedThread", "lastMsg"]
7
7
  }