@medplum/react 0.9.19 → 0.9.22

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.
@@ -1511,6 +1511,64 @@ div.medplum-nav-menu-container {
1511
1511
  margin: 0;
1512
1512
  }
1513
1513
 
1514
+ .medplum-calendar-table {
1515
+ width: 350px;
1516
+ }
1517
+
1518
+ .medplum-calendar-table th {
1519
+ font-weight: normal;
1520
+ font-size: 11px;
1521
+ padding: 8px;
1522
+ text-align: center;
1523
+ }
1524
+
1525
+ .medplum-calendar-table td {
1526
+ padding: 2px 4px;
1527
+ }
1528
+
1529
+ .medplum-calendar-table button {
1530
+ width: 44px;
1531
+ height: 44px;
1532
+ color: #0060e6;
1533
+ font-size: 16px;
1534
+ font-weight: bold;
1535
+ text-align: center;
1536
+ padding: 0;
1537
+ background-color: #eef5ff;
1538
+ border: 0;
1539
+ border-radius: 50%;
1540
+ cursor: pointer;
1541
+ }
1542
+
1543
+ .medplum-calendar-table button:hover {
1544
+ background-color: #d9e9ff;
1545
+ }
1546
+
1547
+ .medplum-calendar-table button:disabled {
1548
+ background-color: white;
1549
+ cursor: default;
1550
+ color: #666;
1551
+ font-weight: normal;
1552
+ }
1553
+
1554
+ .medplum-calendar-container {
1555
+ display: flex;
1556
+ min-height: 400px;
1557
+ }
1558
+
1559
+ .medplum-calendar-info-pane {
1560
+ min-width: 300px;
1561
+ padding: 20px;
1562
+ border-right: 1px solid #ddd;
1563
+ }
1564
+
1565
+ .medplum-calendar-selection-pane {
1566
+ min-width: 300px;
1567
+ padding: 20px;
1568
+ }
1569
+
1570
+ .grecaptcha-badge { visibility: hidden; }
1571
+
1514
1572
  .medplum-signin {
1515
1573
  max-width: 400px;
1516
1574
  }
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { formatAddress, getDisplayString, getImageSrc, capitalize, globalSchema, getPropertyDisplayName, formatHumanName, stringify, buildTypeName, PropertyType, getTypedPropertyValue, createReference, toTypedValue, getReferenceString, evalFhirPath, getSearchParameterDetails, Operator, evalFhirPathTyped, SearchParameterType, formatSearchQuery, parseSearchDefinition, DEFAULT_SEARCH_COUNT, isUUID } from '@medplum/core';
2
- import React, { useState, useRef, createContext, useEffect, useContext, useCallback } from 'react';
2
+ import React, { useState, useRef, createContext, useEffect, useContext, useCallback, useMemo } from 'react';
3
3
  import { useNavigate, useLocation } from 'react-router-dom';
4
4
 
