@medplum/react 0.9.31 → 0.9.34

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,14 +1,17 @@
1
1
  /// <reference types="react" />
2
- import { Questionnaire, QuestionnaireItem, QuestionnaireResponse, QuestionnaireResponseItem, Reference } from '@medplum/fhirtypes';
2
+ import { Questionnaire, QuestionnaireItem, QuestionnaireResponse, QuestionnaireResponseItem, QuestionnaireResponseItemAnswer, Reference } from '@medplum/fhirtypes';
3
3
  import './QuestionnaireForm.css';
4
4
  export interface QuestionnaireFormProps {
5
5
  questionnaire: Questionnaire | Reference<Questionnaire>;
6
6
  subject?: Reference;
7
+ submitButtonText?: string;
7
8
  onSubmit: (response: QuestionnaireResponse) => void;
8
9
  }
9
10
  export declare function QuestionnaireForm(props: QuestionnaireFormProps): JSX.Element | null;
10
11
  export interface QuestionnaireFormItemProps {
11
12
  item: QuestionnaireItem;
13
+ answers: Record<string, QuestionnaireResponseItemAnswer>;
12
14
  onChange: (newResponseItem: QuestionnaireResponseItem) => void;
13
15
  }
14
16
  export declare function QuestionnaireFormItem(props: QuestionnaireFormItemProps): JSX.Element | null;
17
+ export declare function isQuestionEnabled(item: QuestionnaireItem, answers: Record<string, QuestionnaireResponseItemAnswer>): boolean;
@@ -1,7 +1,8 @@
1
1
  /// <reference types="react" />
2
- import { Reference, Schedule } from '@medplum/fhirtypes';
2
+ import { Questionnaire, Reference, Schedule } from '@medplum/fhirtypes';
3
3
  import './Scheduler.css';
4
4
  export interface SchedulerProps {
5
5
  schedule: Schedule | Reference<Schedule>;
6
+ questionnaire: Questionnaire | Reference<Questionnaire>;
6
7
  }
7
8
  export declare function Scheduler(props: SchedulerProps): JSX.Element | null;
@@ -6,6 +6,8 @@ export interface AuthenticationFormProps {
6
6
  readonly scope?: string;
7
7
  readonly nonce?: string;
8
8
  readonly googleClientId?: string;
9
+ readonly codeChallenge?: string;
10
+ readonly codeChallengeMethod?: string;
9
11
  readonly onForgotPassword?: () => void;
10
12
  readonly onRegister?: () => void;
11
13
  readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
@@ -8,6 +8,8 @@ export interface SignInFormProps {
8
8
  readonly clientId?: string;
9
9
  readonly scope?: string;
10
10
  readonly nonce?: string;
11
+ readonly codeChallenge?: string;
12
+ readonly codeChallengeMethod?: string;
11
13
  readonly onSuccess?: () => void;
12
14
  readonly onForgotPassword?: () => void;
13
15
  readonly onRegister?: () => void;
package/dist/cjs/index.js CHANGED
@@ -654,7 +654,7 @@
654
654
  }
655
655
  return (React__default["default"].createElement(Document, { width: 450 },
656
656
  outcome && React__default["default"].createElement("pre", null, JSON.stringify(outcome, null, 2)),
657
- !login && (React__default["default"].createElement(NewUserForm, { projectId: projectId, googleClientId: googleClientId, recaptchaSiteKey: recaptchaSiteKey, handleAuthResponse: handleAuthResponse })),
657
+ !login && (React__default["default"].createElement(NewUserForm, { projectId: projectId, googleClientId: googleClientId, recaptchaSiteKey: recaptchaSiteKey, handleAuthResponse: handleAuthResponse }, props.children)),
658
658
  login && type === 'project' && React__default["default"].createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse })));
659
659
  }
660
660
 
@@ -713,6 +713,8 @@
713
713
  clientId: props.clientId,
714
714
  scope: props.scope,
715
715
  nonce: props.nonce,
716
+ codeChallenge: props.codeChallenge,
717
+ codeChallengeMethod: props.codeChallengeMethod,
716
718
  email: formData.email,
717
719
  password: formData.password,
718
720
  remember: formData.remember === 'true',
