@medplum/react 0.9.20 → 0.9.23
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 +407 -94
- 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 +109 -36
- package/dist/esm/index.js +406 -97
- 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 +109 -36
- package/dist/types/RegisterForm.d.ts +17 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/stories/QuestionnaireForm.stories.d.ts +2 -0
- package/dist/types/stories/RegisterForm.stories.d.ts +7 -0
- package/dist/types/utils/recaptcha.d.ts +10 -0
- package/package.json +12 -12
package/dist/cjs/index.js
CHANGED
|
@@ -1776,18 +1776,24 @@
|
|
|
1776
1776
|
}
|
|
1777
1777
|
|
|
1778
1778
|
function DiagnosticReportDisplay(props) {
|
|
1779
|
-
var _a;
|
|
1779
|
+
var _a, _b;
|
|
1780
1780
|
const diagnosticReport = useResource(props.value);
|
|
1781
|
+
const specimen = useResource((_a = diagnosticReport === null || diagnosticReport === void 0 ? void 0 : diagnosticReport.specimen) === null || _a === void 0 ? void 0 : _a[0]);
|
|
1781
1782
|
if (!diagnosticReport) {
|
|
1782
1783
|
return null;
|
|
1783
1784
|
}
|
|
1784
|
-
let textContent =
|
|
1785
|
+
let textContent = '';
|
|
1785
1786
|
if (diagnosticReport.presentedForm && diagnosticReport.presentedForm.length > 0) {
|
|
1786
1787
|
const pf = diagnosticReport.presentedForm[0];
|
|
1787
|
-
if (((
|
|
1788
|
+
if (((_b = pf.contentType) === null || _b === void 0 ? void 0 : _b.startsWith('text/plain')) && pf.data) {
|
|
1788
1789
|
textContent = window.atob(pf.data);
|
|
1789
1790
|
}
|
|
1790
1791
|
}
|
|
1792
|
+
if (specimen === null || specimen === void 0 ? void 0 : specimen.note) {
|
|
1793
|
+
for (const note of specimen.note) {
|
|
1794
|
+
textContent += note.text + '\n\n';
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1791
1797
|
return (React__default["default"].createElement("div", { className: "medplum-diagnostic-report" },
|
|
1792
1798
|
React__default["default"].createElement("h1", null, "Diagnostic Report"),
|
|
1793
1799
|
React__default["default"].createElement("div", { className: "medplum-diagnostic-report-header" },
|
|
@@ -1807,8 +1813,8 @@
|
|
|
1807
1813
|
diagnosticReport.status && (React__default["default"].createElement("dl", null,
|
|
1808
1814
|
React__default["default"].createElement("dt", null, "Status"),
|
|
1809
1815
|
React__default["default"].createElement("dd", null, core.capitalize(diagnosticReport.status))))),
|
|
1810
|
-
|
|
1811
|
-
|
|
1816
|
+
diagnosticReport.result && React__default["default"].createElement(ObservationTable, { value: diagnosticReport.result }),
|
|
1817
|
+
textContent && React__default["default"].createElement("pre", null, textContent.trim())));
|
|
1812
1818
|
}
|
|
1813
1819
|
function ObservationTable(props) {
|
|
1814
1820
|
var _a;
|
|
@@ -2184,8 +2190,7 @@
|
|
|
2184
2190
|
setHistory({});
|
|
2185
2191
|
return;
|
|
2186
2192
|
}
|
|
2187
|
-
|
|
2188
|
-
medplum.post('fhir/R4', batchRequest).then(handleBatchResponse);
|
|
2193
|
+
medplum.executeBatch(buildSearchRequests(resource)).then(handleBatchResponse);
|
|
2189
2194
|
}, [medplum, resource, buildSearchRequests]);
|
|
2190
2195
|
React.useEffect(() => {
|
|
2191
2196
|
loadTimeline();
|
|
@@ -4613,31 +4618,64 @@
|
|
|
4613
4618
|
return (React__default["default"].createElement(TextArea, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueString, onChange: (newValue) => onChangeAnswer({ valueString: newValue }) }));
|
|
4614
4619
|
case exports.QuestionnaireItemType.url:
|
|
4615
4620
|
return (React__default["default"].createElement(Input, { type: "url", name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueUri, onChange: (newValue) => onChangeAnswer({ valueUri: newValue }) }));
|
|
4616
|
-
case exports.QuestionnaireItemType.choice:
|
|
4617
|
-
case exports.QuestionnaireItemType.openChoice:
|
|
4618
|
-
return (React__default["default"].createElement("div", null, item.answerOption &&
|
|
4619
|
-
item.answerOption.map((option, index) => {
|
|
4620
|
-
const valueElementDefinition = core.globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
|
|
4621
|
-
const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
|
|
4622
|
-
const initialValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
|
|
4623
|
-
const propertyName = 'value' + core.capitalize(optionValue.type);
|
|
4624
|
-
const optionName = `${name}-option-${index}`;
|
|
4625
|
-
return (React__default["default"].createElement("div", { key: optionName, className: "medplum-questionnaire-option-row" },
|
|
4626
|
-
React__default["default"].createElement("div", { className: "medplum-questionnaire-option-checkbox" },
|
|
4627
|
-
React__default["default"].createElement("input", { type: "radio", id: optionName, name: name, value: optionValue.value, defaultChecked: initialValue && core.stringify(optionValue) === core.stringify(initialValue), onChange: () => onChangeAnswer({ [propertyName]: optionValue.value }) })),
|
|
4628
|
-
React__default["default"].createElement("div", null,
|
|
4629
|
-
React__default["default"].createElement("label", { htmlFor: optionName },
|
|
4630
|
-
React__default["default"].createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })))));
|
|
4631
|
-
})));
|
|
4632
4621
|
case exports.QuestionnaireItemType.attachment:
|
|
4633
4622
|
return (React__default["default"].createElement(AttachmentInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueAttachment, onChange: (newValue) => onChangeAnswer({ valueAttachment: newValue }) }));
|
|
4634
4623
|
case exports.QuestionnaireItemType.reference:
|
|
4635
4624
|
return (React__default["default"].createElement(ReferenceInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueReference, onChange: (newValue) => onChangeAnswer({ valueReference: newValue }) }));
|
|
4636
4625
|
case exports.QuestionnaireItemType.quantity:
|
|
4637
4626
|
return (React__default["default"].createElement(QuantityInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueQuantity, onChange: (newValue) => onChangeAnswer({ valueQuantity: newValue }) }));
|
|
4627
|
+
case exports.QuestionnaireItemType.choice:
|
|
4628
|
+
case exports.QuestionnaireItemType.openChoice:
|
|
4629
|
+
if (isDropDownChoice(item)) {
|
|
4630
|
+
return (React__default["default"].createElement(QuestionnaireChoiceDropDownInput, { name: name, item: item, initial: initial, onChangeAnswer: onChangeAnswer }));
|
|
4631
|
+
}
|
|
4632
|
+
else {
|
|
4633
|
+
return (React__default["default"].createElement(QuestionnaireChoiceRadioInput, { name: name, item: item, initial: initial, onChangeAnswer: onChangeAnswer }));
|
|
4634
|
+
}
|
|
4638
4635
|
}
|
|
4639
4636
|
return null;
|
|
4640
4637
|
}
|
|
4638
|
+
function QuestionnaireChoiceDropDownInput(props) {
|
|
4639
|
+
const { name, item, initial } = props;
|
|
4640
|
+
const valueElementDefinition = core.globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
|
|
4641
|
+
const initialValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
|
|
4642
|
+
return (React__default["default"].createElement("select", { id: name, name: name, className: "medplum-select", onChange: (e) => {
|
|
4643
|
+
const index = e.currentTarget.selectedIndex;
|
|
4644
|
+
if (index === 0) {
|
|
4645
|
+
props.onChangeAnswer({});
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
const option = item.answerOption[index - 1];
|
|
4649
|
+
const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
|
|
4650
|
+
const propertyName = 'value' + core.capitalize(optionValue.type);
|
|
4651
|
+
props.onChangeAnswer({ [propertyName]: optionValue.value });
|
|
4652
|
+
} },
|
|
4653
|
+
React__default["default"].createElement("option", null),
|
|
4654
|
+
item.answerOption &&
|
|
4655
|
+
item.answerOption.map((option, index) => {
|
|
4656
|
+
const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
|
|
4657
|
+
const optionName = `${name}-option-${index}`;
|
|
4658
|
+
return (React__default["default"].createElement("option", { key: optionName, value: optionValue.value, selected: initialValue && core.stringify(optionValue) === core.stringify(initialValue) },
|
|
4659
|
+
React__default["default"].createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })));
|
|
4660
|
+
})));
|
|
4661
|
+
}
|
|
4662
|
+
function QuestionnaireChoiceRadioInput(props) {
|
|
4663
|
+
const { name, item, initial, onChangeAnswer } = props;
|
|
4664
|
+
const valueElementDefinition = core.globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
|
|
4665
|
+
const initialValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
|
|
4666
|
+
return (React__default["default"].createElement(React__default["default"].Fragment, null, item.answerOption &&
|
|
4667
|
+
item.answerOption.map((option, index) => {
|
|
4668
|
+
const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
|
|
4669
|
+
const propertyName = 'value' + core.capitalize(optionValue.type);
|
|
4670
|
+
const optionName = `${name}-option-${index}`;
|
|
4671
|
+
return (React__default["default"].createElement("div", { key: optionName, className: "medplum-questionnaire-option-row" },
|
|
4672
|
+
React__default["default"].createElement("div", { className: "medplum-questionnaire-option-checkbox" },
|
|
4673
|
+
React__default["default"].createElement("input", { type: "radio", id: optionName, name: name, value: optionValue.value, defaultChecked: initialValue && core.stringify(optionValue) === core.stringify(initialValue), onChange: () => onChangeAnswer({ [propertyName]: optionValue.value }) })),
|
|
4674
|
+
React__default["default"].createElement("div", null,
|
|
4675
|
+
React__default["default"].createElement("label", { htmlFor: optionName },
|
|
4676
|
+
React__default["default"].createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })))));
|
|
4677
|
+
})));
|
|
4678
|
+
}
|
|
4641
4679
|
function buildInitialResponse(questionnaire) {
|
|
4642
4680
|
const response = {
|
|
4643
4681
|
resourceType: 'QuestionnaireResponse',
|
|
@@ -4663,6 +4701,14 @@
|
|
|
4663
4701
|
// have the same properties.
|
|
4664
4702
|
return Object.assign({}, answer);
|
|
4665
4703
|
}
|
|
4704
|
+
function isDropDownChoice(item) {
|
|
4705
|
+
var _a;
|
|
4706
|
+
return !!((_a = item.extension) === null || _a === void 0 ? void 0 : _a.some((e) => {
|
|
4707
|
+
var _a, _b, _c;
|
|
4708
|
+
return e.url === 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl' &&
|
|
4709
|
+
((_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';
|
|
4710
|
+
}));
|
|
4711
|
+
}
|
|
4666
4712
|
|
|
4667
4713
|
function QuestionnaireBuilder(props) {
|
|
4668
4714
|
const medplum = useMedplum();
|
|
@@ -4864,6 +4910,163 @@
|
|
|
4864
4910
|
return options.map((option) => (Object.assign(Object.assign({}, option), { id: option.id || generateId() })));
|
|
4865
4911
|
}
|
|
4866
4912
|
|
|
4913
|
+
/**
|
|
4914
|
+
* Dynamically creates a script tag for the specified JavaScript file.
|
|
4915
|
+
* @param src The JavaScript file URL.
|
|
4916
|
+
*/
|
|
4917
|
+
function createScriptTag(src, onload) {
|
|
4918
|
+
const head = document.getElementsByTagName('head')[0];
|
|
4919
|
+
const script = document.createElement('script');
|
|
4920
|
+
script.async = true;
|
|
4921
|
+
script.src = src;
|
|
4922
|
+
script.onload = onload || null;
|
|
4923
|
+
head.appendChild(script);
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
function GoogleButton(props) {
|
|
4927
|
+
const medplum = useMedplum();
|
|
4928
|
+
const { googleClientId, handleGoogleCredential } = props;
|
|
4929
|
+
const parentRef = React.useRef(null);
|
|
4930
|
+
const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
|
|
4931
|
+
const [initialized, setInitialized] = React.useState(false);
|
|
4932
|
+
const [buttonRendered, setButtonRendered] = React.useState(false);
|
|
4933
|
+
React.useEffect(() => {
|
|
4934
|
+
if (typeof google === 'undefined') {
|
|
4935
|
+
createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
|
|
4936
|
+
return;
|
|
4937
|
+
}
|
|
4938
|
+
if (!initialized) {
|
|
4939
|
+
google.accounts.id.initialize({
|
|
4940
|
+
client_id: googleClientId,
|
|
4941
|
+
callback: handleGoogleCredential,
|
|
4942
|
+
});
|
|
4943
|
+
setInitialized(true);
|
|
4944
|
+
}
|
|
4945
|
+
if (parentRef.current && !buttonRendered) {
|
|
4946
|
+
google.accounts.id.renderButton(parentRef.current, {});
|
|
4947
|
+
setButtonRendered(true);
|
|
4948
|
+
}
|
|
4949
|
+
}, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
|
|
4950
|
+
if (!googleClientId) {
|
|
4951
|
+
return null;
|
|
4952
|
+
}
|
|
4953
|
+
return React__default["default"].createElement("div", { ref: parentRef });
|
|
4954
|
+
}
|
|
4955
|
+
function getGoogleClientId(clientId) {
|
|
4956
|
+
var _a, _b;
|
|
4957
|
+
if (clientId) {
|
|
4958
|
+
return clientId;
|
|
4959
|
+
}
|
|
4960
|
+
const origin = window.location.protocol + '//' + window.location.host;
|
|
4961
|
+
const authorizedOrigins = (_b = (_a = "http://localhost:3000,http://localhost:6006,https://app.medplum.com,https://docs.medplum.com") === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
|
|
4962
|
+
if (authorizedOrigins.includes(origin)) {
|
|
4963
|
+
return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
|
|
4964
|
+
}
|
|
4965
|
+
return undefined;
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4968
|
+
/**
|
|
4969
|
+
* Dynamically loads the recaptcha script.
|
|
4970
|
+
* We do not want to load the script on page load unless the user needs it.
|
|
4971
|
+
*/
|
|
4972
|
+
function initRecaptcha() {
|
|
4973
|
+
if (typeof grecaptcha === 'undefined') {
|
|
4974
|
+
createScriptTag('https://www.google.com/recaptcha/api.js?render=' + process.env.RECAPTCHA_SITE_KEY);
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
/**
|
|
4978
|
+
* Starts a request to generate a recapcha token.
|
|
4979
|
+
* @returns Promise to a recaptcha token for the current user.
|
|
4980
|
+
*/
|
|
4981
|
+
function getRecaptcha() {
|
|
4982
|
+
return new Promise((resolve) => {
|
|
4983
|
+
grecaptcha.ready(() => {
|
|
4984
|
+
grecaptcha.execute(process.env.RECAPTCHA_SITE_KEY, { action: 'submit' }).then(resolve);
|
|
4985
|
+
});
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
function RegisterForm(props) {
|
|
4990
|
+
const medplum = useMedplum();
|
|
4991
|
+
const googleClientId = getGoogleClientId(props.googleClientId);
|
|
4992
|
+
const [outcome, setOutcome] = React.useState();
|
|
4993
|
+
const issues = getIssuesForExpression(outcome, undefined);
|
|
4994
|
+
React.useEffect(initRecaptcha, []);
|
|
4995
|
+
function handleAuthResponse(registerRequest, partialLogin) {
|
|
4996
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
4997
|
+
try {
|
|
4998
|
+
let login;
|
|
4999
|
+
if (props.type === 'patient') {
|
|
5000
|
+
login = yield medplum.startNewPatient(registerRequest, partialLogin);
|
|
5001
|
+
}
|
|
5002
|
+
else {
|
|
5003
|
+
login = yield medplum.startNewProject(registerRequest, partialLogin);
|
|
5004
|
+
}
|
|
5005
|
+
yield medplum.processCode(login.code);
|
|
5006
|
+
props.onSuccess();
|
|
5007
|
+
}
|
|
5008
|
+
catch (err) {
|
|
5009
|
+
setOutcome(err);
|
|
5010
|
+
}
|
|
5011
|
+
});
|
|
5012
|
+
}
|
|
5013
|
+
return (React__default["default"].createElement(Document, { width: 450 },
|
|
5014
|
+
React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => __awaiter(this, void 0, void 0, function* () {
|
|
5015
|
+
try {
|
|
5016
|
+
const recaptchaToken = yield getRecaptcha();
|
|
5017
|
+
const registerRequest = Object.assign(Object.assign({}, formData), { recaptchaToken });
|
|
5018
|
+
const userLogin = yield medplum.startNewUser(registerRequest);
|
|
5019
|
+
handleAuthResponse(registerRequest, userLogin);
|
|
5020
|
+
}
|
|
5021
|
+
catch (err) {
|
|
5022
|
+
setOutcome(err);
|
|
5023
|
+
}
|
|
5024
|
+
}) },
|
|
5025
|
+
React__default["default"].createElement("div", { className: "medplum-center" }, props.children),
|
|
5026
|
+
issues && (React__default["default"].createElement("div", { className: "medplum-input-error" }, issues.map((issue) => {
|
|
5027
|
+
var _a, _b;
|
|
5028
|
+
return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
|
|
5029
|
+
}))),
|
|
5030
|
+
googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
5031
|
+
React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
|
|
5032
|
+
React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => __awaiter(this, void 0, void 0, function* () {
|
|
5033
|
+
try {
|
|
5034
|
+
const loginRequest = {
|
|
5035
|
+
googleClientId: response.clientId,
|
|
5036
|
+
googleCredential: response.credential,
|
|
5037
|
+
};
|
|
5038
|
+
const userLogin = yield medplum.startGoogleLogin(loginRequest);
|
|
5039
|
+
const googleClaims = core.parseJWTPayload(loginRequest.googleCredential);
|
|
5040
|
+
const registerRequest = {
|
|
5041
|
+
firstName: googleClaims.given_name,
|
|
5042
|
+
lastName: googleClaims.family_name,
|
|
5043
|
+
email: googleClaims.email,
|
|
5044
|
+
};
|
|
5045
|
+
handleAuthResponse(registerRequest, userLogin);
|
|
5046
|
+
}
|
|
5047
|
+
catch (err) {
|
|
5048
|
+
setOutcome(err);
|
|
5049
|
+
}
|
|
5050
|
+
}) })),
|
|
5051
|
+
React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
|
|
5052
|
+
React__default["default"].createElement(FormSection, { title: "First Name", htmlFor: "firstName", outcome: outcome },
|
|
5053
|
+
React__default["default"].createElement(Input, { name: "firstName", type: "text", testid: "firstName", placeholder: "First name", required: true, autoFocus: true, outcome: outcome })),
|
|
5054
|
+
React__default["default"].createElement(FormSection, { title: "Last Name", htmlFor: "lastName", outcome: outcome },
|
|
5055
|
+
React__default["default"].createElement(Input, { name: "lastName", type: "text", testid: "lastName", placeholder: "Last name", required: true, outcome: outcome })),
|
|
5056
|
+
props.type === 'project' && (React__default["default"].createElement(FormSection, { title: "Project Name", htmlFor: "projectName", outcome: outcome },
|
|
5057
|
+
React__default["default"].createElement(Input, { name: "projectName", type: "text", testid: "projectName", placeholder: "My Project", required: true, outcome: outcome }))),
|
|
5058
|
+
React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
|
|
5059
|
+
React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", placeholder: "name@domain.com", required: true, outcome: outcome })),
|
|
5060
|
+
React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
|
|
5061
|
+
React__default["default"].createElement(Input, { name: "password", type: "password", testid: "password", autoComplete: "off", required: true, outcome: outcome })),
|
|
5062
|
+
React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
|
|
5063
|
+
React__default["default"].createElement("div", null,
|
|
5064
|
+
React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
|
|
5065
|
+
React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
|
|
5066
|
+
React__default["default"].createElement("div", null,
|
|
5067
|
+
React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Create account"))))));
|
|
5068
|
+
}
|
|
5069
|
+
|
|
4867
5070
|
function StatusBadge(props) {
|
|
4868
5071
|
return React__default["default"].createElement("span", { className: `medplum-status medplum-status-${props.status}` }, props.status);
|
|
4869
5072
|
}
|
|
@@ -4876,7 +5079,7 @@
|
|
|
4876
5079
|
const [responseBundle, setResponseBundle] = React.useState();
|
|
4877
5080
|
React.useEffect(() => {
|
|
4878
5081
|
if (requestGroup && !startedLoading) {
|
|
4879
|
-
medplum.
|
|
5082
|
+
medplum.executeBatch(buildBatchRequest(requestGroup)).then(setResponseBundle);
|
|
4880
5083
|
setStartedLoading(true);
|
|
4881
5084
|
}
|
|
4882
5085
|
}, [medplum, requestGroup, startedLoading]);
|
|
@@ -5231,6 +5434,165 @@
|
|
|
5231
5434
|
return `/${resource.resourceType}/${resource.id}/_history/${(_a = resource.meta) === null || _a === void 0 ? void 0 : _a.versionId}`;
|
|
5232
5435
|
}
|
|
5233
5436
|
|
|
5437
|
+
/**
|
|
5438
|
+
* Returns a month display string (e.g. "January 2020").
|
|
5439
|
+
* @param date Any date within the month.
|
|
5440
|
+
* @returns The month display string (e.g. "January 2020")
|
|
5441
|
+
*/
|
|
5442
|
+
function getMonthString(date) {
|
|
5443
|
+
return date.toLocaleString('default', { month: 'long' }) + ' ' + date.getFullYear();
|
|
5444
|
+
}
|
|
5445
|
+
function CalendarInput(props) {
|
|
5446
|
+
const [month, setMonth] = React.useState(getStartMonth);
|
|
5447
|
+
function moveMonth(delta) {
|
|
5448
|
+
setMonth((currMonth) => {
|
|
5449
|
+
const prevMonth = new Date(currMonth.getTime());
|
|
5450
|
+
prevMonth.setMonth(currMonth.getMonth() + delta);
|
|
5451
|
+
return prevMonth;
|
|
5452
|
+
});
|
|
5453
|
+
}
|
|
5454
|
+
const grid = React.useMemo(() => buildGrid(month, props.slots), [month, props.slots]);
|
|
5455
|
+
return (React__default["default"].createElement("div", null,
|
|
5456
|
+
React__default["default"].createElement(InputRow, null,
|
|
5457
|
+
React__default["default"].createElement("p", { style: { flex: 1 } }, getMonthString(month)),
|
|
5458
|
+
React__default["default"].createElement("p", null,
|
|
5459
|
+
React__default["default"].createElement(Button, { label: "Previous month", onClick: () => moveMonth(-1) }, "<"),
|
|
5460
|
+
React__default["default"].createElement(Button, { label: "Next month", onClick: () => moveMonth(1) }, ">"))),
|
|
5461
|
+
React__default["default"].createElement("table", { className: "medplum-calendar-table" },
|
|
5462
|
+
React__default["default"].createElement("thead", null,
|
|
5463
|
+
React__default["default"].createElement("tr", null,
|
|
5464
|
+
React__default["default"].createElement("th", null, "SUN"),
|
|
5465
|
+
React__default["default"].createElement("th", null, "MON"),
|
|
5466
|
+
React__default["default"].createElement("th", null, "TUE"),
|
|
5467
|
+
React__default["default"].createElement("th", null, "WED"),
|
|
5468
|
+
React__default["default"].createElement("th", null, "THU"),
|
|
5469
|
+
React__default["default"].createElement("th", null, "FRI"),
|
|
5470
|
+
React__default["default"].createElement("th", null, "SAT"))),
|
|
5471
|
+
React__default["default"].createElement("tbody", null, grid.map((week, weekIndex) => (React__default["default"].createElement("tr", { key: 'week-' + weekIndex }, week.map((day, dayIndex) => (React__default["default"].createElement("td", { key: 'day-' + dayIndex }, day && (React__default["default"].createElement("button", { disabled: !day.available, onClick: () => props.onClick(day.date) }, day.date.getDate()))))))))))));
|
|
5472
|
+
}
|
|
5473
|
+
function getStartMonth() {
|
|
5474
|
+
const result = new Date();
|
|
5475
|
+
result.setDate(1);
|
|
5476
|
+
result.setHours(0, 0, 0, 0);
|
|
5477
|
+
return result;
|
|
5478
|
+
}
|
|
5479
|
+
function buildGrid(startDate, slots) {
|
|
5480
|
+
const d = new Date(startDate.getFullYear(), startDate.getMonth());
|
|
5481
|
+
const grid = [];
|
|
5482
|
+
let row = [];
|
|
5483
|
+
// Fill leading empty days
|
|
5484
|
+
for (let i = 0; i < d.getDay(); i++) {
|
|
5485
|
+
row.push(undefined);
|
|
5486
|
+
}
|
|
5487
|
+
while (d.getMonth() === startDate.getMonth()) {
|
|
5488
|
+
row.push({
|
|
5489
|
+
date: new Date(d.getTime()),
|
|
5490
|
+
// available: isAvailable(d),
|
|
5491
|
+
available: isDayAvailable(d, slots),
|
|
5492
|
+
});
|
|
5493
|
+
if (d.getDay() === 6) {
|
|
5494
|
+
grid.push(row);
|
|
5495
|
+
row = [];
|
|
5496
|
+
}
|
|
5497
|
+
d.setDate(d.getDate() + 1);
|
|
5498
|
+
}
|
|
5499
|
+
// Fill trailing empty days
|
|
5500
|
+
if (d.getDay() !== 0) {
|
|
5501
|
+
for (let i = d.getDay(); i < 7; i++) {
|
|
5502
|
+
row.push(undefined);
|
|
5503
|
+
}
|
|
5504
|
+
grid.push(row);
|
|
5505
|
+
}
|
|
5506
|
+
return grid;
|
|
5507
|
+
}
|
|
5508
|
+
/**
|
|
5509
|
+
* Returns true if the given date is available for booking.
|
|
5510
|
+
* @param day The day to check.
|
|
5511
|
+
* @param slots The list of available slots.
|
|
5512
|
+
* @returns True if there are any available slots for the day.
|
|
5513
|
+
*/
|
|
5514
|
+
function isDayAvailable(day, slots) {
|
|
5515
|
+
// Note that slot start and end time may or may not be in UTC.
|
|
5516
|
+
for (const slot of slots) {
|
|
5517
|
+
const slotStart = new Date(slot.start);
|
|
5518
|
+
if (slotStart.getFullYear() === day.getFullYear() &&
|
|
5519
|
+
slotStart.getMonth() === day.getMonth() &&
|
|
5520
|
+
slotStart.getDate() === day.getDate()) {
|
|
5521
|
+
return true;
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
return false;
|
|
5525
|
+
}
|
|
5526
|
+
|
|
5527
|
+
function Scheduler(props) {
|
|
5528
|
+
var _a;
|
|
5529
|
+
const medplum = useMedplum();
|
|
5530
|
+
const schedule = useResource(props.schedule);
|
|
5531
|
+
const [slots, setSlots] = React.useState();
|
|
5532
|
+
const slotsRef = React.useRef();
|
|
5533
|
+
slotsRef.current = slots;
|
|
5534
|
+
const [date, setDate] = React.useState();
|
|
5535
|
+
const [slot, setSlot] = React.useState();
|
|
5536
|
+
const [info, setInfo] = React.useState();
|
|
5537
|
+
const [form, setForm] = React.useState();
|
|
5538
|
+
React.useEffect(() => {
|
|
5539
|
+
if (schedule) {
|
|
5540
|
+
medplum.search('Slot', 'schedule=' + core.getReferenceString(schedule)).then((bundle) => {
|
|
5541
|
+
setSlots(bundle.entry.map((entry) => entry.resource));
|
|
5542
|
+
});
|
|
5543
|
+
}
|
|
5544
|
+
else {
|
|
5545
|
+
setSlots(undefined);
|
|
5546
|
+
}
|
|
5547
|
+
}, [medplum, schedule]);
|
|
5548
|
+
if (!schedule || !slots) {
|
|
5549
|
+
return null;
|
|
5550
|
+
}
|
|
5551
|
+
const actor = (_a = schedule.actor) === null || _a === void 0 ? void 0 : _a[0];
|
|
5552
|
+
return (React__default["default"].createElement("div", { className: "medplum-calendar-container", "data-testid": "scheduler" },
|
|
5553
|
+
React__default["default"].createElement("div", { className: "medplum-calendar-info-pane" },
|
|
5554
|
+
actor && React__default["default"].createElement(Avatar, { value: actor, size: "large" }),
|
|
5555
|
+
actor && (React__default["default"].createElement("h1", null,
|
|
5556
|
+
React__default["default"].createElement(ResourceName, { value: actor }))),
|
|
5557
|
+
React__default["default"].createElement("p", null, "1 hour"),
|
|
5558
|
+
date && React__default["default"].createElement("p", null, date.toLocaleDateString()),
|
|
5559
|
+
slot && React__default["default"].createElement("p", null, formatTime(new Date(slot.start)))),
|
|
5560
|
+
React__default["default"].createElement("div", { className: "medplum-calendar-selection-pane" },
|
|
5561
|
+
!date && (React__default["default"].createElement("div", null,
|
|
5562
|
+
React__default["default"].createElement("h3", null, "Select date"),
|
|
5563
|
+
React__default["default"].createElement(CalendarInput, { slots: slots, onClick: setDate }))),
|
|
5564
|
+
date && !slot && (React__default["default"].createElement("div", null,
|
|
5565
|
+
React__default["default"].createElement("h3", null, "Select time"),
|
|
5566
|
+
slots.map((s) => {
|
|
5567
|
+
const slotStart = new Date(s.start);
|
|
5568
|
+
return (slotStart.getTime() > date.getTime() &&
|
|
5569
|
+
slotStart.getTime() < date.getTime() + 24 * 3600 * 1000 && (React__default["default"].createElement("div", { key: s.id },
|
|
5570
|
+
React__default["default"].createElement(Button, { style: { width: 150 }, onClick: () => setSlot(s) }, formatTime(slotStart)))));
|
|
5571
|
+
}))),
|
|
5572
|
+
date && slot && !info && (React__default["default"].createElement("div", null,
|
|
5573
|
+
React__default["default"].createElement("h3", null, "Enter your info"),
|
|
5574
|
+
React__default["default"].createElement(FormSection, { title: "Name", htmlFor: "name" },
|
|
5575
|
+
React__default["default"].createElement(Input, { name: "name" })),
|
|
5576
|
+
React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email" },
|
|
5577
|
+
React__default["default"].createElement(Input, { name: "email" })),
|
|
5578
|
+
React__default["default"].createElement(Button, { primary: true, onClick: () => setInfo('info') }, "Next"))),
|
|
5579
|
+
date && slot && info && !form && (React__default["default"].createElement("div", null,
|
|
5580
|
+
React__default["default"].createElement("h3", null, "Custom questions"),
|
|
5581
|
+
React__default["default"].createElement(FormSection, { title: "Question 1", htmlFor: "q1" },
|
|
5582
|
+
React__default["default"].createElement(Input, { name: "q1" })),
|
|
5583
|
+
React__default["default"].createElement(FormSection, { title: "Question 2", htmlFor: "q2" },
|
|
5584
|
+
React__default["default"].createElement(Input, { name: "email" })),
|
|
5585
|
+
React__default["default"].createElement(FormSection, { title: "Question 3", htmlFor: "q3" },
|
|
5586
|
+
React__default["default"].createElement(Input, { name: "email" })),
|
|
5587
|
+
React__default["default"].createElement(Button, { primary: true, onClick: () => setForm('form') }, "Next"))),
|
|
5588
|
+
date && slot && info && form && (React__default["default"].createElement("div", null,
|
|
5589
|
+
React__default["default"].createElement("h3", null, "You're all set!"),
|
|
5590
|
+
React__default["default"].createElement("p", null, "Check your email for a calendar invite."))))));
|
|
5591
|
+
}
|
|
5592
|
+
function formatTime(date) {
|
|
5593
|
+
return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
5594
|
+
}
|
|
5595
|
+
|
|
5234
5596
|
function ServiceRequestTimeline(props) {
|
|
5235
5597
|
return (React__default["default"].createElement(ResourceTimeline, { value: props.serviceRequest, buildSearchRequests: (resource) => ({
|
|
5236
5598
|
resourceType: 'Bundle',
|
|
@@ -5278,62 +5640,6 @@
|
|
|
5278
5640
|
}) }));
|
|
5279
5641
|
}
|
|
5280
5642
|
|
|
5281
|
-
/**
|
|
5282
|
-
* Dynamically creates a script tag for the specified JavaScript file.
|
|
5283
|
-
* @param src The JavaScript file URL.
|
|
5284
|
-
*/
|
|
5285
|
-
function createScriptTag(src, onload) {
|
|
5286
|
-
const head = document.getElementsByTagName('head')[0];
|
|
5287
|
-
const script = document.createElement('script');
|
|
5288
|
-
script.async = true;
|
|
5289
|
-
script.src = src;
|
|
5290
|
-
script.onload = onload || null;
|
|
5291
|
-
head.appendChild(script);
|
|
5292
|
-
}
|
|
5293
|
-
|
|
5294
|
-
function GoogleButton(props) {
|
|
5295
|
-
const medplum = useMedplum();
|
|
5296
|
-
const { handleGoogleCredential } = props;
|
|
5297
|
-
const googleClientId = getGoogleClientId(props.googleClientId);
|
|
5298
|
-
const parentRef = React.useRef(null);
|
|
5299
|
-
const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
|
|
5300
|
-
const [initialized, setInitialized] = React.useState(false);
|
|
5301
|
-
const [buttonRendered, setButtonRendered] = React.useState(false);
|
|
5302
|
-
React.useEffect(() => {
|
|
5303
|
-
if (typeof google === 'undefined') {
|
|
5304
|
-
createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
|
|
5305
|
-
return;
|
|
5306
|
-
}
|
|
5307
|
-
if (!initialized) {
|
|
5308
|
-
google.accounts.id.initialize({
|
|
5309
|
-
client_id: googleClientId,
|
|
5310
|
-
callback: handleGoogleCredential,
|
|
5311
|
-
});
|
|
5312
|
-
setInitialized(true);
|
|
5313
|
-
}
|
|
5314
|
-
if (parentRef.current && !buttonRendered) {
|
|
5315
|
-
google.accounts.id.renderButton(parentRef.current, {});
|
|
5316
|
-
setButtonRendered(true);
|
|
5317
|
-
}
|
|
5318
|
-
}, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
|
|
5319
|
-
if (!googleClientId) {
|
|
5320
|
-
return null;
|
|
5321
|
-
}
|
|
5322
|
-
return React__default["default"].createElement("div", { ref: parentRef });
|
|
5323
|
-
}
|
|
5324
|
-
function getGoogleClientId(clientId) {
|
|
5325
|
-
var _a, _b;
|
|
5326
|
-
if (clientId) {
|
|
5327
|
-
return clientId;
|
|
5328
|
-
}
|
|
5329
|
-
const origin = window.location.protocol + '//' + window.location.host;
|
|
5330
|
-
const authorizedOrigins = (_b = (_a = "http://localhost:3000,http://localhost:6006,https://app.medplum.com,https://docs.medplum.com") === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
|
|
5331
|
-
if (authorizedOrigins.includes(origin)) {
|
|
5332
|
-
return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
|
|
5333
|
-
}
|
|
5334
|
-
return undefined;
|
|
5335
|
-
}
|
|
5336
|
-
|
|
5337
5643
|
function SignInForm(props) {
|
|
5338
5644
|
const medplum = useMedplum();
|
|
5339
5645
|
const [login, setLogin] = React.useState(undefined);
|
|
@@ -5375,6 +5681,7 @@
|
|
|
5375
5681
|
}
|
|
5376
5682
|
function AuthenticationForm(props) {
|
|
5377
5683
|
const medplum = useMedplum();
|
|
5684
|
+
const googleClientId = getGoogleClientId(props.googleClientId);
|
|
5378
5685
|
const [outcome, setOutcome] = React.useState();
|
|
5379
5686
|
const issues = getIssuesForExpression(outcome, undefined);
|
|
5380
5687
|
return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
|
|
@@ -5395,6 +5702,21 @@
|
|
|
5395
5702
|
var _a, _b;
|
|
5396
5703
|
return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
|
|
5397
5704
|
}))),
|
|
5705
|
+
googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
5706
|
+
React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
|
|
5707
|
+
React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => {
|
|
5708
|
+
medplum
|
|
5709
|
+
.startGoogleLogin({
|
|
5710
|
+
clientId: props.clientId,
|
|
5711
|
+
scope: props.scope,
|
|
5712
|
+
nonce: props.nonce,
|
|
5713
|
+
googleClientId: response.clientId,
|
|
5714
|
+
googleCredential: response.credential,
|
|
5715
|
+
})
|
|
5716
|
+
.then(props.handleAuthResponse)
|
|
5717
|
+
.catch(setOutcome);
|
|
5718
|
+
} })),
|
|
5719
|
+
React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
|
|
5398
5720
|
React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
|
|
5399
5721
|
React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", required: true, autoFocus: true, outcome: outcome })),
|
|
5400
5722
|
React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
|
|
@@ -5407,20 +5729,7 @@
|
|
|
5407
5729
|
React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
|
|
5408
5730
|
React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
|
|
5409
5731
|
React__default["default"].createElement("div", null,
|
|
5410
|
-
React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Sign in")))
|
|
5411
|
-
React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
|
|
5412
|
-
React__default["default"].createElement(GoogleButton, { googleClientId: props.googleClientId, handleGoogleCredential: (response) => {
|
|
5413
|
-
medplum
|
|
5414
|
-
.startGoogleLogin({
|
|
5415
|
-
clientId: props.clientId,
|
|
5416
|
-
scope: props.scope,
|
|
5417
|
-
nonce: props.nonce,
|
|
5418
|
-
googleClientId: response.clientId,
|
|
5419
|
-
googleCredential: response.credential,
|
|
5420
|
-
})
|
|
5421
|
-
.then(props.handleAuthResponse)
|
|
5422
|
-
.catch(setOutcome);
|
|
5423
|
-
} }))));
|
|
5732
|
+
React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Sign in")))));
|
|
5424
5733
|
}
|
|
5425
5734
|
function ProfileForm(props) {
|
|
5426
5735
|
const medplum = useMedplum();
|
|
@@ -5545,6 +5854,7 @@
|
|
|
5545
5854
|
exports.RangeDisplay = RangeDisplay;
|
|
5546
5855
|
exports.RangeInput = RangeInput;
|
|
5547
5856
|
exports.ReferenceInput = ReferenceInput;
|
|
5857
|
+
exports.RegisterForm = RegisterForm;
|
|
5548
5858
|
exports.RequestGroupDisplay = RequestGroupDisplay;
|
|
5549
5859
|
exports.ResourceArrayDisplay = ResourceArrayDisplay;
|
|
5550
5860
|
exports.ResourceArrayInput = ResourceArrayInput;
|
|
@@ -5559,6 +5869,7 @@
|
|
|
5559
5869
|
exports.ResourcePropertyInput = ResourcePropertyInput;
|
|
5560
5870
|
exports.ResourceTable = ResourceTable;
|
|
5561
5871
|
exports.ResourceTimeline = ResourceTimeline;
|
|
5872
|
+
exports.Scheduler = Scheduler;
|
|
5562
5873
|
exports.Scrollable = Scrollable;
|
|
5563
5874
|
exports.SearchChangeEvent = SearchChangeEvent;
|
|
5564
5875
|
exports.SearchClickEvent = SearchClickEvent;
|
|
@@ -5602,11 +5913,13 @@
|
|
|
5602
5913
|
exports.deleteFilter = deleteFilter;
|
|
5603
5914
|
exports.formatRangeString = formatRangeString;
|
|
5604
5915
|
exports.getOpString = getOpString;
|
|
5916
|
+
exports.getRecaptcha = getRecaptcha;
|
|
5605
5917
|
exports.getSearchOperators = getSearchOperators;
|
|
5606
5918
|
exports.getSortField = getSortField;
|
|
5607
5919
|
exports.getTimeString = getTimeString;
|
|
5608
5920
|
exports.getValueAndType = getValueAndType;
|
|
5609
5921
|
exports.hasFilterOnField = hasFilterOnField;
|
|
5922
|
+
exports.initRecaptcha = initRecaptcha;
|
|
5610
5923
|
exports.isChoiceQuestion = isChoiceQuestion;
|
|
5611
5924
|
exports.isSortDescending = isSortDescending;
|
|
5612
5925
|
exports.movePage = movePage;
|