@medplum/react 1.0.6 → 2.0.1

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.
Files changed (39) hide show
  1. package/dist/cjs/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +6 -0
  2. package/dist/cjs/ReferenceRangeEditor/ReferenceRangeEditor.d.ts +1 -2
  3. package/dist/cjs/ResourceTimeline/ResourceTimeline.d.ts +2 -2
  4. package/dist/cjs/auth/AuthenticationForm.d.ts +15 -1
  5. package/dist/cjs/auth/SignInForm.d.ts +1 -0
  6. package/dist/cjs/index.cjs +150 -167
  7. package/dist/cjs/index.cjs.map +1 -1
  8. package/dist/cjs/index.min.cjs +1 -1
  9. package/dist/esm/DefaultResourceTimeline/DefaultResourceTimeline.mjs +6 -18
  10. package/dist/esm/DefaultResourceTimeline/DefaultResourceTimeline.mjs.map +1 -1
  11. package/dist/esm/EncounterTimeline/EncounterTimeline.mjs +7 -24
  12. package/dist/esm/EncounterTimeline/EncounterTimeline.mjs.map +1 -1
  13. package/dist/esm/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +6 -0
  14. package/dist/esm/OperationOutcomeAlert/OperationOutcomeAlert.mjs +13 -0
  15. package/dist/esm/OperationOutcomeAlert/OperationOutcomeAlert.mjs.map +1 -0
  16. package/dist/esm/PatientTimeline/PatientTimeline.mjs +13 -20
  17. package/dist/esm/PatientTimeline/PatientTimeline.mjs.map +1 -1
  18. package/dist/esm/ReferenceRangeEditor/ReferenceRangeEditor.d.ts +1 -2
  19. package/dist/esm/ReferenceRangeEditor/ReferenceRangeEditor.mjs.map +1 -1
  20. package/dist/esm/ResourcePropertyDisplay/ResourcePropertyDisplay.mjs +2 -2
  21. package/dist/esm/ResourcePropertyDisplay/ResourcePropertyDisplay.mjs.map +1 -1
  22. package/dist/esm/ResourcePropertyInput/ResourcePropertyInput.mjs +2 -2
  23. package/dist/esm/ResourcePropertyInput/ResourcePropertyInput.mjs.map +1 -1
  24. package/dist/esm/ResourceTimeline/ResourceTimeline.d.ts +2 -2
  25. package/dist/esm/ResourceTimeline/ResourceTimeline.mjs +19 -24
  26. package/dist/esm/ResourceTimeline/ResourceTimeline.mjs.map +1 -1
  27. package/dist/esm/ServiceRequestTimeline/ServiceRequestTimeline.mjs +8 -30
  28. package/dist/esm/ServiceRequestTimeline/ServiceRequestTimeline.mjs.map +1 -1
  29. package/dist/esm/auth/AuthenticationForm.d.ts +15 -1
  30. package/dist/esm/auth/AuthenticationForm.mjs +66 -34
  31. package/dist/esm/auth/AuthenticationForm.mjs.map +1 -1
  32. package/dist/esm/auth/NewUserForm.mjs +3 -4
  33. package/dist/esm/auth/NewUserForm.mjs.map +1 -1
  34. package/dist/esm/auth/SignInForm.d.ts +1 -0
  35. package/dist/esm/auth/SignInForm.mjs +27 -19
  36. package/dist/esm/auth/SignInForm.mjs.map +1 -1
  37. package/dist/esm/index.min.mjs +1 -1
  38. package/package.json +17 -17
  39. package/rollup.config.mjs +0 -110
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ import { OperationOutcomeIssue } from '@medplum/fhirtypes';
3
+ export interface OperationOutcomeAlertProps {
4
+ issues?: OperationOutcomeIssue[];
5
+ }
6
+ export declare function OperationOutcomeAlert(props: OperationOutcomeAlertProps): JSX.Element | null;
@@ -1,13 +1,12 @@
1
1
  /// <reference types="react" />