@@ -734,6 +736,8 @@
734
736
  clientId: props.clientId,
735
737
  scope: props.scope,
736
738
  nonce: props.nonce,
739
+ codeChallenge: props.codeChallenge,
740
+ codeChallengeMethod: props.codeChallengeMethod,
737
741
  googleClientId: response.clientId,
738
742
  googleCredential: response.credential,
739
743
  })
@@ -887,7 +891,7 @@
887
891
  }
888
892
  return (React__default["default"].createElement(Document, { width: 450 }, (() => {
889
893
  if (!login) {
890
- return (React__default["default"].createElement(AuthenticationForm, { projectId: props.projectId, clientId: props.clientId, scope: props.scope, nonce: props.nonce, googleClientId: props.googleClientId, onForgotPassword: props.onForgotPassword, onRegister: props.onRegister, handleAuthResponse: handleAuthResponse }, props.children));
894
+ return (React__default["default"].createElement(AuthenticationForm, { projectId: props.projectId, clientId: props.clientId, scope: props.scope, nonce: props.nonce, googleClientId: props.googleClientId, codeChallenge: props.codeChallenge, codeChallengeMethod: props.codeChallengeMethod, onForgotPassword: props.onForgotPassword, onRegister: props.onRegister, handleAuthResponse: handleAuthResponse }, props.children));
891
895
  }
892
896
  else if (memberships) {
893
897
  return React__default["default"].createElement(ChooseProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
@@ -4221,9 +4225,9 @@
4221
4225
  props.onBulk && (React__default["default"].createElement(Button, { size: "small", onClick: () => props.onBulk(Object.keys(state.selected)) }, "Bulk..."))),
4222
4226
  lastResult && (React__default["default"].createElement("div", null,
4223
4227
  React__default["default"].createElement("span", { className: "medplum-search-summary" },
4224
- getStart(search, lastResult.total),
4228
+ getStart$1(search, lastResult.total),
4225
4229
  "-",
4226
- getEnd(search, lastResult.total),
4230
+ getEnd$1(search, lastResult.total),
4227
4231
  " of",
4228
4232
  ' ', (_d = lastResult.total) === null || _d === void 0 ? void 0 :
4229
4233
  _d.toLocaleString()),
@@ -4290,11 +4294,11 @@
4290
4294
  return (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "rgba(0, 0, 0, 0.3)", strokeWidth: 2 },
4291
4295
  React__default["default"].createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 6h16M4 12h16m-7 6h7" })));
4292
4296
  }
4293
- function getStart(search, total) {
4297
+ function getStart$1(search, total) {
4294
4298
  var _a;
4295
4299
  return Math.min(total, ((_a = search.offset) !== null && _a !== void 0 ? _a : 0) + 1);
4296
4300
  }
4297
- function getEnd(search, total) {
4301
+ function getEnd$1(search, total) {
4298
4302
  var _a, _b;
4299
4303
  return Math.min(total, (((_a = search.offset) !== null && _a !== void 0 ? _a : 0) + 1) * ((_b = search.count) !== null && _b !== void 0 ? _b : core.DEFAULT_SEARCH_COUNT));
4300
4304
  }
@@ -5015,17 +5019,24 @@
5015
5019
  const [schema, setSchema] = React.useState();
5016
5020
  const questionnaire = useResource(props.questionnaire);
5017
5021
  const [response, setResponse] = React.useState();
5022
+ const [answers, setAnswers] = React.useState({});
5018
5023
  React.useEffect(() => {
5019
- medplum.requestSchema('Questionnaire').then(setSchema).catch(console.log);
5024
+ medplum
5025
+ .requestSchema('Questionnaire')
5026
+ .then(() => medplum.requestSchema('QuestionnaireResponse'))
5027
+ .then(setSchema)
5028
+ .catch(console.log);
5020
5029
  }, [medplum]);
5021
5030
  React.useEffect(() => {
5022
5031
  setResponse(questionnaire ? buildInitialResponse(questionnaire) : undefined);
5023
5032
  }, [questionnaire]);
5024
5033
  function setItems(newResponseItems) {
5025
- setResponse({
5034
+ const newResponse = {
5026
5035
  resourceType: 'QuestionnaireResponse',
5027
5036
  item: newResponseItems,
5028
- });
5037
+ };
5038
+ setResponse(newResponse);
5039
+ setAnswers(core.getQuestionnaireAnswers(newResponse));
5029
5040
  }
5030
5041
  if (!schema || !questionnaire) {
5031
5042
  return null;
@@ -5036,8 +5047,8 @@
5036
5047
  }
5037
5048
  } },
5038
5049
  questionnaire.title && React__default["default"].createElement("h1", null, questionnaire.title),
5039
- questionnaire.item && React__default["default"].createElement(QuestionnaireFormItemArray, { items: questionnaire.item, onChange: setItems }),
5040
- React__default["default"].createElement(Button, { type: "submit", size: "large" }, "OK")));
5050
+ questionnaire.item && (React__default["default"].createElement(QuestionnaireFormItemArray, { items: questionnaire.item, answers: answers, onChange: setItems })),
5051
+ React__default["default"].createElement(Button, { type: "submit", size: "large" }, props.submitButtonText || 'OK')));
5041
5052
  }
5042
5053
  function QuestionnaireFormItemArray(props) {
5043
5054
  const [responseItems, setResponseItems] = React.useState(buildInitialResponseItems(props.items));
@@ -5048,11 +5059,14 @@
5048
5059
  props.onChange(newResponseItems);
5049
5060
  }
5050
5061
  return (React__default["default"].createElement(React__default["default"].Fragment, null, props.items.map((item, index) => {
5062
+ if (!isQuestionEnabled(item, props.answers)) {
5063
+ return null;
5064
+ }
5051
5065
  if (item.type === exports.QuestionnaireItemType.display) {
5052
5066
  return React__default["default"].createElement("p", { key: item.linkId }, item.text);
5053
5067
  }
5054
5068
  if (item.type === exports.QuestionnaireItemType.group) {
5055
- return (React__default["default"].createElement(QuestionnaireFormItem, { key: item.linkId, item: item, onChange: (newResponseItem) => setResponseItem(index, newResponseItem) }));
5069
+ return (React__default["default"].createElement(QuestionnaireFormItem, { key: item.linkId, item: item, answers: props.answers, onChange: (newResponseItem) => setResponseItem(index, newResponseItem) }));
5056
5070
  }
5057
5071
  if (item.type === exports.QuestionnaireItemType.boolean) {
5058
5072
  const initial = item.initial && item.initial.length > 0 ? item.initial[0] : undefined;
@@ -5064,7 +5078,7 @@
5064
5078
  }) })));