5
5
  function AddressDisplay(props) {
@@ -19,7 +19,7 @@ function Input(props) {
19
19
  const className = 'medplum-input';
20
20
  const issues = getIssuesForExpression(props.outcome, props.name);
21
21
  const invalid = issues && issues.length > 0;
22
- return (React.createElement("input", { id: props.name, name: props.name, type: getInputType(props.type), size: props.size, step: props.step, className: className, defaultValue: props.defaultValue || '', required: props.required, autoCapitalize: props.autoCapitalize, autoComplete: props.autoComplete, autoFocus: props.autoFocus, ref: props.inputRef, "aria-invalid": invalid, "aria-describedby": invalid ? props.name + '-errors' : '', placeholder: props.placeholder, "data-testid": props.testid, disabled: props.disabled, onChange: (e) => {
22
+ return (React.createElement("input", { id: props.name, name: props.name, type: getInputType(props.type), size: props.size, step: props.step, className: className, style: props.style, defaultValue: props.defaultValue || '', required: props.required, autoCapitalize: props.autoCapitalize, autoComplete: props.autoComplete, autoFocus: props.autoFocus, ref: props.inputRef, "aria-invalid": invalid, "aria-describedby": invalid ? props.name + '-errors' : '', placeholder: props.placeholder, "data-testid": props.testid, disabled: props.disabled, onChange: (e) => {
23
23
  if (props.onChange) {
24
24
  props.onChange(e.currentTarget.value);
25
25
  }
@@ -870,6 +870,18 @@ function ContactPointDisplay(props) {
870
870
  return React.createElement(React.Fragment, null, builder.join('').trim());
871
871
  }
872
872
 
873
+ function ContactDetailDisplay(props) {
874
+ var _a;
875
+ const contactDetail = props.value;
876
+ if (!contactDetail) {
877
+ return null;
878
+ }
879
+ return (React.createElement(React.Fragment, null,
880
+ contactDetail.name,
881
+ contactDetail.name && ': ', (_a = contactDetail.telecom) === null || _a === void 0 ? void 0 :
882
+ _a.map((telecom, index) => (React.createElement(ContactPointDisplay, { key: 'telecom-' + index, value: telecom })))));
883
+ }
884
+
873
885
  function DateTimeDisplay(props) {
874
886
  if (!props.value) {
875
887
  return null;
@@ -1018,6 +1030,8 @@ function ResourcePropertyDisplay(props) {
1018
1030
  return React.createElement(CodeableConceptDisplay, { value: value });
1019
1031
  case PropertyType.Coding:
1020
1032
  return React.createElement(CodingDisplay, { value: value });
1033
+ case PropertyType.ContactDetail:
1034
+ return React.createElement(ContactDetailDisplay, { value: value });
1021
1035
  case PropertyType.ContactPoint:
1022
1036
  return React.createElement(ContactPointDisplay, { value: value });
1023
1037
  case PropertyType.HumanName:
@@ -1187,19 +1201,34 @@ function ContactPointInput(props) {
1187
1201
  const ref = useRef();
1188
1202
  ref.current = contactPoint;
1189
1203
  function setContactPointWrapper(newValue) {
1204
+ if (newValue && Object.keys(newValue).length === 0) {
1205
+ newValue = undefined;
1206
+ }
1190
1207
  setContactPoint(newValue);
1191
1208
  if (props.onChange) {
1192
1209
  props.onChange(newValue);
1193
1210
  }
1194
1211
  }
1195
1212
  function setSystem(system) {
1196
- setContactPointWrapper(Object.assign(Object.assign({}, ref.current), { system: system ? system : undefined }));
1213
+ const newValue = Object.assign(Object.assign({}, ref.current), { system });
1214
+ if (!system) {
1215
+ delete newValue.system;
1216
+ }
1217
+ setContactPointWrapper(newValue);
1197
1218
  }
1198
1219
  function setUse(use) {
1199
- setContactPointWrapper(Object.assign(Object.assign({}, ref.current), { use: use ? use : undefined }));
1220
+ const newValue = Object.assign(Object.assign({}, ref.current), { use });
1221
+ if (!use) {
1222
+ delete newValue.use;
1223
+ }
1224
+ setContactPointWrapper(newValue);
1200
1225
  }
1201
1226
  function setValue(value) {
1202
- setContactPointWrapper(Object.assign(Object.assign({}, ref.current), { value: value ? value : undefined }));
1227
+ const newValue = Object.assign(Object.assign({}, ref.current), { value });
1228
+ if (!value) {
1229
+ delete newValue.value;
1230
+ }
1231
+ setContactPointWrapper(newValue);
1203
1232
  }
1204
1233
  return (React.createElement(InputRow, null,
1205
1234
  React.createElement(Select, { defaultValue: contactPoint === null || contactPoint === void 0 ? void 0 : contactPoint.system, onChange: setSystem, testid: "system" },
@@ -1220,6 +1249,36 @@ function ContactPointInput(props) {
1220
1249
  React.createElement(Input, { placeholder: "Value", defaultValue: contactPoint === null || contactPoint === void 0 ? void 0 : contactPoint.value, onChange: setValue })));
1221
1250
  }
1222
1251
 
1252
+ function ContactDetailInput(props) {
1253
+ var _a;
1254
+ const [contactPoint, setContactDetail] = useState(props.defaultValue);
1255
+ const ref = useRef();
1256
+ ref.current = contactPoint;
1257
+ function setContactDetailWrapper(newValue) {
1258
+ setContactDetail(newValue);
1259
+ if (props.onChange) {
1260
+ props.onChange(newValue);
1261
+ }
1262
+ }
1263
+ function setName(name) {
1264
+ const newValue = Object.assign(Object.assign({}, ref.current), { name });
1265
+ if (!name) {
1266
+ delete newValue.name;
1267
+ }
1268
+ setContactDetailWrapper(newValue);
1269
+ }
1270
+ function setTelecom(telecom) {
1271
+ const newValue = Object.assign(Object.assign({}, ref.current), { telecom: telecom && [telecom] });
1272
+ if (!telecom) {
1273
+ delete newValue.telecom;
1274
+ }
1275
+ setContactDetailWrapper(newValue);
1276
+ }
1277
+ return (React.createElement(InputRow, null,
1278
+ React.createElement(Input, { name: props.name + '-name', placeholder: "Name", style: { width: 180 }, defaultValue: contactPoint === null || contactPoint === void 0 ? void 0 : contactPoint.name, onChange: setName }),
1279
+ React.createElement(ContactPointInput, { name: props.name + '-telecom', defaultValue: (_a = contactPoint === null || contactPoint === void 0 ? void 0 : contactPoint.telecom) === null || _a === void 0 ? void 0 : _a[0], onChange: setTelecom })));
1280
+ }
1281
+
1223
1282
  /**
1224
1283
  * The DateTimeInput component is a wrapper around the HTML5 input type="datetime-local".
1225
1284
  * The main purpose is to reconcile time zones.
@@ -1634,6 +1693,8 @@ function ElementDefinitionTypeInput(props) {
1634
1693
  return React.createElement(CodeableConceptInput, { property: property, name: name, defaultValue: value, onChange: props.onChange });
1635
1694
  case PropertyType.Coding:
1636
1695
  return React.createElement(CodingInput, { property: property, name: name, defaultValue: value, onChange: props.onChange });
1696
+ case PropertyType.ContactDetail:
1697
+ return React.createElement(ContactDetailInput, { name: name, defaultValue: value, onChange: props.onChange });
1637
1698
  case PropertyType.ContactPoint:
1638
1699
  return React.createElement(ContactPointInput, { name: name, defaultValue: value, onChange: props.onChange });
1639
1700
  case PropertyType.Extension:
@@ -2117,8 +2178,7 @@ function ResourceTimeline(props) {
2117
2178
  setHistory({});
2118
2179
  return;
2119
2180
  }
2120
- const batchRequest = buildSearchRequests(resource);
2121
- medplum.post('fhir/R4', batchRequest).then(handleBatchResponse);
2181
+ medplum.executeBatch(buildSearchRequests(resource)).then(handleBatchResponse);
2122
2182
  }, [medplum, resource, buildSearchRequests]);
2123
2183
  useEffect(() => {
2124
2184
  loadTimeline();
@@ -4546,31 +4606,64 @@ function QuestionnaireFormItem(props) {
4546
4606
  return (React.createElement(TextArea, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueString, onChange: (newValue) => onChangeAnswer({ valueString: newValue }) }));
4547
4607
  case QuestionnaireItemType.url:
4548
4608
  return (React.createElement(Input, { type: "url", name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueUri, onChange: (newValue) => onChangeAnswer({ valueUri: newValue }) }));
4549
- case QuestionnaireItemType.choice:
4550
- case QuestionnaireItemType.openChoice:
4551
- return (React.createElement("div", null, item.answerOption &&
4552
- item.answerOption.map((option, index) => {
4553
- const valueElementDefinition = globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
4554
- const optionValue = getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4555
- const initialValue = getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
4556
- const propertyName = 'value' + capitalize(optionValue.type);
4557
- const optionName = `${name}-option-${index}`;
4558
- return (React.createElement("div", { key: optionName, className: "medplum-questionnaire-option-row" },
4559
- React.createElement("div", { className: "medplum-questionnaire-option-checkbox" },
4560
- React.createElement("input", { type: "radio", id: optionName, name: name, value: optionValue.value, defaultChecked: initialValue && stringify(optionValue) === stringify(initialValue), onChange: () => onChangeAnswer({ [propertyName]: optionValue.value }) })),
4561
- React.createElement("div", null,
4562
- React.createElement("label", { htmlFor: optionName },
4563
- React.createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })))));
4564
- })));
4565
4609
  case QuestionnaireItemType.attachment:
4566
4610
  return (React.createElement(AttachmentInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueAttachment, onChange: (newValue) => onChangeAnswer({ valueAttachment: newValue }) }));
4567
4611
  case QuestionnaireItemType.reference:
4568
4612
  return (React.createElement(ReferenceInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueReference, onChange: (newValue) => onChangeAnswer({ valueReference: newValue }) }));
4569
4613
  case QuestionnaireItemType.quantity:
4570
4614
  return (React.createElement(QuantityInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueQuantity, onChange: (newValue) => onChangeAnswer({ valueQuantity: newValue }) }));
4615
+ case QuestionnaireItemType.choice:
4616
+ case QuestionnaireItemType.openChoice:
4617
+ if (isDropDownChoice(item)) {
4618
+ return (React.createElement(QuestionnaireChoiceDropDownInput, { name: name, item: item, initial: initial, onChangeAnswer: onChangeAnswer }));
4619
+ }
4620
+ else {
4621
+ return (React.createElement(QuestionnaireChoiceRadioInput, { name: name, item: item, initial: initial, onChangeAnswer: onChangeAnswer }));
4622
+ }
4571
4623
  }
4572
4624
  return null;
4573
4625
  }
4626
+ function QuestionnaireChoiceDropDownInput(props) {
4627
+ const { name, item, initial } = props;
4628
+ const valueElementDefinition = globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
4629
+ const initialValue = getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
4630
+ return (React.createElement("select", { id: name, name: name, className: "medplum-select", onChange: (e) => {
4631
+ const index = e.currentTarget.selectedIndex;
4632
+ if (index === 0) {
4633
+ props.onChangeAnswer({});
4634
+ return;
4635
+ }
4636
+ const option = item.answerOption[index - 1];
4637
+ const optionValue = getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4638
+ const propertyName = 'value' + capitalize(optionValue.type);
4639
+ props.onChangeAnswer({ [propertyName]: optionValue.value });
4640
+ } },
4641
+ React.createElement("option", null),
4642
+ item.answerOption &&
4643
+ item.answerOption.map((option, index) => {
4644
+ const optionValue = getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4645
+ const optionName = `${name}-option-${index}`;
4646
+ return (React.createElement("option", { key: optionName, value: optionValue.value, selected: initialValue && stringify(optionValue) === stringify(initialValue) },
4647
+ React.createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })));
4648
+ })));
4649
+ }
4650
+ function QuestionnaireChoiceRadioInput(props) {
4651
+ const { name, item, initial, onChangeAnswer } = props;
4652
+ const valueElementDefinition = globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
4653
+ const initialValue = getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
4654
+ return (React.createElement(React.Fragment, null, item.answerOption &&
4655
+ item.answerOption.map((option, index) => {
4656
+ const optionValue = getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4657
+ const propertyName = 'value' + capitalize(optionValue.type);
4658
+ const optionName = `${name}-option-${index}`;
4659
+ return (React.createElement("div", { key: optionName, className: "medplum-questionnaire-option-row" },
4660
+ React.createElement("div", { className: "medplum-questionnaire-option-checkbox" },
4661
+ React.createElement("input", { type: "radio", id: optionName, name: name, value: optionValue.value, defaultChecked: initialValue && stringify(optionValue) === stringify(initialValue), onChange: () => onChangeAnswer({ [propertyName]: optionValue.value }) })),
4662
+ React.createElement("div", null,
4663
+ React.createElement("label", { htmlFor: optionName },
4664
+ React.createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })))));
4665
+ })));
4666
+ }
4574
4667
  function buildInitialResponse(questionnaire) {
4575
4668
  const response = {
4576
4669
  resourceType: 'QuestionnaireResponse',
@@ -4596,6 +4689,14 @@ function buildInitialResponseAnswer(answer) {
4596
4689
  // have the same properties.
4597
4690
  return Object.assign({}, answer);
4598
4691
  }
4692
+ function isDropDownChoice(item) {
4693
+ var _a;
4694
+ return !!((_a = item.extension) === null || _a === void 0 ? void 0 : _a.some((e) => {
4695
+ var _a, _b, _c;
4696
+ return e.url === 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl' &&
4697
+ ((_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';
4698
+ }));
4699
+ }
4599
4700
 
4600
4701
  function QuestionnaireBuilder(props) {
4601
4702
  const medplum = useMedplum();
@@ -4809,7 +4910,7 @@ function RequestGroupDisplay(props) {
4809
4910
  const [responseBundle, setResponseBundle] = useState();
4810
4911
  useEffect(() => {
4811
4912
  if (requestGroup && !startedLoading) {
4812
- medplum.post('fhir/R4', buildBatchRequest(requestGroup)).then(setResponseBundle);
4913
+ medplum.executeBatch(buildBatchRequest(requestGroup)).then(setResponseBundle);
4813
4914
  setStartedLoading(true);
4814
4915
  }
4815
4916
  }, [medplum, requestGroup, startedLoading]);
@@ -5164,6 +5265,165 @@ function getVersionUrl(resource) {
5164
5265
  return `/${resource.resourceType}/${resource.id}/_history/${(_a = resource.meta) === null || _a === void 0 ? void 0 : _a.versionId}`;
5165
5266
  }
5166
5267
 
5268
+ /**
5269
+ * Returns a month display string (e.g. "January 2020").
5270
+ * @param date Any date within the month.
5271
+ * @returns The month display string (e.g. "January 2020")
5272
+ */
5273
+ function getMonthString(date) {
5274
+ return date.toLocaleString('default', { month: 'long' }) + ' ' + date.getFullYear();
5275
+ }
5276
+ function CalendarInput(props) {
5277
+ const [month, setMonth] = useState(getStartMonth);
5278
+ function moveMonth(delta) {
5279
+ setMonth((currMonth) => {
5280
+ const prevMonth = new Date(currMonth.getTime());
5281
+ prevMonth.setMonth(currMonth.getMonth() + delta);
5282
+ return prevMonth;
5283
+ });
5284
+ }
5285
+ const grid = useMemo(() => buildGrid(month, props.slots), [month, props.slots]);
5286
+ return (React.createElement("div", null,
5287
+ React.createElement(InputRow, null,
5288
+ React.createElement("p", { style: { flex: 1 } }, getMonthString(month)),
5289
+ React.createElement("p", null,
5290
+ React.createElement(Button, { label: "Previous month", onClick: () => moveMonth(-1) }, "<"),
5291
+ React.createElement(Button, { label: "Next month", onClick: () => moveMonth(1) }, ">"))),
5292
+ React.createElement("table", { className: "medplum-calendar-table" },
5293
+ React.createElement("thead", null,
5294
+ React.createElement("tr", null,
5295
+ React.createElement("th", null, "SUN"),
5296
+ React.createElement("th", null, "MON"),
5297
+ React.createElement("th", null, "TUE"),
5298
+ React.createElement("th", null, "WED"),
5299
+ React.createElement("th", null, "THU"),
5300
+ React.createElement("th", null, "FRI"),
5301
+ React.createElement("th", null, "SAT"))),
5302
+ React.createElement("tbody", null, grid.map((week, weekIndex) => (React.createElement("tr", { key: 'week-' + weekIndex }, week.map((day, dayIndex) => (React.createElement("td", { key: 'day-' + dayIndex }, day && (React.createElement("button", { disabled: !day.available, onClick: () => props.onClick(day.date) }, day.date.getDate()))))))))))));
5303
+ }
5304
+ function getStartMonth() {
5305
+ const result = new Date();
5306
+ result.setDate(1);
5307
+ result.setHours(0, 0, 0, 0);
5308
+ return result;
5309
+ }
5310
+ function buildGrid(startDate, slots) {
5311
+ const d = new Date(startDate.getFullYear(), startDate.getMonth());
5312
+ const grid = [];
5313
+ let row = [];
5314
+ // Fill leading empty days
5315
+ for (let i = 0; i < d.getDay(); i++) {
5316
+ row.push(undefined);
5317
+ }
5318
+ while (d.getMonth() === startDate.getMonth()) {
5319
+ row.push({
5320
+ date: new Date(d.getTime()),
5321
+ // available: isAvailable(d),
5322
+ available: isDayAvailable(d, slots),
5323
+ });
5324
+ if (d.getDay() === 6) {
5325
+ grid.push(row);
5326
+ row = [];
5327
+ }
5328
+ d.setDate(d.getDate() + 1);
5329
+ }
5330
+ // Fill trailing empty days
5331
+ if (d.getDay() !== 0) {
5332
+ for (let i = d.getDay(); i < 7; i++) {
5333
+ row.push(undefined);
5334
+ }
5335
+ grid.push(row);
5336
+ }
5337
+ return grid;
5338
+ }
5339
+ /**
5340
+ * Returns true if the given date is available for booking.
5341
+ * @param day The day to check.
5342
+ * @param slots The list of available slots.
5343
+ * @returns True if there are any available slots for the day.
5344
+ */
5345
+ function isDayAvailable(day, slots) {
5346
+ // Note that slot start and end time may or may not be in UTC.
5347
+ for (const slot of slots) {
5348
+ const slotStart = new Date(slot.start);
5349
+ if (slotStart.getFullYear() === day.getFullYear() &&
5350
+ slotStart.getMonth() === day.getMonth() &&
5351
+ slotStart.getDate() === day.getDate()) {
5352
+ return true;
5353
+ }
5354
+ }
5355
+ return false;
5356
+ }
5357
+
5358
+ function Scheduler(props) {
5359
+ var _a;
5360
+ const medplum = useMedplum();
5361
+ const schedule = useResource(props.schedule);
5362
+ const [slots, setSlots] = useState();
5363
+ const slotsRef = useRef();
5364
+ slotsRef.current = slots;
5365
+ const [date, setDate] = useState();
5366
+ const [slot, setSlot] = useState();
5367
+ const [info, setInfo] = useState();
5368
+ const [form, setForm] = useState();
5369
+ useEffect(() => {
5370
+ if (schedule) {
5371
+ medplum.search('Slot', 'schedule=' + getReferenceString(schedule)).then((bundle) => {
5372
+ setSlots(bundle.entry.map((entry) => entry.resource));
5373
+ });
5374
+ }
5375
+ else {
5376
+ setSlots(undefined);
5377
+ }
5378
+ }, [medplum, schedule]);
5379
+ if (!schedule || !slots) {
5380
+ return null;
5381
+ }
5382
+ const actor = (_a = schedule.actor) === null || _a === void 0 ? void 0 : _a[0];
5383
+ return (React.createElement("div", { className: "medplum-calendar-container", "data-testid": "scheduler" },
5384
+ React.createElement("div", { className: "medplum-calendar-info-pane" },
5385
+ actor && React.createElement(Avatar, { value: actor, size: "large" }),
5386
+ actor && (React.createElement("h1", null,
5387
+ React.createElement(ResourceName, { value: actor }))),
5388
+ React.createElement("p", null, "1 hour"),
5389
+ date && React.createElement("p", null, date.toLocaleDateString()),
5390
+ slot && React.createElement("p", null, formatTime(new Date(slot.start)))),
5391
+ React.createElement("div", { className: "medplum-calendar-selection-pane" },
5392
+ !date && (React.createElement("div", null,
5393
+ React.createElement("h3", null, "Select date"),
5394
+ React.createElement(CalendarInput, { slots: slots, onClick: setDate }))),
5395
+ date && !slot && (React.createElement("div", null,
5396
+ React.createElement("h3", null, "Select time"),
5397
+ slots.map((s) => {
5398
+ const slotStart = new Date(s.start);
5399
+ return (slotStart.getTime() > date.getTime() &&
5400
+ slotStart.getTime() < date.getTime() + 24 * 3600 * 1000 && (React.createElement("div", { key: s.id },
5401
+ React.createElement(Button, { style: { width: 150 }, onClick: () => setSlot(s) }, formatTime(slotStart)))));
5402
+ }))),
5403
+ date && slot && !info && (React.createElement("div", null,
5404
+ React.createElement("h3", null, "Enter your info"),
5405
+ React.createElement(FormSection, { title: "Name", htmlFor: "name" },
5406
+ React.createElement(Input, { name: "name" })),
5407
+ React.createElement(FormSection, { title: "Email", htmlFor: "email" },
5408
+ React.createElement(Input, { name: "email" })),
5409
+ React.createElement(Button, { primary: true, onClick: () => setInfo('info') }, "Next"))),
5410
+ date && slot && info && !form && (React.createElement("div", null,
5411
+ React.createElement("h3", null, "Custom questions"),
5412
+ React.createElement(FormSection, { title: "Question 1", htmlFor: "q1" },
5413
+ React.createElement(Input, { name: "q1" })),
5414
+ React.createElement(FormSection, { title: "Question 2", htmlFor: "q2" },
5415
+ React.createElement(Input, { name: "email" })),
5416
+ React.createElement(FormSection, { title: "Question 3", htmlFor: "q3" },
5417
+ React.createElement(Input, { name: "email" })),
5418
+ React.createElement(Button, { primary: true, onClick: () => setForm('form') }, "Next"))),
5419
+ date && slot && info && form && (React.createElement("div", null,
5420
+ React.createElement("h3", null, "You're all set!"),
5421
+ React.createElement("p", null, "Check your email for a calendar invite."))))));
5422
+ }
5423
+ function formatTime(date) {
5424
+ return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
5425
+ }
5426
+
5167
5427
  function ServiceRequestTimeline(props) {
5168
5428
  return (React.createElement(ResourceTimeline, { value: props.serviceRequest, buildSearchRequests: (resource) => ({
5169
5429
  resourceType: 'Bundle',
@@ -5226,7 +5486,7 @@ function createScriptTag(src, onload) {
5226
5486
 
5227
5487
  function GoogleButton(props) {
5228
5488
  const medplum = useMedplum();
5229
- const { handleAuthResponse } = props;
5489
+ const { handleGoogleCredential } = props;
5230
5490
  const googleClientId = getGoogleClientId(props.googleClientId);
5231
5491
  const parentRef = useRef(null);
5232
5492
  const [scriptLoaded, setScriptLoaded] = useState(typeof google !== 'undefined');
@@ -5240,7 +5500,7 @@ function GoogleButton(props) {
5240
5500
  if (!initialized) {
5241
5501
  google.accounts.id.initialize({
5242
5502
  client_id: googleClientId,
5243
- callback: (response) => medplum.startGoogleLogin(response).then(handleAuthResponse),
5503
+ callback: handleGoogleCredential,
5244
5504
  });
5245
5505
  setInitialized(true);
5246
5506
  }
@@ -5248,7 +5508,7 @@ function GoogleButton(props) {
5248
5508
  google.accounts.id.renderButton(parentRef.current, {});
5249
5509
  setButtonRendered(true);
5250
5510
  }
5251
- }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleAuthResponse]);
5511
+ }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
5252
5512
  if (!googleClientId) {
5253
5513
  return null;
5254
5514
  }
@@ -5279,19 +5539,24 @@ function SignInForm(props) {
5279
5539
  setMemberships(response.memberships);
5280
5540
  }
5281
5541
  if (response.code) {
5282
- medplum
5283
- .processCode(response.code)
5284
- .then(() => {
5285
- if (props.onSuccess) {
5286
- props.onSuccess();
5287
- }
5288
- })
5289
- .catch(console.log);
5542
+ if (props.onCode) {
5543
+ props.onCode(response.code);
5544
+ }
5545
+ else {
5546
+ medplum
5547
+ .processCode(response.code)
5548
+ .then(() => {
5549
+ if (props.onSuccess) {
5550
+ props.onSuccess();
5551
+ }
5552
+ })
5553
+ .catch(console.log);
5554
+ }
5290
5555
  }
5291
5556
  }
5292
5557
  return (React.createElement(Document, { width: 450 }, (() => {
5293
5558
  if (!login) {
5294
- return (React.createElement(AuthenticationForm, { googleClientId: props.googleClientId, onForgotPassword: props.onForgotPassword, onRegister: props.onRegister, handleAuthResponse: handleAuthResponse }, props.children));
5559
+ return (React.createElement(AuthenticationForm, { clientId: props.clientId, scope: props.scope, nonce: props.nonce, googleClientId: props.googleClientId, onForgotPassword: props.onForgotPassword, onRegister: props.onRegister, handleAuthResponse: handleAuthResponse }, props.children));
5295
5560
  }
5296
5561
  else if (memberships) {
5297
5562
  return React.createElement(ProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
@@ -5308,6 +5573,9 @@ function AuthenticationForm(props) {
5308
5573
  return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
5309
5574
  medplum
5310
5575
  .startLogin({
5576
+ clientId: props.clientId,
5577
+ scope: props.scope,
5578
+ nonce: props.nonce,
5311
5579
  email: formData.email,
5312
5580
  password: formData.password,
5313
5581
  remember: formData.remember === 'true',
@@ -5334,7 +5602,18 @@ function AuthenticationForm(props) {
5334
5602
  React.createElement("div", null,
5335
5603
  React.createElement(Button, { type: "submit", testid: "submit" }, "Sign in"))),
5336
5604
  React.createElement("div", { className: "medplum-signin-google-container" },
5337
- React.createElement(GoogleButton, { googleClientId: props.googleClientId, handleAuthResponse: props.handleAuthResponse }))));
5605
+ React.createElement(GoogleButton, { googleClientId: props.googleClientId, handleGoogleCredential: (response) => {
5606
+ medplum
5607
+ .startGoogleLogin({
5608
+ clientId: props.clientId,
5609
+ scope: props.scope,
5610
+ nonce: props.nonce,
5611
+ googleClientId: response.clientId,
5612
+ googleCredential: response.credential,
5613
+ })
5614
+ .then(props.handleAuthResponse)
5615
+ .catch(setOutcome);
5616
+ } }))));
5338
5617
  }
5339
5618
  function ProfileForm(props) {
5340
5619
  const medplum = useMedplum();
@@ -5406,5 +5685,5 @@ function TabSwitch(props) {
5406
5685
  })));
5407
5686
  }
5408
5687
 
5409
- export { AddressDisplay, AddressInput, AttachmentArrayDisplay, AttachmentArrayInput, AttachmentInput, Autocomplete, Avatar, BackboneElementInput, Button, Checkbox, CheckboxFormSection, CodeInput, CodeableConceptDisplay, CodeableConceptInput, ContactPointDisplay, ContactPointInput, DateTimeDisplay, DateTimeInput, DefaultResourceTimeline, DiagnosticReportDisplay, Document, ElementDefinitionInputSelector, ElementDefinitionTypeInput, EncounterTimeline, ErrorBoundary, FhirPathTable, FooterLinks, Form, FormSection, Header, HumanNameDisplay, HumanNameInput, IdentifierInput, Input, Loading, Logo, MedplumLink, MedplumProvider, MemoizedFhirPathTable, MemoizedSearchControl, MenuItem, ObservationTable, PatientTimeline, PlanDefinitionBuilder, Popup, QuestionnaireBuilder, QuestionnaireForm, QuestionnaireFormItem, QuestionnaireItemType, RangeDisplay, RangeInput, ReferenceInput, RequestGroupDisplay, ResourceArrayDisplay, ResourceArrayInput, ResourceBadge, ResourceBlame, ResourceDiff, ResourceForm, ResourceHistoryTable, ResourceInput, ResourceName, ResourcePropertyDisplay, ResourcePropertyInput, ResourceTable, ResourceTimeline, Scrollable, SearchChangeEvent, SearchClickEvent, SearchControl, SearchFieldEditor, SearchFilterEditor, SearchLoadEvent, Select, ServiceRequestTimeline, SignInForm, StatusBadge, Tab, TabList, TabPanel, TabSwitch, TextArea, Timeline, TimelineItem, TitleBar, UploadButton, addDateEqualsFilter, addDateFilter, addDateFilterBetween, addField, addFilter, addLastMonthFilter, addMissingFilter, addNextMonthFilter, addQuestionnaireInitialValues, addThisMonthFilter, addTodayFilter, addTomorrowFilter, addYearToDateFilter, addYesterdayFilter, buildFieldNameString, clearFilters, clearFiltersOnField, convertIsoToLocal, convertLocalToIso, createScriptTag, deleteFilter, formatRangeString, getOpString, getSearchOperators, getSortField, getTimeString, getValueAndType, hasFilterOnField, isChoiceQuestion, isSortDescending, movePage, parseForm, renderValue, setFilters, setOffset, setPropertyValue, setSort, sortByDateAndPriority, toggleSort, useMedplum, useMedplumContext, useMedplumProfile, useResource };
5688
+ export { AddressDisplay, AddressInput, AttachmentArrayDisplay, AttachmentArrayInput, AttachmentInput, Autocomplete, Avatar, BackboneElementInput, Button, Checkbox, CheckboxFormSection, CodeInput, CodeableConceptDisplay, CodeableConceptInput, ContactDetailDisplay, ContactDetailInput, ContactPointDisplay, ContactPointInput, DateTimeDisplay, DateTimeInput, DefaultResourceTimeline, DiagnosticReportDisplay, Document, ElementDefinitionInputSelector, ElementDefinitionTypeInput, EncounterTimeline, ErrorBoundary, FhirPathTable, FooterLinks, Form, FormSection, Header, HumanNameDisplay, HumanNameInput, IdentifierInput, Input, Loading, Logo, MedplumLink, MedplumProvider, MemoizedFhirPathTable, MemoizedSearchControl, MenuItem, ObservationTable, PatientTimeline, PlanDefinitionBuilder, Popup, QuestionnaireBuilder, QuestionnaireForm, QuestionnaireFormItem, QuestionnaireItemType, RangeDisplay, RangeInput, ReferenceInput, RequestGroupDisplay, ResourceArrayDisplay, ResourceArrayInput, ResourceBadge, ResourceBlame, ResourceDiff, ResourceForm, ResourceHistoryTable, ResourceInput, ResourceName, ResourcePropertyDisplay, ResourcePropertyInput, ResourceTable, ResourceTimeline, Scheduler, Scrollable, SearchChangeEvent, SearchClickEvent, SearchControl, SearchFieldEditor, SearchFilterEditor, SearchLoadEvent, Select, ServiceRequestTimeline, SignInForm, StatusBadge, Tab, TabList, TabPanel, TabSwitch, TextArea, Timeline, TimelineItem, TitleBar, UploadButton, addDateEqualsFilter, addDateFilter, addDateFilterBetween, addField, addFilter, addLastMonthFilter, addMissingFilter, addNextMonthFilter, addQuestionnaireInitialValues, addThisMonthFilter, addTodayFilter, addTomorrowFilter, addYearToDateFilter, addYesterdayFilter, buildFieldNameString, clearFilters, clearFiltersOnField, convertIsoToLocal, convertLocalToIso, createScriptTag, deleteFilter, formatRangeString, getOpString, getSearchOperators, getSortField, getTimeString, getValueAndType, hasFilterOnField, isChoiceQuestion, isSortDescending, movePage, parseForm, renderValue, setFilters, setOffset, setPropertyValue, setSort, sortByDateAndPriority, toggleSort, useMedplum, useMedplumContext, useMedplumProfile, useResource };
5410
5689
  //# sourceMappingURL=index.js.map