2
2
  import { ObservationDefinition, ObservationDefinitionQualifiedInterval } from '@medplum/fhirtypes';
3
- declare const intervalFilters: readonly ["gender", "age", "gestationalAge", "context", "appliesTo"];
4
3
  export interface ReferenceRangeEditorProps {
5
4
  definition: ObservationDefinition;
6
5
  onSubmit: (result: ObservationDefinition) => void;
7
6
  }
8
7
  type IntervalGroup = {
9
8
  id: string;
10
- filters: Record<typeof intervalFilters[number], any>;
9
+ filters: Record<string, any>;
11
10
  intervals: ObservationDefinitionQualifiedInterval[];
12
11
  };
13
12
  export declare function ReferenceRangeEditor(props: ReferenceRangeEditorProps): JSX.Element;
@@ -1,9 +1,9 @@
1
1
  /// <reference types="react" />
2
- import { ProfileResource } from '@medplum/core';
2
+ import { MedplumClient, ProfileResource } from '@medplum/core';
3
3
  import { Attachment, Bundle, Communication, Media, Reference, Resource } from '@medplum/fhirtypes';
4
4
  export interface ResourceTimelineProps<T extends Resource> {
5
5
  value: T | Reference<T>;
6
- buildSearchRequests: (resource: T) => Bundle;
6
+ loadTimelineResources: (medplum: MedplumClient, resource: T) => Promise<Bundle[]>;
7
7
  createCommunication?: (resource: T, sender: ProfileResource, text: string) => Communication;
8
8
  createMedia?: (resource: T, operator: ProfileResource, attachment: Attachment) => Media;
9
9
  }
@@ -1,10 +1,24 @@
1
1
  import { BaseLoginRequest, LoginAuthenticationResponse } from '@medplum/core';
2
2
  import React from 'react';
3
3
  export interface AuthenticationFormProps extends BaseLoginRequest {
4
- readonly generatePkce?: boolean;
5
4
  readonly onForgotPassword?: () => void;
6
5
  readonly onRegister?: () => void;
7
6
  readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
8
7
  readonly children?: React.ReactNode;
9
8
  }
10
9
  export declare function AuthenticationForm(props: AuthenticationFormProps): JSX.Element;
10
+ export interface EmailFormProps extends BaseLoginRequest {
11
+ readonly generatePkce?: boolean;
12
+ readonly onRegister?: () => void;
13
+ readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
14
+ readonly setEmail: (email: string) => void;
15
+ readonly children?: React.ReactNode;
16
+ }
17
+ export declare function EmailForm(props: EmailFormProps): JSX.Element;
18
+ export interface PasswordFormProps extends BaseLoginRequest {
19
+ readonly email: string;
20
+ readonly onForgotPassword?: () => void;
21
+ readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
22
+ readonly children?: React.ReactNode;
23
+ }
24
+ export declare function PasswordForm(props: PasswordFormProps): JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { BaseLoginRequest } from '@medplum/core';
2
2
  import React from 'react';
3
3
  export interface SignInFormProps extends BaseLoginRequest {
4
+ readonly login?: string;
4
5
  readonly chooseScopes?: boolean;
5
6
  readonly onSuccess?: () => void;
6
7
  readonly onForgotPassword?: () => void;
@@ -630,6 +630,13 @@
630
630
  return undefined;
631
631
  }
632
632
 