5065
5079
  }
5066
5080
  return (React__default["default"].createElement(FormSection, { key: item.linkId, htmlFor: item.linkId, title: item.text || '' },
5067
- React__default["default"].createElement(QuestionnaireFormItem, { item: item, onChange: (newResponseItem) => setResponseItem(index, newResponseItem) })));
5081
+ React__default["default"].createElement(QuestionnaireFormItem, { item: item, answers: props.answers, onChange: (newResponseItem) => setResponseItem(index, newResponseItem) })));
5068
5082
  })));
5069
5083
  }
5070
5084
  function QuestionnaireFormItem(props) {
@@ -5096,7 +5110,7 @@
5096
5110
  case exports.QuestionnaireItemType.group:
5097
5111
  return (React__default["default"].createElement("div", null,
5098
5112
  React__default["default"].createElement("h3", null, item.text),
5099
- item.item && React__default["default"].createElement(QuestionnaireFormItemArray, { items: item.item, onChange: onChangeItem })));
5113
+ item.item && (React__default["default"].createElement(QuestionnaireFormItemArray, { items: item.item, answers: props.answers, onChange: onChangeItem }))));
5100
5114
  case exports.QuestionnaireItemType.boolean:
5101
5115
  return (React__default["default"].createElement(Checkbox, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueBoolean, onChange: (newValue) => onChangeAnswer({ valueBoolean: newValue }) }));
5102
5116
  case exports.QuestionnaireItemType.decimal:
@@ -5207,6 +5221,35 @@
5207
5221
  ((_c = (_b = (_a = e.valueCodeableConcept) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.code) === 'drop-down';
5208
5222
  }));
5209
5223
  }
