@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.
- package/dist/cjs/index.js +318 -36
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +1 -1
- package/dist/cjs/index.min.js.map +1 -1
- package/dist/cjs/styles.css +58 -0
- package/dist/esm/index.js +317 -38
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.min.js +1 -1
- package/dist/esm/index.min.js.map +1 -1
- package/dist/esm/styles.css +58 -0
- package/dist/types/ContactDetailDisplay.d.ts +6 -0
- package/dist/types/ContactDetailInput.d.ts +8 -0
- package/dist/types/ContactPointInput.d.ts +1 -1
- package/dist/types/GoogleButton.d.ts +3 -3
- package/dist/types/Input.d.ts +2 -1
- package/dist/types/SignInForm.d.ts +11 -8
- package/dist/types/index.d.ts +3 -0
- package/dist/types/stories/QuestionnaireForm.stories.d.ts +2 -0
- package/package.json +12 -12
package/dist/cjs/styles.css
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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:
|
|
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,
|
|
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
|
-
|
|
5283
|
-
.
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
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,
|
|
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
|