633
+ function OperationOutcomeAlert(props) {
634
+ if (!props.issues) {
635
+ return null;
636
+ }
637
+ return (React.createElement(core$1.Alert, { icon: React.createElement(icons.IconAlertCircle, { size: 16 }), color: "red" }, props.issues.map((issue) => (React.createElement("div", { "data-testid": "text-field-error", key: issue.details?.text }, issue.details?.text)))));
638
+ }
639
+
633
640
  /**
634
641
  * Dynamically loads the recaptcha script.
635
642
  * We do not want to load the script on page load unless the user needs it.
@@ -684,12 +691,11 @@
684
691
  }
685
692
  } },
686
693
  React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, props.children),
687
- issues && (React.createElement(core$1.Alert, { icon: React.createElement(icons.IconAlertCircle, { size: 16 }), color: "red" }, issues.map((issue) => (React.createElement("div", { "data-testid": "text-field-error", key: issue.details?.text }, issue.details?.text))))),
694
+ React.createElement(OperationOutcomeAlert, { issues: issues }),
688
695
  googleClientId && (React.createElement(React.Fragment, null,
689
696
  React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
690
697
  React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: async (response) => {
691
698
  try {
692
- await medplum.startPkce();
693
699
  props.handleAuthResponse(await medplum.startGoogleLogin({
694
700
  googleClientId: response.clientId,
695
701
  googleCredential: response.credential,
@@ -757,47 +763,79 @@
757
763
  }
758
764
 
759
765
  function AuthenticationForm(props) {
760
- const { generatePkce, onForgotPassword, onRegister, handleAuthResponse, children, ...baseLoginRequest } = props;
766
+ const [email, setEmail] = React.useState();
767
+ if (!email) {
768
+ return React.createElement(EmailForm, { setEmail: setEmail, ...props });
769
+ }
770
+ else {
771
+ return React.createElement(PasswordForm, { email: email, ...props });
772
+ }
773
+ }
774
+ function EmailForm(props) {
775
+ const { setEmail, onRegister, handleAuthResponse, children, ...baseLoginRequest } = props;
761
776
  const medplum = useMedplum();
762
777
  const googleClientId = getGoogleClientId(props.googleClientId);
763
- const [outcome, setOutcome] = React.useState();
764
- const issues = getIssuesForExpression(outcome, undefined);
765
- async function startPkce() {
766
- if (generatePkce) {
767
- await medplum.startPkce();
778
+ const isExternalAuth = React.useCallback(async (authMethod) => {
779
+ if (!authMethod.authorizeUrl) {
780
+ return false;
768
781
  }
769
- }
770
- return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
771
- startPkce()
772
- .then(() => medplum.startLogin({
773
- ...baseLoginRequest,
774
- email: formData.email,
775
- password: formData.password,
776
- remember: formData.remember === 'on',
777
- }))
778
- .then(handleAuthResponse)
779
- .catch(setOutcome);
780
- } },
782
+ const state = JSON.stringify({
783
+ ...(await medplum.ensureCodeChallenge(baseLoginRequest)),
784
+ domain: authMethod.domain,
785
+ });
786
+ const url = new URL(authMethod.authorizeUrl);
787
+ url.searchParams.set('state', state);
788
+ window.location.assign(url.toString());
789
+ return true;
790
+ }, [medplum, baseLoginRequest]);
791
+ const handleSubmit = React.useCallback(async (formData) => {
792
+ const authMethod = await medplum.post('auth/method', { email: formData.email });
793
+ if (!(await isExternalAuth(authMethod))) {
794
+ setEmail(formData.email);
795
+ }
796
+ }, [medplum, isExternalAuth, setEmail]);
797
+ const handleGoogleCredential = React.useCallback(async (response) => {
798
+ const authResponse = await medplum.startGoogleLogin({
799
+ ...baseLoginRequest,
800
+ googleCredential: response.credential,
801
+ });
802
+ if (!(await isExternalAuth(authResponse))) {
803
+ handleAuthResponse(authResponse);
804
+ }
805
+ }, [medplum, baseLoginRequest, isExternalAuth, handleAuthResponse]);
806
+ return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
781
807
  React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
782
- issues && (React.createElement(core$1.Alert, { icon: React.createElement(icons.IconAlertCircle, { size: 16 }), color: "red" }, issues.map((issue) => (React.createElement("div", { "data-testid": "text-field-error", key: issue.details?.text }, issue.details?.text))))),
783
808
  googleClientId && (React.createElement(React.Fragment, null,
784
809
  React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
785
- React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => {
786
- startPkce()
787
- .then(() => medplum.startGoogleLogin({
788
- ...baseLoginRequest,
789
- googleCredential: response.credential,
790
- }))
791
- .then(props.handleAuthResponse)
792
- .catch(setOutcome);
793
- } })),
810
+ React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: handleGoogleCredential })),
794
811
  React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
812
+ React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true }),
813
+ React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
814
+ onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register")),
815
+ React.createElement(core$1.Button, { type: "submit" }, "Next"))));
816
+ }
817
+ function PasswordForm(props) {
818
+ const { onForgotPassword, handleAuthResponse, children, ...baseLoginRequest } = props;
819
+ const medplum = useMedplum();
820
+ const [outcome, setOutcome] = React.useState();
821
+ const issues = getIssuesForExpression(outcome, undefined);
822
+ const handleSubmit = React.useCallback((formData) => {
823
+ medplum
824
+ .startLogin({
825
+ ...baseLoginRequest,
826
+ password: formData.password,
827
+ remember: formData.remember === 'on',
828
+ })
829
+ .then(handleAuthResponse)
830
+ .catch(setOutcome);
831
+ }, [medplum, baseLoginRequest, handleAuthResponse]);
832
+ return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
833
+ React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
834
+ React.createElement(OperationOutcomeAlert, { issues: issues }),
795
835
  React.createElement(core$1.Stack, { spacing: "xl" },
796
- React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'email') }),
797
836
  React.createElement(core$1.PasswordInput, { name: "password", type: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') })),
798
837
  React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
799
838
  onForgotPassword && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onForgotPassword, size: "xs" }, "Forgot password")),
800
- onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register")),
801
839
  React.createElement(core$1.Checkbox, { id: "remember", name: "remember", label: "Remember me", size: "xs", sx: { lineHeight: 1 } }),
802
840
  React.createElement(core$1.Button, { type: "submit" }, "Sign in"))));
803
841
  }
@@ -884,7 +922,22 @@
884
922
  const [login, setLogin] = React.useState(undefined);
885
923
  const [mfaRequired, setAuthenticatorRequired] = React.useState(false);
886
924
  const [memberships, setMemberships] = React.useState(undefined);
887
- function handleAuthResponse(response) {
925
+ const handleCode = React.useCallback((code) => {
926
+ if (onCode) {
927
+ onCode(code);
928
+ }
929
+ else {
930
+ medplum
931
+ .processCode(code)
932
+ .then(() => {
933
+ if (onSuccess) {
934
+ onSuccess();
935
+ }
936
+ })
937
+ .catch(console.log);
938
+ }
939
+ }, [medplum, onCode, onSuccess]);
940
+ const handleAuthResponse = React.useCallback((response) => {
888
941
  setAuthenticatorRequired(!!response.mfaRequired);
889
942
  if (response.login) {
890
943
  setLogin(response.login);
@@ -900,28 +953,21 @@
900
953
  handleCode(response.code);
901
954
  }
902
955
  }
903
- }
904
- function handleScopeResponse(response) {
956
+ }, [chooseScopes, handleCode]);
957
+ const handleScopeResponse = React.useCallback((response) => {
905
958
  handleCode(response.code);
906
- }
907
- function handleCode(code) {
908
- if (onCode) {
909
- onCode(code);
910
- }
911
- else {
959
+ }, [handleCode]);
960
+ React.useEffect(() => {
961
+ if (props.login) {
912
962
  medplum
913
- .processCode(code)
914
- .then(() => {
915
- if (onSuccess) {
916
- onSuccess();
917
- }
918
- })
919
- .catch(console.log);
963
+ .get('auth/login/' + props.login)
964
+ .then(handleAuthResponse)
965
+ .catch(console.error);
920
966
  }
921
- }
967
+ }, [medplum, props, handleAuthResponse]);
922
968
  return (React.createElement(Document, { width: 450 }, (() => {
923
969
  if (!login) {
924
- return (React.createElement(AuthenticationForm, { generatePkce: !onCode, onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, ...baseLoginRequest }, props.children));
970
+ return (React.createElement(AuthenticationForm, { onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, ...baseLoginRequest }, props.children));
925
971
  }
926
972
  else if (mfaRequired) {
927
973
  return React.createElement(MfaForm, { login: login, handleAuthResponse: handleAuthResponse });
@@ -1198,7 +1244,7 @@
1198
1244
  if (!property?.path) {
1199
1245
  throw Error(`Displaying property of type ${props.propertyType} requires element definition path`);
1200
1246
  }
1201
- return (React.createElement(BackboneElementDisplay, { value: { type: core.buildTypeName(property?.path?.split('.')), value }, compact: true, ignoreMissingValues: props.ignoreMissingValues }));
1247
+ return (React.createElement(BackboneElementDisplay, { value: { type: core.getElementDefinitionTypeName(property), value }, compact: true, ignoreMissingValues: props.ignoreMissingValues }));
1202
1248
  }
1203
1249
  }
1204
1250
  /**
@@ -2195,7 +2241,7 @@
2195
2241
  case core.PropertyType.UsageContext:
2196
2242
  return (React.createElement(BackboneElementInput, { typeName: propertyType, defaultValue: value, onChange: props.onChange, outcome: props.outcome }));
2197
2243
  default:
2198
- return (React.createElement(BackboneElementInput, { typeName: core.buildTypeName(property.path?.split('.')), defaultValue: value, onChange: props.onChange, outcome: props.outcome }));
2244
+ return (React.createElement(BackboneElementInput, { typeName: core.getElementDefinitionTypeName(property), defaultValue: value, onChange: props.onChange, outcome: props.outcome }));
2199
2245
  }
2200
2246
  }
2201
2247
  function getTargetTypes(property) {
@@ -2753,7 +2799,7 @@
2753
2799
  const resource = useResource(props.value);
2754
2800
  const [history, setHistory] = React.useState();
2755
2801
  const [items, setItems] = React.useState([]);
2756
- const buildSearchRequests = props.buildSearchRequests;
2802
+ const loadTimelineResources = props.loadTimelineResources;
2757
2803
  const itemsRef = React.useRef(items);
2758
2804
  itemsRef.current = items;
2759
2805
  const loadTimeline = React.useCallback(() => {
@@ -2762,8 +2808,8 @@
2762
2808
  setHistory({});
2763
2809
  return;
2764
2810
  }
2765
- medplum.executeBatch(buildSearchRequests(resource)).then(handleBatchResponse).catch(console.log);
2766
- }, [medplum, resource, buildSearchRequests]);
2811
+ loadTimelineResources(medplum, resource).then(handleBatchResponse).catch(console.log);
2812
+ }, [medplum, resource, loadTimelineResources]);
2767
2813
  React.useEffect(() => {
2768
2814
  loadTimeline();
2769
2815
  }, [loadTimeline]);
@@ -2771,29 +2817,24 @@
2771
2817
  * Handles a batch request response.
2772
2818
  * @param batchResponse The batch response.
2773
2819
  */