5224
+ function isQuestionEnabled(item, answers) {
5225
+ if (!item.enableWhen) {
5226
+ return true;
5227
+ }
5228
+ const enableBehavior = item.enableBehavior || 'any';
5229
+ for (const enableWhen of item.enableWhen) {
5230
+ const expectedAnswer = core.getTypedPropertyValue({
5231
+ type: 'QuestionnaireItemEnableWhen',
5232
+ value: enableWhen,
5233
+ }, 'answer[x]');
5234
+ const actualAnswer = core.getTypedPropertyValue({
5235
+ type: 'QuestionnaireResponseItemAnswer',
5236
+ value: answers[enableWhen.question],
5237
+ }, 'value[x]');
5238
+ const match = core.deepEquals(expectedAnswer, actualAnswer);
5239
+ if (enableBehavior === 'any' && match) {
5240
+ return true;
5241
+ }
5242
+ if (enableBehavior === 'all' && !match) {
5243
+ return false;
5244
+ }
5245
+ }
5246
+ if (enableBehavior === 'any') {
5247
+ return false;
5248
+ }
5249
+ else {
5250
+ return true;
5251
+ }
5252
+ }
5210
5253
 
5211
5254
  function QuestionnaireBuilder(props) {
5212
5255
  const medplum = useMedplum();
@@ -5303,7 +5346,7 @@
5303
5346
  isChoiceQuestion(item) && (React__default["default"].createElement(AnswerBuilder, { options: item.answerOption, onChange: (newOptions) => changeProperty('answerOption', newOptions) })))) : (React__default["default"].createElement(React__default["default"].Fragment, null,
5304
5347
  resource.title && React__default["default"].createElement("h1", null, resource.title),
5305
5348
  item.text && React__default["default"].createElement("p", null, item.text),
5306
- !isContainer && React__default["default"].createElement(QuestionnaireFormItem, { item: item, onChange: () => undefined }))),
5349
+ !isContainer && React__default["default"].createElement(QuestionnaireFormItem, { item: item, answers: {}, onChange: () => undefined }))),
5307
5350
  item.item &&
5308
5351
  item.item.map((i) => (React__default["default"].createElement("div", { key: i.id },
5309
5352
  React__default["default"].createElement(ItemBuilder, { item: i, selectedKey: props.selectedKey, setSelectedKey: props.setSelectedKey, hoverKey: props.hoverKey, setHoverKey: props.setHoverKey, onChange: changeItem, onRemove: () => removeItem(i) })))),
@@ -5876,22 +5919,23 @@
5876
5919
  var _a;
5877
5920
  const medplum = useMedplum();
5878
5921
  const schedule = useResource(props.schedule);
5922
+ const questionnaire = useResource(props.questionnaire);
5879
5923
  const [slots, setSlots] = React.useState();
5880
5924
  const slotsRef = React.useRef();
5881
5925
  slotsRef.current = slots;
5882
5926
  const [month, setMonth] = React.useState(getStartMonth());
5883
5927
  const [date, setDate] = React.useState();
5884
5928
  const [slot, setSlot] = React.useState();
5885
- const [info, setInfo] = React.useState();
5886
- const [form, setForm] = React.useState();
5929
+ const [response, setResponse] = React.useState();
5887
5930
  React.useEffect(() => {
5888
5931
  if (schedule) {
5889
5932
  setSlots([]);
5890
5933
  medplum
5891
5934
  .searchResources('Slot', new URLSearchParams([
5935
+ ['_count', (30 * 24).toString()],
5892
5936
  ['schedule', core.getReferenceString(schedule)],
5893
- ['start', 'gt' + month.toISOString()],
5894
- ['start', 'lt' + new Date(month.getTime() + 31 * 24 * 60 * 60 * 1000).toISOString()],
5937
+ ['start', 'gt' + getStart(month)],
5938
+ ['start', 'lt' + getEnd(month)],
5895
5939
  ]))
5896
5940
  .then(setSlots)
5897
5941
  .catch(console.log);
@@ -5900,7 +5944,7 @@
5900
5944
  setSlots(undefined);
5901
5945
  }
5902
5946
  }, [medplum, schedule, month]);
5903
- if (!schedule || !slots) {
5947
+ if (!schedule || !slots || !questionnaire) {
5904
5948
  return null;
5905
5949
  }
5906
5950
  const actor = (_a = schedule.actor) === null || _a === void 0 ? void 0 : _a[0];
@@ -5924,26 +5968,20 @@
5924
5968
  slotStart.getTime() < date.getTime() + 24 * 3600 * 1000 && (React__default["default"].createElement("div", { key: s.id },
5925
5969
  React__default["default"].createElement(Button, { style: { width: 150 }, onClick: () => setSlot(s) }, formatTime(slotStart)))));
5926
5970
  }))),
5927
- date && slot && !info && (React__default["default"].createElement("div", null,
5928
- React__default["default"].createElement("h3", null, "Enter your info"),
5929
- React__default["default"].createElement(FormSection, { title: "Name", htmlFor: "name" },
5930
- React__default["default"].createElement(Input, { name: "name" })),
5931
- React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email" },
5932
- React__default["default"].createElement(Input, { name: "email" })),
5933
- React__default["default"].createElement(Button, { primary: true, onClick: () => setInfo('info') }, "Next"))),
5934
- date && slot && info && !form && (React__default["default"].createElement("div", null,
5935
- React__default["default"].createElement("h3", null, "Custom questions"),
5936
- React__default["default"].createElement(FormSection, { title: "Question 1", htmlFor: "q1" },
5937
- React__default["default"].createElement(Input, { name: "q1" })),
5938
- React__default["default"].createElement(FormSection, { title: "Question 2", htmlFor: "q2" },
5939
- React__default["default"].createElement(Input, { name: "email" })),
5940
- React__default["default"].createElement(FormSection, { title: "Question 3", htmlFor: "q3" },
5941
- React__default["default"].createElement(Input, { name: "email" })),
5942
- React__default["default"].createElement(Button, { primary: true, onClick: () => setForm('form') }, "Next"))),
5943
- date && slot && info && form && (React__default["default"].createElement("div", null,
5971
+ date && slot && !response && (React__default["default"].createElement(QuestionnaireForm, { questionnaire: questionnaire, submitButtonText: 'Next', onSubmit: setResponse })),
5972
+ date && slot && response && (React__default["default"].createElement("div", null,
5944
5973
  React__default["default"].createElement("h3", null, "You're all set!"),
5945
5974
  React__default["default"].createElement("p", null, "Check your email for a calendar invite."))))));
5946
5975
  }
5976
+ function getStart(month) {
5977
+ return formatSlotInstant(month.getTime());
5978
+ }
5979
+ function getEnd(month) {
5980
+ return formatSlotInstant(month.getTime() + 31 * 24 * 60 * 60 * 1000);
5981
+ }
5982
+ function formatSlotInstant(time) {
5983
+ return new Date(Math.max(Date.now(), time)).toISOString();
5984
+ }
5947
5985
  function formatTime(date) {
5948
5986
  return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
5949
5987
  }
@@ -5962,19 +6000,19 @@
5962
6000
  {
5963
6001
  request: {
5964
6002
  method: 'GET',
5965
- url: `Communication?based-on=${core.getReferenceString(resource)}`,
6003
+ url: `Communication?based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
5966
6004
  },
5967
6005
  },
5968
6006
  {
5969
6007
  request: {
5970
6008
  method: 'GET',
5971
- url: `Media?_count=100&based-on=${core.getReferenceString(resource)}`,
6009
+ url: `Media?_count=100&based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
5972
6010
  },
5973
6011
  },
5974
6012
  {
5975
6013
  request: {
5976
6014
  method: 'GET',
5977
- url: `DiagnosticReport?based-on=${core.getReferenceString(resource)}`,
6015
+ url: `DiagnosticReport?based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
5978
6016
  },
5979
6017
  },
5980
6018
  ],
@@ -6163,6 +6201,7 @@
6163
6201
  exports.hasFilterOnField = hasFilterOnField;
6164
6202
  exports.initRecaptcha = initRecaptcha;
6165
6203
  exports.isChoiceQuestion = isChoiceQuestion;
6204
+ exports.isQuestionEnabled = isQuestionEnabled;
6166
6205
  exports.isSortDescending = isSortDescending;
6167
6206
  exports.movePage = movePage;
6168
6207
  exports.parseForm = parseForm;