2774
- function handleBatchResponse(batchResponse) {
2820
+ function handleBatchResponse(bundles) {
2775
2821
  const newItems = [];
2776
- if (batchResponse.entry) {
2777
- for (const batchEntry of batchResponse.entry) {
2778
- const bundle = batchEntry.resource;
2779
- if (!bundle) {
2780
- // User may not have access to all resource types
2781
- continue;
2782
- }
2783
- if (bundle.type === 'history') {
2784
- setHistory(bundle);
2785
- }
2786
- if (bundle.entry) {
2787
- for (const entry of bundle.entry) {
2788
- if (entry.resource) {
2789
- newItems.push(entry.resource);
2790
- }
2791
- }
2822
+ for (const bundle of bundles) {
2823
+ if (!bundle) {
2824
+ // User may not have access to all resource types
2825
+ continue;
2826
+ }
2827
+ if (bundle.type === 'history') {
2828
+ setHistory(bundle);
2829
+ }
2830
+ if (bundle.entry) {
2831
+ for (const entry of bundle.entry) {
2832
+ newItems.push(entry.resource);
2792
2833
  }
2793
2834
  }
2794
- sortByDateAndPriority(newItems);
2795
- newItems.reverse();
2796
2835
  }
2836
+ sortByDateAndPriority(newItems);
2837
+ newItems.reverse();
2797
2838
  setItems(newItems);
2798
2839
  }
2799
2840
  /**
@@ -2914,10 +2955,10 @@
2914
2955
  React.createElement(AttachmentButton, { onUpload: createMedia, onUploadStart: onUploadStart, onUploadProgress: onUploadProgress }, (props) => (React.createElement(core$1.ActionIcon, { ...props, radius: "xl", color: "blue", variant: "filled" },
2915
2956
  React.createElement(icons.IconCloudUpload, { size: 16 })))))))),
2916
2957
  items.map((item) => {
2958
+ const key = `${item.resourceType}/${item.id}/${item.meta?.versionId}`;
2917
2959
  if (item.resourceType === resource.resourceType && item.id === resource.id) {
2918
- return (React.createElement(HistoryTimelineItem, { key: item.meta?.versionId, history: history, resource: item, onDetails: onVersionDetails }));
2960
+ return (React.createElement(HistoryTimelineItem, { key: key, history: history, resource: item, onDetails: onVersionDetails }));
2919
2961
  }
2920
- const key = `${item.resourceType}/${item.id}`;
2921
2962
  switch (item.resourceType) {
2922
2963
  case 'AuditEvent':
2923
2964
  return React.createElement(AuditEventTimelineItem, { key: key, resource: item, onDetails: onDetails });
@@ -3006,51 +3047,22 @@
3006
3047
  }
3007
3048
 
3008
3049
  function DefaultResourceTimeline(props) {
3009
- return (React.createElement(ResourceTimeline, { value: props.resource, buildSearchRequests: (resource) => ({
3010
- resourceType: 'Bundle',
3011
- type: 'batch',
3012
- entry: [
3013
- {
3014
- request: {
3015
- method: 'GET',
3016
- url: `${core.getReferenceString(resource)}/_history`,
3017
- },
3018
- },
3019
- {
3020
- request: {
3021
- method: 'GET',
3022
- url: `AuditEvent?entity=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
3023
- },
3024
- },
3025
- ],
3026
- }) }));
3050
+ return (React.createElement(ResourceTimeline, { value: props.resource, loadTimelineResources: async (medplum, resource) => {
3051
+ return Promise.all([
3052
+ medplum.readHistory(resource.resourceType, resource.id),
3053
+ medplum.search('AuditEvent', '_sort=-_lastUpdated&entity=' + core.getReferenceString(resource)),
3054
+ ]);
3055
+ } }));
3027
3056
  }
3028
3057
 
3029
3058
  function EncounterTimeline(props) {
3030
- return (React.createElement(ResourceTimeline, { value: props.encounter, buildSearchRequests: (resource) => ({
3031
- resourceType: 'Bundle',
3032
- type: 'batch',
3033
- entry: [
3034
- {
3035
- request: {
3036
- method: 'GET',
3037
- url: `${core.getReferenceString(resource)}/_history`,
3038
- },
3039
- },
3040
- {
3041
- request: {
3042
- method: 'GET',
3043
- url: `Communication?encounter=${core.getReferenceString(resource)}`,
3044
- },
3045
- },
3046
- {
3047
- request: {
3048
- method: 'GET',
3049
- url: `Media?encounter=${core.getReferenceString(resource)}`,
3050
- },
3051
- },
3052
- ],
3053
- }), createCommunication: (resource, sender, text) => ({
3059
+ return (React.createElement(ResourceTimeline, { value: props.encounter, loadTimelineResources: async (medplum, resource) => {
3060
+ return Promise.all([
3061
+ medplum.readHistory('Encounter', resource.id),
3062
+ medplum.search('Communication', 'encounter=' + core.getReferenceString(resource)),
3063
+ medplum.search('Media', 'encounter=' + core.getReferenceString(resource)),
3064
+ ]);
3065
+ }, createCommunication: (resource, sender, text) => ({
3054
3066
  resourceType: 'Communication',
3055
3067
  status: 'completed',
3056
3068
  encounter: core.createReference(resource),
@@ -4523,26 +4535,19 @@
4523
4535
  }
4524
4536
  const MemoizedFhirPathTable = React.memo(FhirPathTable);
4525
4537
 
4526
- const searches = [
4527
- '$/_history',
4528
- 'Communication?subject=$',
4529
- 'Device?patient=$',
4530
- 'DeviceRequest?patient=$',
4531
- 'DiagnosticReport?subject=$',
4532
- 'Media?subject=$',
4533
- 'ServiceRequest?subject=$',
4534
- ];
4535
4538
  function PatientTimeline(props) {
4536
- return (React.createElement(ResourceTimeline, { value: props.patient, buildSearchRequests: (resource) => ({
4537
- resourceType: 'Bundle',
4538
- type: 'batch',
4539
- entry: searches.map((search) => ({
4540
- request: {
4541
- method: 'GET',
4542
- url: search.replaceAll('$', core.getReferenceString(resource)),
4543
- },
4544
- })),
4545
- }), createCommunication: (resource, sender, text) => ({
4539
+ const loadTimelineResources = React.useCallback((medplum, resource) => {
4540
+ return Promise.all([
4541
+ medplum.readHistory('Patient', resource.id),
4542
+ medplum.search('Communication', 'subject=' + core.getReferenceString(resource)),
4543
+ medplum.search('Device', 'patient=' + core.getReferenceString(resource)),
4544
+ medplum.search('DeviceRequest', 'patient=' + core.getReferenceString(resource)),
4545
+ medplum.search('DiagnosticReport', 'subject=' + core.getReferenceString(resource)),
4546
+ medplum.search('Media', 'subject=' + core.getReferenceString(resource)),
4547
+ medplum.search('ServiceRequest', 'subject=' + core.getReferenceString(resource)),
4548
+ ]);
4549
+ }, []);
4550
+ return (React.createElement(ResourceTimeline, { value: props.patient, loadTimelineResources: loadTimelineResources, createCommunication: (resource, sender, text) => ({
4546
4551
  resourceType: 'Communication',
4547
4552
  status: 'completed',
4548
4553
  subject: core.createReference(resource),
@@ -6129,36 +6134,14 @@
6129
6134
  }
6130
6135
 
6131
6136
  function ServiceRequestTimeline(props) {
6132
- return (React.createElement(ResourceTimeline, { value: props.serviceRequest, buildSearchRequests: (resource) => ({
6133
- resourceType: 'Bundle',
6134
- type: 'batch',
6135
- entry: [
6136
- {
6137
- request: {
6138
- method: 'GET',
6139
- url: `${core.getReferenceString(resource)}/_history`,
6140
- },
6141
- },
6142
- {
6143
- request: {
6144
- method: 'GET',
6145
- url: `Communication?based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
6146
- },
6147
- },
6148
- {
6149
- request: {
6150
- method: 'GET',
6151
- url: `Media?_count=100&based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
6152
- },
6153
- },
6154
- {
6155
- request: {
6156
- method: 'GET',
6157
- url: `DiagnosticReport?based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
6158
- },
6159
- },
6160
- ],
6161
- }), createCommunication: (resource, sender, text) => ({
6137
+ return (React.createElement(ResourceTimeline, { value: props.serviceRequest, loadTimelineResources: async (medplum, resource) => {
6138
+ return Promise.all([
6139
+ medplum.readHistory('ServiceRequest', resource.id),
6140
+ medplum.search('Communication', 'based-on=' + core.getReferenceString(resource)),
6141
+ medplum.search('Media', '_count=100&based-on=' + core.getReferenceString(resource)),
6142
+ medplum.search('DiagnosticReport', 'based-on=' + core.getReferenceString(resource)),
6143
+ ]);
6144
+ }, createCommunication: (resource, sender, text) => ({
6162
6145
  resourceType: 'Communication',
6163
6146
  status: 'completed',
6164
6147
  basedOn: [core.createReference(resource)],