@medplum/react 2.0.15 → 2.0.17
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.cjs +1583 -1100
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/esm/AppShell/AppShell.mjs +37 -0
- package/dist/esm/AppShell/AppShell.mjs.map +1 -0
- package/dist/esm/AppShell/Header.mjs +88 -0
- package/dist/esm/AppShell/Header.mjs.map +1 -0
- package/dist/esm/AppShell/HeaderSearchInput.mjs +232 -0
- package/dist/esm/AppShell/HeaderSearchInput.mjs.map +1 -0
- package/dist/esm/AppShell/Navbar.mjs +113 -0
- package/dist/esm/AppShell/Navbar.mjs.map +1 -0
- package/dist/esm/AsyncAutocomplete/AsyncAutocomplete.mjs +6 -5
- package/dist/esm/AsyncAutocomplete/AsyncAutocomplete.mjs.map +1 -1
- package/dist/esm/CodeInput/CodeInput.mjs +1 -1
- package/dist/esm/CodeInput/CodeInput.mjs.map +1 -1
- package/dist/esm/GoogleButton/GoogleButton.mjs +2 -2
- package/dist/esm/GoogleButton/GoogleButton.mjs.map +1 -1
- package/dist/esm/Loading/Loading.mjs +10 -0
- package/dist/esm/Loading/Loading.mjs.map +1 -0
- package/dist/esm/MedplumLink/MedplumLink.mjs +1 -1
- package/dist/esm/MedplumLink/MedplumLink.mjs.map +1 -1
- package/dist/esm/SearchControl/SearchControl.mjs +3 -3
- package/dist/esm/SearchControl/SearchControl.mjs.map +1 -1
- package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs +2 -2
- package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs.map +1 -1
- package/dist/esm/auth/NewUserForm.mjs.map +1 -1
- package/dist/esm/auth/RegisterForm.mjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/index.mjs +7 -3
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAdjustmentsHorizontal.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAdjustmentsHorizontal.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleach.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleach.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleachOff.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleachOff.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBoxMultiple.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBoxMultiple.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBracketsContain.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBracketsContain.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucket.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucket.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucketOff.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucketOff.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCalendar.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCalendar.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheckbox.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheckbox.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs +12 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs.map +1 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCirclePlus.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCirclePlus.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCloudUpload.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCloudUpload.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCurrencyDollar.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCurrencyDollar.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconDots.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconDots.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEdit.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEdit.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqual.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqual.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqualNot.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqualNot.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFileAlert.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFileAlert.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconListDetails.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconListDetails.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs +19 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs.map +1 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathLower.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathLower.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMessage.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMessage.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPin.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPin.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPinnedOff.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPinnedOff.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs +13 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs.map +1 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortAscending.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortAscending.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortDescending.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortDescending.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSquare.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSquare.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs +19 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs.map +1 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTrash.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTrash.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs.map +1 -1
- package/dist/types/AddressDisplay/AddressDisplay.d.ts +0 -1
- package/dist/types/AddressInput/AddressInput.d.ts +0 -1
- package/dist/types/AnnotationInput/AnnotationInput.d.ts +0 -1
- package/dist/types/AppShell/AppShell.d.ts +9 -0
- package/dist/types/AppShell/Header.d.ts +8 -0
- package/dist/types/AppShell/HeaderSearchInput.d.ts +3 -0
- package/dist/types/AppShell/Navbar.d.ts +14 -0
- package/dist/types/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -1
- package/dist/types/AttachmentArrayDisplay/AttachmentArrayDisplay.d.ts +0 -1
- package/dist/types/AttachmentArrayInput/AttachmentArrayInput.d.ts +0 -1
- package/dist/types/AttachmentDisplay/AttachmentDisplay.d.ts +0 -1
- package/dist/types/AttachmentInput/AttachmentInput.d.ts +0 -1
- package/dist/types/BackboneElementDisplay/BackboneElementDisplay.d.ts +0 -1
- package/dist/types/BackboneElementInput/BackboneElementInput.d.ts +0 -1
- package/dist/types/CalendarInput/CalendarInput.d.ts +0 -1
- package/dist/types/CodeInput/CodeInput.d.ts +4 -1
- package/dist/types/CodeableConceptDisplay/CodeableConceptDisplay.d.ts +0 -1
- package/dist/types/CodeableConceptInput/CodeableConceptInput.d.ts +0 -1
- package/dist/types/CodingDisplay/CodingDisplay.d.ts +0 -1
- package/dist/types/CodingInput/CodingInput.d.ts +0 -1
- package/dist/types/ContactDetailDisplay/ContactDetailDisplay.d.ts +0 -1
- package/dist/types/ContactDetailInput/ContactDetailInput.d.ts +0 -1
- package/dist/types/ContactPointDisplay/ContactPointDisplay.d.ts +0 -1
- package/dist/types/ContactPointInput/ContactPointInput.d.ts +0 -1
- package/dist/types/Container/Container.d.ts +0 -1
- package/dist/types/DateTimeInput/DateTimeInput.d.ts +0 -1
- package/dist/types/DefaultResourceTimeline/DefaultResourceTimeline.d.ts +0 -1
- package/dist/types/DiagnosticReportDisplay/DiagnosticReportDisplay.d.ts +0 -1
- package/dist/types/Document/Document.d.ts +0 -1
- package/dist/types/EncounterTimeline/EncounterTimeline.d.ts +0 -1
- package/dist/types/ExtensionInput/ExtensionInput.d.ts +0 -1
- package/dist/types/FhirPathDisplay/FhirPathDisplay.d.ts +0 -1
- package/dist/types/GoogleButton/GoogleButton.d.ts +0 -1
- package/dist/types/HumanNameDisplay/HumanNameDisplay.d.ts +0 -1
- package/dist/types/HumanNameInput/HumanNameInput.d.ts +0 -1
- package/dist/types/IdentifierDisplay/IdentifierDisplay.d.ts +0 -1
- package/dist/types/IdentifierInput/IdentifierInput.d.ts +0 -1
- package/dist/types/Loading/Loading.d.ts +1 -0
- package/dist/types/Logo/Logo.d.ts +0 -1
- package/dist/types/MedplumLink/MedplumLink.d.ts +1 -1
- package/dist/types/MoneyDisplay/MoneyDisplay.d.ts +0 -1
- package/dist/types/MoneyInput/MoneyInput.d.ts +0 -1
- package/dist/types/NoteDisplay/NoteDisplay.d.ts +0 -1
- package/dist/types/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +0 -1
- package/dist/types/Panel/Panel.d.ts +0 -1
- package/dist/types/PatientTimeline/PatientTimeline.d.ts +0 -1
- package/dist/types/PeriodInput/PeriodInput.d.ts +0 -1
- package/dist/types/PlanDefinitionBuilder/PlanDefinitionBuilder.d.ts +0 -1
- package/dist/types/QuantityDisplay/QuantityDisplay.d.ts +0 -1
- package/dist/types/QuantityInput/QuantityInput.d.ts +0 -1
- package/dist/types/QuestionnaireBuilder/QuestionnaireBuilder.d.ts +0 -1
- package/dist/types/QuestionnaireForm/QuestionnaireForm.d.ts +0 -1
- package/dist/types/RangeDisplay/RangeDisplay.d.ts +0 -1
- package/dist/types/RangeInput/RangeInput.d.ts +0 -1
- package/dist/types/RatioDisplay/RatioDisplay.d.ts +0 -1
- package/dist/types/RatioInput/RatioInput.d.ts +0 -1
- package/dist/types/ReferenceDisplay/ReferenceDisplay.d.ts +0 -1
- package/dist/types/ReferenceInput/ReferenceInput.d.ts +0 -1
- package/dist/types/ReferenceRangeEditor/ReferenceRangeEditor.d.ts +0 -1
- package/dist/types/RequestGroupDisplay/RequestGroupDisplay.d.ts +0 -1
- package/dist/types/ResourceArrayDisplay/ResourceArrayDisplay.d.ts +0 -1
- package/dist/types/ResourceArrayInput/ResourceArrayInput.d.ts +0 -1
- package/dist/types/ResourceAvatar/ResourceAvatar.d.ts +0 -1
- package/dist/types/ResourceBadge/ResourceBadge.d.ts +0 -1
- package/dist/types/ResourceBlame/ResourceBlame.d.ts +0 -1
- package/dist/types/ResourceDiff/ResourceDiff.d.ts +0 -1
- package/dist/types/ResourceDiffTable/ResourceDiffTable.d.ts +0 -1
- package/dist/types/ResourceForm/ResourceForm.d.ts +0 -1
- package/dist/types/ResourceHistoryTable/ResourceHistoryTable.d.ts +0 -1
- package/dist/types/ResourceInput/ResourceInput.d.ts +0 -1
- package/dist/types/ResourceName/ResourceName.d.ts +0 -1
- package/dist/types/ResourcePropertyDisplay/ResourcePropertyDisplay.d.ts +0 -1
- package/dist/types/ResourcePropertyInput/ResourcePropertyInput.d.ts +0 -1
- package/dist/types/ResourceTable/ResourceTable.d.ts +0 -1
- package/dist/types/ResourceTimeline/ResourceTimeline.d.ts +0 -1
- package/dist/types/Scheduler/Scheduler.d.ts +0 -1
- package/dist/types/SearchControl/SearchUtils.d.ts +0 -1
- package/dist/types/SearchExportDialog/SearchExportDialog.d.ts +0 -1
- package/dist/types/SearchFieldEditor/SearchFieldEditor.d.ts +0 -1
- package/dist/types/SearchFilterEditor/SearchFilterEditor.d.ts +0 -1
- package/dist/types/SearchFilterValueDialog/SearchFilterValueDialog.d.ts +0 -1
- package/dist/types/SearchFilterValueDisplay/SearchFilterValueDisplay.d.ts +0 -1
- package/dist/types/SearchFilterValueInput/SearchFilterValueInput.d.ts +0 -1
- package/dist/types/SearchPopupMenu/SearchPopupMenu.d.ts +0 -1
- package/dist/types/ServiceRequestTimeline/ServiceRequestTimeline.d.ts +0 -1
- package/dist/types/StatusBadge/StatusBadge.d.ts +0 -1
- package/dist/types/TimingInput/TimingInput.d.ts +0 -1
- package/dist/types/ValueSetAutocomplete/ValueSetAutocomplete.d.ts +2 -1
- package/dist/types/auth/ChooseProfileForm.d.ts +0 -1
- package/dist/types/auth/ChooseScopeForm.d.ts +0 -1
- package/dist/types/auth/MfaForm.d.ts +0 -1
- package/dist/types/auth/NewProjectForm.d.ts +0 -1
- package/dist/types/auth/NewUserForm.d.ts +1 -1
- package/dist/types/auth/RegisterForm.d.ts +1 -1
- package/dist/types/index.d.ts +7 -3
- package/package.json +17 -17
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@medplum/core'), require('react'), require('@mantine/core'), require('prop-types'), require('@mantine/notifications')) :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', '@medplum/core', 'react', '@mantine/core', 'prop-types', '@mantine/notifications'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.medplum = global.medplum || {}, global.medplum.react = {}), global.medplum.core, global.React, global.mantine.core, global.PropTypes, global.mantine.notifications));
|
|
5
|
-
})(this, (function (exports, core, React, core$1, PropTypes, notifications) { 'use strict';
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@medplum/core'), require('react'), require('@mantine/core'), require('prop-types'), require('react-router-dom'), require('@mantine/notifications')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', '@medplum/core', 'react', '@mantine/core', 'prop-types', 'react-router-dom', '@mantine/notifications'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.medplum = global.medplum || {}, global.medplum.react = {}), global.medplum.core, global.React, global.mantine.core, global.PropTypes, global.ReactRouterDOM, global.mantine.notifications));
|
|
5
|
+
})(this, (function (exports, core, React, core$1, PropTypes, reactRouterDom, notifications) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function AddressDisplay(props) {
|
|
8
8
|
const address = props.value;
|
|
@@ -154,168 +154,7 @@
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
158
|
-
* Prevents default behavior.
|
|
159
|
-
* Stops event propagation.
|
|
160
|
-
* @param e The event.
|
|
161
|
-
*/
|
|
162
|
-
function killEvent(e) {
|
|
163
|
-
e.preventDefault();
|
|
164
|
-
e.stopPropagation();
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Returns true if the element is a checkbox or a table cell containing a checkbox.
|
|
168
|
-
* Table cells containing checkboxes are commonly accidentally clicked.
|
|
169
|
-
* @param el The HTML DOM element.
|
|
170
|
-
* @returns True if the element is a checkbox or a table cell containing a checkbox.
|
|
171
|
-
*/
|
|
172
|
-
function isCheckboxCell(el) {
|
|
173
|
-
if (isCheckboxElement(el)) {
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
if (el instanceof HTMLTableCellElement) {
|
|
177
|
-
const children = el.children;
|
|
178
|
-
if (children.length === 1 && isCheckboxElement(children[0])) {
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
function isCheckboxElement(el) {
|
|
185
|
-
return el instanceof HTMLInputElement && el.type === 'checkbox';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function AsyncAutocomplete(props) {
|
|
189
|
-
const { defaultValue, toKey, toOption, loadOptions, onChange, onCreate, ...rest } = props;
|
|
190
|
-
const defaultItems = toDefaultItems(defaultValue);
|
|
191
|
-
const inputRef = React.useRef(null);
|
|
192
|
-
const [lastValue, setLastValue] = React.useState(undefined);
|
|
193
|
-
const [timer, setTimer] = React.useState();
|
|
194
|
-
const [abortController, setAbortController] = React.useState();
|
|
195
|
-
const [autoSubmit, setAutoSubmit] = React.useState();
|
|
196
|
-
const [options, setOptions] = React.useState(defaultItems?.map(toOption));
|
|
197
|
-
const lastValueRef = React.useRef();
|
|
198
|
-
lastValueRef.current = lastValue;
|
|
199
|
-
const timerRef = React.useRef();
|
|
200
|
-
timerRef.current = timer;
|
|
201
|
-
const abortControllerRef = React.useRef();
|
|
202
|
-
abortControllerRef.current = abortController;
|
|
203
|
-
const autoSubmitRef = React.useRef();
|
|
204
|
-
autoSubmitRef.current = autoSubmit;
|
|
205
|
-
const optionsRef = React.useRef();
|
|
206
|
-
optionsRef.current = options;
|
|
207
|
-
const handleTimer = React.useCallback(() => {
|
|
208
|
-
setTimer(undefined);
|
|
209
|
-
const value = inputRef.current?.value?.trim() || '';
|
|
210
|
-
if (value === lastValueRef.current) {
|
|
211
|
-
// Nothing has changed, move on
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
setLastValue(value);
|
|
215
|
-
const newAbortController = new AbortController();
|
|
216
|
-
setAbortController(newAbortController);
|
|
217
|
-
loadOptions(value, newAbortController.signal)
|
|
218
|
-
.then((newValues) => {
|
|
219
|
-
if (!newAbortController.signal.aborted) {
|
|
220
|
-
setOptions(newValues.map(toOption));
|
|
221
|
-
setAbortController(undefined);
|
|
222
|
-
if (autoSubmitRef.current) {
|
|
223
|
-
if (newValues.length > 0) {
|
|
224
|
-
onChange(newValues.slice(0, 1));
|
|
225
|
-
}
|
|
226
|
-
setAutoSubmit(false);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
.catch(console.log);
|
|
231
|
-
}, [loadOptions, onChange, toOption]);
|
|
232
|
-
const handleSearchChange = React.useCallback(() => {
|
|
233
|
-
if (abortControllerRef.current) {
|
|
234
|
-
abortControllerRef.current.abort();
|
|
235
|
-
setAbortController(undefined);
|
|
236
|
-
}
|
|
237
|
-
if (timerRef.current !== undefined) {
|
|
238
|
-
window.clearTimeout(timerRef.current);
|
|
239
|
-
}
|
|
240
|
-
const newTimer = window.setTimeout(() => handleTimer(), 100);
|
|
241
|
-
setTimer(newTimer);
|
|
242
|
-
}, [handleTimer]);
|
|
243
|
-
const handleChange = React.useCallback((values) => {
|
|
244
|
-
const result = [];
|
|
245
|
-
for (const value of values) {
|
|
246
|
-
let item = optionsRef.current?.find((option) => option.value === value)?.resource;
|
|
247
|
-
if (!item) {
|
|
248
|
-
item = onCreate(value);
|
|
249
|
-
}
|
|
250
|
-
result.push(item);
|
|
251
|
-
}
|
|
252
|
-
onChange(result);
|
|
253
|
-
}, [onChange, onCreate]);
|
|
254
|
-
const handleKeyDown = React.useCallback((e) => {
|
|
255
|
-
if (e.key === 'Enter') {
|
|
256
|
-
if (!timerRef.current && !abortControllerRef.current) {
|
|
257
|
-
killEvent(e);
|
|
258
|
-
if (optionsRef.current && optionsRef.current.length > 0) {
|
|
259
|
-
setOptions(optionsRef.current.slice(0, 1));
|
|
260
|
-
handleChange([optionsRef.current[0].value]);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
// The user pressed enter, but we don't have results yet.
|
|
265
|
-
// We need to wait for the results to come in.
|
|
266
|
-
setAutoSubmit(true);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}, [handleChange]);
|
|
270
|
-
const handleCreate = React.useCallback((input) => {
|
|
271
|
-
const option = toOption(onCreate(input));
|
|
272
|
-
setOptions([...optionsRef.current, option]);
|
|
273
|
-
return option;
|
|
274
|
-
}, [onCreate, setOptions, toOption]);
|
|
275
|
-
const handleFilter = React.useCallback((_value, selected) => !selected, []);
|
|
276
|
-
React.useEffect(() => {
|
|
277
|
-
return () => {
|
|
278
|
-
if (abortControllerRef.current) {
|
|
279
|
-
abortControllerRef.current.abort();
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
}, []);
|
|
283
|
-
return (React.createElement(core$1.MultiSelect, { ...rest, ref: inputRef, defaultValue: defaultItems.map(toKey), searchable: true, onKeyDown: handleKeyDown, onSearchChange: handleSearchChange, data: options, onFocus: handleTimer, onChange: handleChange, onCreate: handleCreate, rightSectionWidth: 40, rightSection: abortController ? React.createElement(core$1.Loader, { size: 16 }) : null, filter: handleFilter }));
|
|
284
|
-
}
|
|
285
|
-
function toDefaultItems(defaultValue) {
|
|
286
|
-
if (!defaultValue) {
|
|
287
|
-
return [];
|
|
288
|
-
}
|
|
289
|
-
if (Array.isArray(defaultValue)) {
|
|
290
|
-
return defaultValue;
|
|
291
|
-
}
|
|
292
|
-
return [defaultValue];
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function AttachmentDisplay(props) {
|
|
296
|
-
const value = props.value;
|
|
297
|
-
const { contentType, url, title } = value ?? {};
|
|
298
|
-
if (!url) {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
return (React.createElement("div", { "data-testid": "attachment-display" },
|
|
302
|
-
contentType?.startsWith('image/') && (React.createElement("img", { "data-testid": "attachment-image", style: { maxWidth: props.maxWidth }, src: url, alt: value?.title })),
|
|
303
|
-
contentType?.startsWith('video/') && (React.createElement("video", { "data-testid": "attachment-video", style: { maxWidth: props.maxWidth }, controls: true },
|
|
304
|
-
React.createElement("source", { type: contentType, src: url }))),
|
|
305
|
-
contentType === 'application/pdf' && !title?.endsWith('.pdf') && (React.createElement("div", { "data-testid": "attachment-pdf", style: { maxWidth: props.maxWidth, minHeight: 400 } },
|
|
306
|
-
React.createElement("iframe", { width: "100%", height: "400", src: url + '#navpanes=0', allowFullScreen: true, frameBorder: 0, seamless: true }))),
|
|
307
|
-
React.createElement("div", { "data-testid": "download-link", style: { padding: '2px 16px 16px 16px' } },
|
|
308
|
-
React.createElement(core$1.Anchor, { href: value?.url, "data-testid": "attachment-details", target: "_blank", rel: "noopener noreferrer" }, value?.title || 'Download'))));
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function AttachmentArrayDisplay(props) {
|
|
312
|
-
return (React.createElement("div", null, props.values &&
|
|
313
|
-
props.values.map((v, index) => (React.createElement("div", { key: 'attatchment-' + index },
|
|
314
|
-
React.createElement(AttachmentDisplay, { value: v, maxWidth: props.maxWidth }))))));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* @tabler/icons-react v2.14.0 - MIT
|
|
157
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
319
158
|
*/
|
|
320
159
|
|
|
321
160
|
var defaultAttributes = {
|
|
@@ -331,7 +170,7 @@
|
|
|
331
170
|
};
|
|
332
171
|
|
|
333
172
|
/**
|
|
334
|
-
* @tabler/icons-react v2.
|
|
173
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
335
174
|
*/
|
|
336
175
|
|
|
337
176
|
var __defProp = Object.defineProperty;
|
|
@@ -394,7 +233,7 @@
|
|
|
394
233
|
};
|
|
395
234
|
|
|
396
235
|
/**
|
|
397
|
-
* @tabler/icons-react v2.
|
|
236
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
398
237
|
*/
|
|
399
238
|
|
|
400
239
|
var IconAdjustmentsHorizontal = createReactComponent(
|
|
@@ -414,7 +253,7 @@
|
|
|
414
253
|
);
|
|
415
254
|
|
|
416
255
|
/**
|
|
417
|
-
* @tabler/icons-react v2.
|
|
256
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
418
257
|
*/
|
|
419
258
|
|
|
420
259
|
var IconAlertCircle = createReactComponent("alert-circle", "IconAlertCircle", [
|
|
@@ -424,7 +263,7 @@
|
|
|
424
263
|
]);
|
|
425
264
|
|
|
426
265
|
/**
|
|
427
|
-
* @tabler/icons-react v2.
|
|
266
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
428
267
|
*/
|
|
429
268
|
|
|
430
269
|
var IconBleachOff = createReactComponent("bleach-off", "IconBleachOff", [
|
|
@@ -439,7 +278,7 @@
|
|
|
439
278
|
]);
|
|
440
279
|
|
|
441
280
|
/**
|
|
442
|
-
* @tabler/icons-react v2.
|
|
281
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
443
282
|
*/
|
|
444
283
|
|
|
445
284
|
var IconBleach = createReactComponent("bleach", "IconBleach", [
|
|
@@ -453,7 +292,7 @@
|
|
|
453
292
|
]);
|
|
454
293
|
|
|
455
294
|
/**
|
|
456
|
-
* @tabler/icons-react v2.
|
|
295
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
457
296
|
*/
|
|
458
297
|
|
|
459
298
|
var IconBoxMultiple = createReactComponent("box-multiple", "IconBoxMultiple", [
|
|
@@ -474,7 +313,7 @@
|
|
|
474
313
|
]);
|
|
475
314
|
|
|
476
315
|
/**
|
|
477
|
-
* @tabler/icons-react v2.
|
|
316
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
478
317
|
*/
|
|
479
318
|
|
|
480
319
|
var IconBracketsContain = createReactComponent("brackets-contain", "IconBracketsContain", [
|
|
@@ -486,7 +325,7 @@
|
|
|
486
325
|
]);
|
|
487
326
|
|
|
488
327
|
/**
|
|
489
|
-
* @tabler/icons-react v2.
|
|
328
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
490
329
|
*/
|
|
491
330
|
|
|
492
331
|
var IconBucketOff = createReactComponent("bucket-off", "IconBucketOff", [
|
|
@@ -508,7 +347,7 @@
|
|
|
508
347
|
]);
|
|
509
348
|
|
|
510
349
|
/**
|
|
511
|
-
* @tabler/icons-react v2.
|
|
350
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
512
351
|
*/
|
|
513
352
|
|
|
514
353
|
var IconBucket = createReactComponent("bucket", "IconBucket", [
|
|
@@ -523,7 +362,7 @@
|
|
|
523
362
|
]);
|
|
524
363
|
|
|
525
364
|
/**
|
|
526
|
-
* @tabler/icons-react v2.
|
|
365
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
527
366
|
*/
|
|
528
367
|
|
|
529
368
|
var IconCalendar = createReactComponent("calendar", "IconCalendar", [
|
|
@@ -542,7 +381,7 @@
|
|
|
542
381
|
]);
|
|
543
382
|
|
|
544
383
|
/**
|
|
545
|
-
* @tabler/icons-react v2.
|
|
384
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
546
385
|
*/
|
|
547
386
|
|
|
548
387
|
var IconCheck = createReactComponent("check", "IconCheck", [
|
|
@@ -550,7 +389,7 @@
|
|
|
550
389
|
]);
|
|
551
390
|
|
|
552
391
|
/**
|
|
553
|
-
* @tabler/icons-react v2.
|
|
392
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
554
393
|
*/
|
|
555
394
|
|
|
556
395
|
var IconCheckbox = createReactComponent("checkbox", "IconCheckbox", [
|
|
@@ -565,7 +404,15 @@
|
|
|
565
404
|
]);
|
|
566
405
|
|
|
567
406
|
/**
|
|
568
|
-
* @tabler/icons-react v2.
|
|
407
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
408
|
+
*/
|
|
409
|
+
|
|
410
|
+
var IconChevronDown = createReactComponent("chevron-down", "IconChevronDown", [
|
|
411
|
+
["path", { d: "M6 9l6 6l6 -6", key: "svg-0" }]
|
|
412
|
+
]);
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
569
416
|
*/
|
|
570
417
|
|
|
571
418
|
var IconCircleMinus = createReactComponent("circle-minus", "IconCircleMinus", [
|
|
@@ -574,7 +421,7 @@
|
|
|
574
421
|
]);
|
|
575
422
|
|
|
576
423
|
/**
|
|
577
|
-
* @tabler/icons-react v2.
|
|
424
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
578
425
|
*/
|
|
579
426
|
|
|
580
427
|
var IconCirclePlus = createReactComponent("circle-plus", "IconCirclePlus", [
|
|
@@ -584,7 +431,7 @@
|
|
|
584
431
|
]);
|
|
585
432
|
|
|
586
433
|
/**
|
|
587
|
-
* @tabler/icons-react v2.
|
|
434
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
588
435
|
*/
|
|
589
436
|
|
|
590
437
|
var IconCloudUpload = createReactComponent("cloud-upload", "IconCloudUpload", [
|
|
@@ -600,7 +447,7 @@
|
|
|
600
447
|
]);
|
|
601
448
|
|
|
602
449
|
/**
|
|
603
|
-
* @tabler/icons-react v2.
|
|
450
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
604
451
|
*/
|
|
605
452
|
|
|
606
453
|
var IconColumns = createReactComponent("columns", "IconColumns", [
|
|
@@ -615,7 +462,7 @@
|
|
|
615
462
|
]);
|
|
616
463
|
|
|
617
464
|
/**
|
|
618
|
-
* @tabler/icons-react v2.
|
|
465
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
619
466
|
*/
|
|
620
467
|
|
|
621
468
|
var IconCurrencyDollar = createReactComponent("currency-dollar", "IconCurrencyDollar", [
|
|
@@ -630,7 +477,7 @@
|
|
|
630
477
|
]);
|
|
631
478
|
|
|
632
479
|
/**
|
|
633
|
-
* @tabler/icons-react v2.
|
|
480
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
634
481
|
*/
|
|
635
482
|
|
|
636
483
|
var IconDots = createReactComponent("dots", "IconDots", [
|
|
@@ -640,7 +487,7 @@
|
|
|
640
487
|
]);
|
|
641
488
|
|
|
642
489
|
/**
|
|
643
|
-
* @tabler/icons-react v2.
|
|
490
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
644
491
|
*/
|
|
645
492
|
|
|
646
493
|
var IconEdit = createReactComponent("edit", "IconEdit", [
|
|
@@ -662,7 +509,7 @@
|
|
|
662
509
|
]);
|
|
663
510
|
|
|
664
511
|
/**
|
|
665
|
-
* @tabler/icons-react v2.
|
|
512
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
666
513
|
*/
|
|
667
514
|
|
|
668
515
|
var IconEqualNot = createReactComponent("equal-not", "IconEqualNot", [
|
|
@@ -672,7 +519,7 @@
|
|
|
672
519
|
]);
|
|
673
520
|
|
|
674
521
|
/**
|
|
675
|
-
* @tabler/icons-react v2.
|
|
522
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
676
523
|
*/
|
|
677
524
|
|
|
678
525
|
var IconEqual = createReactComponent("equal", "IconEqual", [
|
|
@@ -681,7 +528,7 @@
|
|
|
681
528
|
]);
|
|
682
529
|
|
|
683
530
|
/**
|
|
684
|
-
* @tabler/icons-react v2.
|
|
531
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
685
532
|
*/
|
|
686
533
|
|
|
687
534
|
var IconFileAlert = createReactComponent("file-alert", "IconFileAlert", [
|
|
@@ -698,7 +545,7 @@
|
|
|
698
545
|
]);
|
|
699
546
|
|
|
700
547
|
/**
|
|
701
|
-
* @tabler/icons-react v2.
|
|
548
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
702
549
|
*/
|
|
703
550
|
|
|
704
551
|
var IconFilePlus = createReactComponent("file-plus", "IconFilePlus", [
|
|
@@ -715,7 +562,7 @@
|
|
|
715
562
|
]);
|
|
716
563
|
|
|
717
564
|
/**
|
|
718
|
-
* @tabler/icons-react v2.
|
|
565
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
719
566
|
*/
|
|
720
567
|
|
|
721
568
|
var IconFilter = createReactComponent("filter", "IconFilter", [
|
|
@@ -729,7 +576,7 @@
|
|
|
729
576
|
]);
|
|
730
577
|
|
|
731
578
|
/**
|
|
732
|
-
* @tabler/icons-react v2.
|
|
579
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
733
580
|
*/
|
|
734
581
|
|
|
735
582
|
var IconListDetails = createReactComponent("list-details", "IconListDetails", [
|
|
@@ -754,7 +601,22 @@
|
|
|
754
601
|
]);
|
|
755
602
|
|
|
756
603
|
/**
|
|
757
|
-
* @tabler/icons-react v2.
|
|
604
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
605
|
+
*/
|
|
606
|
+
|
|
607
|
+
var IconLogout = createReactComponent("logout", "IconLogout", [
|
|
608
|
+
[
|
|
609
|
+
"path",
|
|
610
|
+
{
|
|
611
|
+
d: "M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2",
|
|
612
|
+
key: "svg-0"
|
|
613
|
+
}
|
|
614
|
+
],
|
|
615
|
+
["path", { d: "M7 12h14l-3 -3m0 6l3 -3", key: "svg-1" }]
|
|
616
|
+
]);
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
758
620
|
*/
|
|
759
621
|
|
|
760
622
|
var IconMathGreater = createReactComponent("math-greater", "IconMathGreater", [
|
|
@@ -762,7 +624,7 @@
|
|
|
762
624
|
]);
|
|
763
625
|
|
|
764
626
|
/**
|
|
765
|
-
* @tabler/icons-react v2.
|
|
627
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
766
628
|
*/
|
|
767
629
|
|
|
768
630
|
var IconMathLower = createReactComponent("math-lower", "IconMathLower", [
|
|
@@ -770,7 +632,7 @@
|
|
|
770
632
|
]);
|
|
771
633
|
|
|
772
634
|
/**
|
|
773
|
-
* @tabler/icons-react v2.
|
|
635
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
774
636
|
*/
|
|
775
637
|
|
|
776
638
|
var IconMessage = createReactComponent("message", "IconMessage", [
|
|
@@ -786,7 +648,7 @@
|
|
|
786
648
|
]);
|
|
787
649
|
|
|
788
650
|
/**
|
|
789
|
-
* @tabler/icons-react v2.
|
|
651
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
790
652
|
*/
|
|
791
653
|
|
|
792
654
|
var IconPin = createReactComponent("pin", "IconPin", [
|
|
@@ -802,7 +664,7 @@
|
|
|
802
664
|
]);
|
|
803
665
|
|
|
804
666
|
/**
|
|
805
|
-
* @tabler/icons-react v2.
|
|
667
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
806
668
|
*/
|
|
807
669
|
|
|
808
670
|
var IconPinnedOff = createReactComponent("pinned-off", "IconPinnedOff", [
|
|
@@ -819,7 +681,16 @@
|
|
|
819
681
|
]);
|
|
820
682
|
|
|
821
683
|
/**
|
|
822
|
-
* @tabler/icons-react v2.
|
|
684
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
685
|
+
*/
|
|
686
|
+
|
|
687
|
+
var IconSearch = createReactComponent("search", "IconSearch", [
|
|
688
|
+
["path", { d: "M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0", key: "svg-0" }],
|
|
689
|
+
["path", { d: "M21 21l-6 -6", key: "svg-1" }]
|
|
690
|
+
]);
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
823
694
|
*/
|
|
824
695
|
|
|
825
696
|
var IconSettings = createReactComponent("settings", "IconSettings", [
|
|
@@ -834,7 +705,7 @@
|
|
|
834
705
|
]);
|
|
835
706
|
|
|
836
707
|
/**
|
|
837
|
-
* @tabler/icons-react v2.
|
|
708
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
838
709
|
*/
|
|
839
710
|
|
|
840
711
|
var IconSortAscending = createReactComponent("sort-ascending", "IconSortAscending", [
|
|
@@ -846,7 +717,7 @@
|
|
|
846
717
|
]);
|
|
847
718
|
|
|
848
719
|
/**
|
|
849
|
-
* @tabler/icons-react v2.
|
|
720
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
850
721
|
*/
|
|
851
722
|
|
|
852
723
|
var IconSortDescending = createReactComponent("sort-descending", "IconSortDescending", [
|
|
@@ -858,7 +729,7 @@
|
|
|
858
729
|
]);
|
|
859
730
|
|
|
860
731
|
/**
|
|
861
|
-
* @tabler/icons-react v2.
|
|
732
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
862
733
|
*/
|
|
863
734
|
|
|
864
735
|
var IconSquare = createReactComponent("square", "IconSquare", [
|
|
@@ -872,7 +743,22 @@
|
|
|
872
743
|
]);
|
|
873
744
|
|
|
874
745
|
/**
|
|
875
|
-
* @tabler/icons-react v2.
|
|
746
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
747
|
+
*/
|
|
748
|
+
|
|
749
|
+
var IconSwitchHorizontal = createReactComponent(
|
|
750
|
+
"switch-horizontal",
|
|
751
|
+
"IconSwitchHorizontal",
|
|
752
|
+
[
|
|
753
|
+
["path", { d: "M16 3l4 4l-4 4", key: "svg-0" }],
|
|
754
|
+
["path", { d: "M10 7l10 0", key: "svg-1" }],
|
|
755
|
+
["path", { d: "M8 13l-4 4l4 4", key: "svg-2" }],
|
|
756
|
+
["path", { d: "M4 17l9 0", key: "svg-3" }]
|
|
757
|
+
]
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
876
762
|
*/
|
|
877
763
|
|
|
878
764
|
var IconTableExport = createReactComponent("table-export", "IconTableExport", [
|
|
@@ -890,7 +776,7 @@
|
|
|
890
776
|
]);
|
|
891
777
|
|
|
892
778
|
/**
|
|
893
|
-
* @tabler/icons-react v2.
|
|
779
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
894
780
|
*/
|
|
895
781
|
|
|
896
782
|
var IconTrash = createReactComponent("trash", "IconTrash", [
|
|
@@ -905,7 +791,7 @@
|
|
|
905
791
|
]);
|
|
906
792
|
|
|
907
793
|
/**
|
|
908
|
-
* @tabler/icons-react v2.
|
|
794
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
909
795
|
*/
|
|
910
796
|
|
|
911
797
|
var IconX = createReactComponent("x", "IconX", [
|
|
@@ -913,741 +799,947 @@
|
|
|
913
799
|
["path", { d: "M6 6l12 12", key: "svg-1" }]
|
|
914
800
|
]);
|
|
915
801
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
802
|
+
/**
|
|
803
|
+
* ErrorBoundary is a React component that handles errors in its child components.
|
|
804
|
+
* See: https://reactjs.org/docs/error-boundaries.html
|
|
805
|
+
*/
|
|
806
|
+
class ErrorBoundary extends React.Component {
|
|
807
|
+
constructor(props) {
|
|
808
|
+
super(props);
|
|
809
|
+
this.state = {};
|
|
922
810
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const files = e.target.files;
|
|
926
|
-
if (files) {
|
|
927
|
-
Array.from(files).forEach(processFile);
|
|
928
|
-
}
|
|
811
|
+
static getDerivedStateFromError(error) {
|
|
812
|
+
return { error };
|
|
929
813
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (!file) {
|
|
937
|
-
return;
|
|
938
|
-
}
|
|
939
|
-
const fileName = file.name;
|
|
940
|
-
if (!fileName) {
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
if (props.onUploadStart) {
|
|
944
|
-
props.onUploadStart();
|
|
814
|
+
componentDidCatch(error, errorInfo) {
|
|
815
|
+
console.error('Uncaught error:', error, errorInfo);
|
|
816
|
+
}
|
|
817
|
+
render() {
|
|
818
|
+
if (this.state.error) {
|
|
819
|
+
return (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Something went wrong", color: "red" }, core.normalizeErrorString(this.state.error)));
|
|
945
820
|
}
|
|
946
|
-
|
|
947
|
-
const contentType = file.type || 'application/octet-stream';
|
|
948
|
-
medplum
|
|
949
|
-
.createBinary(file, filename, contentType, props.onUploadProgress)
|
|
950
|
-
.then((binary) => {
|
|
951
|
-
props.onUpload({
|
|
952
|
-
contentType: binary.contentType,
|
|
953
|
-
url: binary.url,
|
|
954
|
-
title: filename,
|
|
955
|
-
});
|
|
956
|
-
})
|
|
957
|
-
.catch((outcome) => {
|
|
958
|
-
alert(outcome?.issue?.[0]?.details?.text);
|
|
959
|
-
});
|
|
821
|
+
return this.props.children;
|
|
960
822
|
}
|
|
961
|
-
return (React.createElement(React.Fragment, null,
|
|
962
|
-
React.createElement("input", { type: "file", "data-testid": "upload-file-input", style: { display: 'none' }, ref: fileInputRef, onChange: (e) => onFileChange(e) }),
|
|
963
|
-
props.children({ onClick })));
|
|
964
823
|
}
|
|
965
824
|
|
|
966
|
-
function
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
valuesRef.current = values;
|
|
970
|
-
function setValuesWrapper(newValues) {
|
|
971
|
-
setValues(newValues);
|
|
972
|
-
if (props.onChange) {
|
|
973
|
-
props.onChange(newValues);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return (React.createElement("table", { style: { width: '100%' } },
|
|
977
|
-
React.createElement("colgroup", null,
|
|
978
|
-
React.createElement("col", { width: "97%" }),
|
|
979
|
-
React.createElement("col", { width: "3%" })),
|
|
980
|
-
React.createElement("tbody", null,
|
|
981
|
-
values.map((v, index) => (React.createElement("tr", { key: `${index}-${values.length}` },
|
|
982
|
-
React.createElement("td", null,
|
|
983
|
-
React.createElement(AttachmentDisplay, { value: v, maxWidth: 200 })),
|
|
984
|
-
React.createElement("td", null,
|
|
985
|
-
React.createElement(core$1.ActionIcon, { title: "Remove", size: "sm", onClick: (e) => {
|
|
986
|
-
killEvent(e);
|
|
987
|
-
const copy = values.slice();
|
|
988
|
-
copy.splice(index, 1);
|
|
989
|
-
setValuesWrapper(copy);
|
|
990
|
-
} },
|
|
991
|
-
React.createElement(IconCircleMinus, null)))))),
|
|
992
|
-
React.createElement("tr", null,
|
|
993
|
-
React.createElement("td", null),
|
|
994
|
-
React.createElement("td", null,
|
|
995
|
-
React.createElement(AttachmentButton, { onUpload: (attachment) => {
|
|
996
|
-
setValuesWrapper([...valuesRef.current, attachment]);
|
|
997
|
-
} }, (props) => (React.createElement(core$1.ActionIcon, { ...props, title: "Add", size: "sm", color: "green" },
|
|
998
|
-
React.createElement(IconCloudUpload, { size: 16 })))))))));
|
|
825
|
+
function Loading() {
|
|
826
|
+
return (React.createElement(core$1.Center, { style: { width: '100%', height: '100vh' } },
|
|
827
|
+
React.createElement(core$1.Loader, null)));
|
|
999
828
|
}
|
|
1000
829
|
|
|
1001
|
-
function
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (props.onChange) {
|
|
1006
|
-
props.onChange(newValue);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
if (value) {
|
|
1010
|
-
return (React.createElement(React.Fragment, null,
|
|
1011
|
-
React.createElement(AttachmentDisplay, { value: value, maxWidth: 200 }),
|
|
1012
|
-
React.createElement(core$1.Button, { onClick: (e) => {
|
|
1013
|
-
killEvent(e);
|
|
1014
|
-
setValueWrapper(undefined);
|
|
1015
|
-
} }, "Remove")));
|
|
830
|
+
function HumanNameDisplay(props) {
|
|
831
|
+
const name = props.value;
|
|
832
|
+
if (!name) {
|
|
833
|
+
return null;
|
|
1016
834
|
}
|
|
1017
|
-
return
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
const useStyles$e = core$1.createStyles(() => ({
|
|
1021
|
-
root: {
|
|
1022
|
-
'@media (max-width: 800px)': {
|
|
1023
|
-
paddingLeft: 4,
|
|
1024
|
-
paddingRight: 4,
|
|
1025
|
-
},
|
|
1026
|
-
},
|
|
1027
|
-
}));
|
|
1028
|
-
function Container(props) {
|
|
1029
|
-
const { children, ...others } = props;
|
|
1030
|
-
const { classes } = useStyles$e();
|
|
1031
|
-
return (React.createElement(core$1.Container, { className: classes.root, ...others }, children));
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
const useStyles$d = core$1.createStyles((theme, { width, fill }) => ({
|
|
1035
|
-
paper: {
|
|
1036
|
-
maxWidth: width,
|
|
1037
|
-
margin: `${theme.spacing.xl} auto`,
|
|
1038
|
-
padding: fill ? 0 : theme.spacing.md,
|
|
1039
|
-
'@media (max-width: 800px)': {
|
|
1040
|
-
padding: fill ? 0 : 8,
|
|
1041
|
-
},
|
|
1042
|
-
'& img': {
|
|
1043
|
-
width: '100%',
|
|
1044
|
-
maxWidth: '100%',
|
|
1045
|
-
},
|
|
1046
|
-
'& video': {
|
|
1047
|
-
width: '100%',
|
|
1048
|
-
maxWidth: '100%',
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
}));
|
|
1052
|
-
const defaultProps$1 = {
|
|
1053
|
-
shadow: 'xs',
|
|
1054
|
-
radius: 'md',
|
|
1055
|
-
withBorder: true,
|
|
1056
|
-
};
|
|
1057
|
-
function Panel(props) {
|
|
1058
|
-
const { className, children, width, fill, unstyled, ...others } = core$1.useComponentDefaultProps('Panel', defaultProps$1, props);
|
|
1059
|
-
const { classes, cx } = useStyles$d({ width, fill }, { name: 'Panel', unstyled });
|
|
1060
|
-
return (React.createElement(core$1.Paper, { className: cx(classes.paper, className), ...others }, children));
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
function Document(props) {
|
|
1064
|
-
const { children, ...others } = props;
|
|
1065
|
-
return (React.createElement(Container, null,
|
|
1066
|
-
React.createElement(Panel, { ...others }, children)));
|
|
835
|
+
return React.createElement(React.Fragment, null, core.formatHumanName(name, props.options));
|
|
1067
836
|
}
|
|
1068
837
|
|
|
1069
838
|
/**
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
839
|
+
* Kills a browser event.
|
|
840
|
+
* Prevents default behavior.
|
|
841
|
+
* Stops event propagation.
|
|
842
|
+
* @param e The event.
|
|
1072
843
|
*/
|
|
1073
|
-
function
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
if (element instanceof HTMLInputElement) {
|
|
1077
|
-
parseInputElement(result, element);
|
|
1078
|
-
}
|
|
1079
|
-
else if (element instanceof HTMLTextAreaElement) {
|
|
1080
|
-
result[element.name] = element.value;
|
|
1081
|
-
}
|
|
1082
|
-
else if (element instanceof HTMLSelectElement) {
|
|
1083
|
-
parseSelectElement(result, element);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
return result;
|
|
844
|
+
function killEvent(e) {
|
|
845
|
+
e.preventDefault();
|
|
846
|
+
e.stopPropagation();
|
|
1087
847
|
}
|
|
1088
848
|
/**
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
* @
|
|
1093
|
-
* @param result The result builder.
|
|
849
|
+
* Returns true if the element is a checkbox or a table cell containing a checkbox.
|
|
850
|
+
* Table cells containing checkboxes are commonly accidentally clicked.
|
|
851
|
+
* @param el The HTML DOM element.
|
|
852
|
+
* @returns True if the element is a checkbox or a table cell containing a checkbox.
|
|
1094
853
|
*/
|
|
1095
|
-
function
|
|
1096
|
-
if (el
|
|
1097
|
-
|
|
1098
|
-
return;
|
|
854
|
+
function isCheckboxCell(el) {
|
|
855
|
+
if (isCheckboxElement(el)) {
|
|
856
|
+
return true;
|
|
1099
857
|
}
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
|
|
858
|
+
if (el instanceof HTMLTableCellElement) {
|
|
859
|
+
const children = el.children;
|
|
860
|
+
if (children.length === 1 && isCheckboxElement(children[0])) {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
1103
863
|
}
|
|
1104
|
-
|
|
864
|
+
return false;
|
|
1105
865
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
* Sets the name/value pair if one is selected.
|
|
1109
|
-
* @param result The result builder.
|
|
1110
|
-
* @param el The select element.
|
|
1111
|
-
*/
|
|
1112
|
-
function parseSelectElement(result, el) {
|
|
1113
|
-
result[el.name] = el.value;
|
|
866
|
+
function isCheckboxElement(el) {
|
|
867
|
+
return el instanceof HTMLInputElement && el.type === 'checkbox';
|
|
1114
868
|
}
|
|
1115
869
|
|
|
1116
|
-
function
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
870
|
+
function MedplumLink(props) {
|
|
871
|
+
const navigate = useMedplumNavigate();
|
|
872
|
+
const { to, suffix, label, onClick, children, ...rest } = props;
|
|
873
|
+
let href = getHref(to);
|
|
874
|
+
if (suffix) {
|
|
875
|
+
href += '/' + suffix;
|
|
876
|
+
}
|
|
877
|
+
return (React.createElement(core$1.Anchor, { href: href, "aria-label": label, onClick: (e) => {
|
|
878
|
+
killEvent(e);
|
|
879
|
+
if (onClick) {
|
|
880
|
+
onClick(e);
|
|
1122
881
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
return (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 491 491", style: { width: props.size, height: props.size } },
|
|
1128
|
-
React.createElement("title", null, "Medplum Logo"),
|
|
1129
|
-
React.createElement("path", { fill: props.fill || '#ad7136', d: "M282 67c6-16 16-29 29-40L289 0c-22 17-37 41-43 68l17 23 19-24z" }),
|
|
1130
|
-
React.createElement("path", { fill: props.fill || '#946af9', d: "M311 63c-17 0-33 4-48 11-16-7-32-11-49-11-87 0-158 96-158 214s71 214 158 214c17 0 33-4 49-11 15 7 31 11 48 11 87 0 158-96 158-214S398 63 311 63z" }),
|
|
1131
|
-
React.createElement("path", { fill: props.fill || '#7857c5', d: "M231 489l-17 2c-87 0-158-96-158-214S127 63 214 63l17 1c-39 12-70 102-70 213s31 201 70 212z" }),
|
|
1132
|
-
React.createElement("path", { fill: props.fill || '#40bc26', d: "M207 220a176 176 0 01-177 43A176 176 0 01251 43l1 5c17 59 2 125-45 172z" }),
|
|
1133
|
-
React.createElement("path", { fill: props.fill || '#33961e', d: "M252 48A421 421 0 0057 270l-27-7A176 176 0 01251 43l1 5z" })));
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
function getErrorsForInput(outcome, expression) {
|
|
1137
|
-
return outcome?.issue
|
|
1138
|
-
?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression))
|
|
1139
|
-
?.map((issue) => issue.details?.text)
|
|
1140
|
-
?.join('\n');
|
|
1141
|
-
}
|
|
1142
|
-
function getIssuesForExpression(outcome, expression) {
|
|
1143
|
-
return outcome?.issue?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression));
|
|
882
|
+
else if (to) {
|
|
883
|
+
navigate(href);
|
|
884
|
+
}
|
|
885
|
+
}, ...rest }, children));
|
|
1144
886
|
}
|
|
1145
|
-
function
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
887
|
+
function getHref(to) {
|
|
888
|
+
if (to) {
|
|
889
|
+
if (typeof to === 'string') {
|
|
890
|
+
return getStringHref(to);
|
|
891
|
+
}
|
|
892
|
+
else if (core.isResource(to)) {
|
|
893
|
+
return getResourceHref(to);
|
|
894
|
+
}
|
|
895
|
+
else if (core.isReference(to)) {
|
|
896
|
+
return getReferenceHref(to);
|
|
897
|
+
}
|
|
1156
898
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
899
|
+
return '#';
|
|
900
|
+
}
|
|
901
|
+
function getStringHref(to) {
|
|
902
|
+
if (to.startsWith('http://') || to.startsWith('https://') || to.startsWith('/')) {
|
|
903
|
+
return to;
|
|
1160
904
|
}
|
|
1161
|
-
return
|
|
905
|
+
return '/' + to;
|
|
1162
906
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
return
|
|
1168
|
-
try {
|
|
1169
|
-
props.handleAuthResponse(await medplum.startNewProject({
|
|
1170
|
-
login: props.login,
|
|
1171
|
-
projectName: formData.projectName,
|
|
1172
|
-
}));
|
|
1173
|
-
}
|
|
1174
|
-
catch (err) {
|
|
1175
|
-
setOutcome(err);
|
|
1176
|
-
}
|
|
1177
|
-
} },
|
|
1178
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
1179
|
-
React.createElement(Logo, { size: 32 }),
|
|
1180
|
-
React.createElement(core$1.Title, null, "Create project")),
|
|
1181
|
-
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
1182
|
-
React.createElement(core$1.TextInput, { name: "projectName", label: "Project Name", placeholder: "My Project", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
1183
|
-
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
1184
|
-
"By clicking submit you agree to the Medplum",
|
|
1185
|
-
' ',
|
|
1186
|
-
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
1187
|
-
' and ',
|
|
1188
|
-
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
1189
|
-
".")),
|
|
1190
|
-
React.createElement(core$1.Group, { position: "right", mt: "xl", noWrap: true },
|
|
1191
|
-
React.createElement(core$1.Button, { type: "submit" }, "Create project"))));
|
|
907
|
+
function getResourceHref(to) {
|
|
908
|
+
return `/${to.resourceType}/${to.id}`;
|
|
909
|
+
}
|
|
910
|
+
function getReferenceHref(to) {
|
|
911
|
+
return `/${to.reference}`;
|
|
1192
912
|
}
|
|
1193
913
|
|
|
1194
914
|
/**
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
915
|
+
* React Hook to use a FHIR reference.
|
|
916
|
+
* Handles the complexity of resolving references and caching resources.
|
|
917
|
+
* @param value The resource or reference to resource.
|
|
918
|
+
* @returns The resolved resource.
|
|
1197
919
|
*/
|
|
1198
|
-
function
|
|
1199
|
-
const head = document.getElementsByTagName('head')[0];
|
|
1200
|
-
const script = document.createElement('script');
|
|
1201
|
-
script.async = true;
|
|
1202
|
-
script.src = src;
|
|
1203
|
-
script.onload = onload || null;
|
|
1204
|
-
head.appendChild(script);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
function GoogleButton(props) {
|
|
920
|
+
function useResource(value, setOutcome) {
|
|
1208
921
|
const medplum = useMedplum();
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const [buttonRendered, setButtonRendered] = React.useState(false);
|
|
1214
|
-
React.useEffect(() => {
|
|
1215
|
-
if (typeof google === 'undefined') {
|
|
1216
|
-
createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
|
|
1217
|
-
return;
|
|
922
|
+
const [resource, setResource] = React.useState(getInitialResource(medplum, value));
|
|
923
|
+
const setResourceIfChanged = React.useCallback((r) => {
|
|
924
|
+
if (!core.deepEquals(r, resource)) {
|
|
925
|
+
setResource(r);
|
|
1218
926
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
927
|
+
}, [resource, setResource]);
|
|
928
|
+
React.useEffect(() => {
|
|
929
|
+
setResourceIfChanged(getInitialResource(medplum, value));
|
|
930
|
+
}, [medplum, value, setResourceIfChanged]);
|
|
931
|
+
React.useEffect(() => {
|
|
932
|
+
let subscribed = true;
|
|
933
|
+
if (core.isReference(value)) {
|
|
934
|
+
medplum
|
|
935
|
+
.readReference(value)
|
|
936
|
+
.then((r) => {
|
|
937
|
+
if (subscribed) {
|
|
938
|
+
setResourceIfChanged(r);
|
|
939
|
+
}
|
|
940
|
+
})
|
|
941
|
+
.catch((err) => {
|
|
942
|
+
if (subscribed) {
|
|
943
|
+
setResourceIfChanged(undefined);
|
|
944
|
+
if (setOutcome) {
|
|
945
|
+
setOutcome(core.normalizeOperationOutcome(err));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
1223
948
|
});
|
|
1224
|
-
setInitialized(true);
|
|
1225
949
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
950
|
+
return (() => (subscribed = false));
|
|
951
|
+
}, [medplum, resource, value, setResourceIfChanged, setOutcome]);
|
|
952
|
+
return resource;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Returns the initial resource value based on the input value.
|
|
956
|
+
* If the input value is a resource, returns the resource.
|
|
957
|
+
* If the input value is a reference to a resource available in the cache, returns the resource.
|
|
958
|
+
* Otherwise, returns undefined.
|
|
959
|
+
* @param medplum The medplum client.
|
|
960
|
+
* @param value The resource or reference to resource.
|
|
961
|
+
* @returns An initial resource if available; undefined otherwise.
|
|
962
|
+
*/
|
|
963
|
+
function getInitialResource(medplum, value) {
|
|
964
|
+
if (value) {
|
|
965
|
+
if (core.isResource(value)) {
|
|
966
|
+
return value;
|
|
967
|
+
}
|
|
968
|
+
if (core.isReference(value)) {
|
|
969
|
+
return medplum.getCachedReference(value);
|
|
1229
970
|
}
|
|
1230
|
-
}, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
|
|
1231
|
-
if (!googleClientId) {
|
|
1232
|
-
return null;
|
|
1233
971
|
}
|
|
1234
|
-
return
|
|
972
|
+
return undefined;
|
|
1235
973
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
974
|
+
|
|
975
|
+
function ResourceAvatar(props) {
|
|
976
|
+
const resource = useResource(props.value);
|
|
977
|
+
const text = resource ? core.getDisplayString(resource) : props.alt ?? '';
|
|
978
|
+
const imageUrl = (resource && core.getImageSrc(resource)) ?? props.src;
|
|
979
|
+
const radius = props.radius ?? 'xl';
|
|
980
|
+
const avatarProps = { ...props };
|
|
981
|
+
delete avatarProps.value;
|
|
982
|
+
delete avatarProps.link;
|
|
983
|
+
if (props.link) {
|
|
984
|
+
return (React.createElement(MedplumLink, { to: resource },
|
|
985
|
+
React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps })));
|
|
1239
986
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
987
|
+
return React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps });
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function AsyncAutocomplete(props) {
|
|
991
|
+
const { defaultValue, toKey, toOption, loadOptions, onChange, onCreate, creatable, ...rest } = props;
|
|
992
|
+
const defaultItems = toDefaultItems(defaultValue);
|
|
993
|
+
const inputRef = React.useRef(null);
|
|
994
|
+
const [lastValue, setLastValue] = React.useState(undefined);
|
|
995
|
+
const [timer, setTimer] = React.useState();
|
|
996
|
+
const [abortController, setAbortController] = React.useState();
|
|
997
|
+
const [autoSubmit, setAutoSubmit] = React.useState();
|
|
998
|
+
const [options, setOptions] = React.useState(defaultItems?.map(toOption));
|
|
999
|
+
const lastValueRef = React.useRef();
|
|
1000
|
+
lastValueRef.current = lastValue;
|
|
1001
|
+
const timerRef = React.useRef();
|
|
1002
|
+
timerRef.current = timer;
|
|
1003
|
+
const abortControllerRef = React.useRef();
|
|
1004
|
+
abortControllerRef.current = abortController;
|
|
1005
|
+
const autoSubmitRef = React.useRef();
|
|
1006
|
+
autoSubmitRef.current = autoSubmit;
|
|
1007
|
+
const optionsRef = React.useRef();
|
|
1008
|
+
optionsRef.current = options;
|
|
1009
|
+
const handleTimer = React.useCallback(() => {
|
|
1010
|
+
setTimer(undefined);
|
|
1011
|
+
const value = inputRef.current?.value?.trim() || '';
|
|
1012
|
+
if (value === lastValueRef.current) {
|
|
1013
|
+
// Nothing has changed, move on
|
|
1014
|
+
return;
|
|
1245
1015
|
}
|
|
1016
|
+
setLastValue(value);
|
|
1017
|
+
const newAbortController = new AbortController();
|
|
1018
|
+
setAbortController(newAbortController);
|
|
1019
|
+
loadOptions(value, newAbortController.signal)
|
|
1020
|
+
.then((newValues) => {
|
|
1021
|
+
if (!newAbortController.signal.aborted) {
|
|
1022
|
+
setOptions(newValues.map(toOption));
|
|
1023
|
+
setAbortController(undefined);
|
|
1024
|
+
if (autoSubmitRef.current) {
|
|
1025
|
+
if (newValues.length > 0) {
|
|
1026
|
+
onChange(newValues.slice(0, 1));
|
|
1027
|
+
}
|
|
1028
|
+
setAutoSubmit(false);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
})
|
|
1032
|
+
.catch(console.log);
|
|
1033
|
+
}, [loadOptions, onChange, toOption]);
|
|
1034
|
+
const handleSearchChange = React.useCallback(() => {
|
|
1035
|
+
if (abortControllerRef.current) {
|
|
1036
|
+
abortControllerRef.current.abort();
|
|
1037
|
+
setAbortController(undefined);
|
|
1038
|
+
}
|
|
1039
|
+
if (timerRef.current !== undefined) {
|
|
1040
|
+
window.clearTimeout(timerRef.current);
|
|
1041
|
+
}
|
|
1042
|
+
const newTimer = window.setTimeout(() => handleTimer(), 100);
|
|
1043
|
+
setTimer(newTimer);
|
|
1044
|
+
}, [handleTimer]);
|
|
1045
|
+
const handleChange = React.useCallback((values) => {
|
|
1046
|
+
const result = [];
|
|
1047
|
+
for (const value of values) {
|
|
1048
|
+
let item = optionsRef.current?.find((option) => option.value === value)?.resource;
|
|
1049
|
+
if (!item && creatable !== false) {
|
|
1050
|
+
item = onCreate(value);
|
|
1051
|
+
}
|
|
1052
|
+
if (item)
|
|
1053
|
+
result.push(item);
|
|
1054
|
+
}
|
|
1055
|
+
onChange(result);
|
|
1056
|
+
}, [creatable, onChange, onCreate]);
|
|
1057
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
1058
|
+
if (e.key === 'Enter') {
|
|
1059
|
+
if (!timerRef.current && !abortControllerRef.current) {
|
|
1060
|
+
killEvent(e);
|
|
1061
|
+
if (optionsRef.current && optionsRef.current.length > 0) {
|
|
1062
|
+
setOptions(optionsRef.current.slice(0, 1));
|
|
1063
|
+
handleChange([optionsRef.current[0].value]);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
// The user pressed enter, but we don't have results yet.
|
|
1068
|
+
// We need to wait for the results to come in.
|
|
1069
|
+
setAutoSubmit(true);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}, [handleChange]);
|
|
1073
|
+
const handleCreate = React.useCallback((input) => {
|
|
1074
|
+
const option = toOption(onCreate(input));
|
|
1075
|
+
setOptions([...optionsRef.current, option]);
|
|
1076
|
+
return option;
|
|
1077
|
+
}, [onCreate, setOptions, toOption]);
|
|
1078
|
+
const handleFilter = React.useCallback((_value, selected) => !selected, []);
|
|
1079
|
+
React.useEffect(() => {
|
|
1080
|
+
return () => {
|
|
1081
|
+
if (abortControllerRef.current) {
|
|
1082
|
+
abortControllerRef.current.abort();
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}, []);
|
|
1086
|
+
return (React.createElement(core$1.MultiSelect, { ...rest, ref: inputRef, defaultValue: defaultItems.map(toKey), searchable: true, onKeyDown: handleKeyDown, onSearchChange: handleSearchChange, data: options, onFocus: handleTimer, onChange: handleChange, onCreate: handleCreate, rightSectionWidth: 40, rightSection: abortController ? React.createElement(core$1.Loader, { size: 16 }) : null, filter: handleFilter, creatable: true }));
|
|
1087
|
+
}
|
|
1088
|
+
function toDefaultItems(defaultValue) {
|
|
1089
|
+
if (!defaultValue) {
|
|
1090
|
+
return [];
|
|
1246
1091
|
}
|
|
1247
|
-
|
|
1092
|
+
if (Array.isArray(defaultValue)) {
|
|
1093
|
+
return defaultValue;
|
|
1094
|
+
}
|
|
1095
|
+
return [defaultValue];
|
|
1248
1096
|
}
|
|
1249
1097
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1098
|
+
const useStyles$h = core$1.createStyles(() => {
|
|
1099
|
+
return {
|
|
1100
|
+
searchInput: {
|
|
1101
|
+
input: {
|
|
1102
|
+
width: 220,
|
|
1103
|
+
transition: 'width 0.2s',
|
|
1104
|
+
},
|
|
1105
|
+
'input:focus': {
|
|
1106
|
+
width: 400,
|
|
1107
|
+
},
|
|
1108
|
+
'@media (max-width: 800px)': {
|
|
1109
|
+
input: {
|
|
1110
|
+
width: 150,
|
|
1111
|
+
},
|
|
1112
|
+
'input:focus': {
|
|
1113
|
+
width: 150,
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
});
|
|
1119
|
+
function toKey$1(resource) {
|
|
1120
|
+
return resource.id;
|
|
1121
|
+
}
|
|
1122
|
+
function toOption$1(resource) {
|
|
1123
|
+
return {
|
|
1124
|
+
value: resource.id,
|
|
1125
|
+
label: core.getDisplayString(resource),
|
|
1126
|
+
resource,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function HeaderSearchInput() {
|
|
1130
|
+
const { classes } = useStyles$h();
|
|
1131
|
+
const navigate = useMedplumNavigate();
|
|
1132
|
+
const medplum = useMedplum();
|
|
1133
|
+
const location = reactRouterDom.useLocation();
|
|
1134
|
+
const loadData = React.useCallback(async (input, signal) => {
|
|
1135
|
+
const query = buildGraphQLQuery(input);
|
|
1136
|
+
const options = { signal };
|
|
1137
|
+
const response = (await medplum.graphql(query, undefined, undefined, options));
|
|
1138
|
+
return getResourcesFromResponse(response, input);
|
|
1139
|
+
}, [medplum]);
|
|
1140
|
+
const handleSelect = React.useCallback((item) => {
|
|
1141
|
+
if (item.length > 0) {
|
|
1142
|
+
navigate(`/${core.getReferenceString(item[0])}`);
|
|
1143
|
+
}
|
|
1144
|
+
}, [navigate]);
|
|
1145
|
+
return (React.createElement(AsyncAutocomplete, { key: location.pathname, size: "sm", radius: "md", className: classes.searchInput, icon: React.createElement(IconSearch, { size: 16 }), placeholder: "Search", itemComponent: ItemComponent$1, toKey: toKey$1, toOption: toOption$1, onChange: handleSelect, loadOptions: loadData }));
|
|
1146
|
+
}
|
|
1147
|
+
const ItemComponent$1 = React.forwardRef(({ resource, ...others }, ref) => {
|
|
1148
|
+
let helpText = undefined;
|
|
1149
|
+
if (resource.resourceType === 'Patient') {
|
|
1150
|
+
helpText = resource.birthDate;
|
|
1254
1151
|
}
|
|
1255
|
-
|
|
1152
|
+
else if (resource.resourceType === 'ServiceRequest') {
|
|
1153
|
+
helpText = resource.subject?.display;
|
|
1154
|
+
}
|
|
1155
|
+
return (React.createElement("div", { ref: ref, ...others },
|
|
1156
|
+
React.createElement(core$1.Group, { noWrap: true },
|
|
1157
|
+
React.createElement(ResourceAvatar, { value: resource }),
|
|
1158
|
+
React.createElement("div", null,
|
|
1159
|
+
React.createElement(core$1.Text, null, core.getDisplayString(resource)),
|
|
1160
|
+
React.createElement(core$1.Text, { size: "xs", color: "dimmed" }, helpText)))));
|
|
1161
|
+
});
|
|
1162
|
+
function buildGraphQLQuery(input) {
|
|
1163
|
+
const escaped = JSON.stringify(input);
|
|
1164
|
+
if (core.isUUID(input)) {
|
|
1165
|
+
return `{
|
|
1166
|
+
Patients1: PatientList(_id: ${escaped}, _count: 1) {
|
|
1167
|
+
resourceType
|
|
1168
|
+
id
|
|
1169
|
+
identifier {
|
|
1170
|
+
system
|
|
1171
|
+
value
|
|
1172
|
+
}
|
|
1173
|
+
name {
|
|
1174
|
+
given
|
|
1175
|
+
family
|
|
1176
|
+
}
|
|
1177
|
+
birthDate
|
|
1178
|
+
}
|
|
1179
|
+
ServiceRequestList(_id: ${escaped}, _count: 1) {
|
|
1180
|
+
resourceType
|
|
1181
|
+
id
|
|
1182
|
+
identifier {
|
|
1183
|
+
system
|
|
1184
|
+
value
|
|
1185
|
+
}
|
|
1186
|
+
subject {
|
|
1187
|
+
display
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}`.replace(/\s+/g, ' ');
|
|
1191
|
+
}
|
|
1192
|
+
return `{
|
|
1193
|
+
Patients1: PatientList(name: ${escaped}, _count: 5) {
|
|
1194
|
+
resourceType
|
|
1195
|
+
id
|
|
1196
|
+
identifier {
|
|
1197
|
+
system
|
|
1198
|
+
value
|
|
1199
|
+
}
|
|
1200
|
+
name {
|
|
1201
|
+
given
|
|
1202
|
+
family
|
|
1203
|
+
}
|
|
1204
|
+
birthDate
|
|
1205
|
+
}
|
|
1206
|
+
Patients2: PatientList(identifier: ${escaped}, _count: 5) {
|
|
1207
|
+
resourceType
|
|
1208
|
+
id
|
|
1209
|
+
identifier {
|
|
1210
|
+
system
|
|
1211
|
+
value
|
|
1212
|
+
}
|
|
1213
|
+
name {
|
|
1214
|
+
given
|
|
1215
|
+
family
|
|
1216
|
+
}
|
|
1217
|
+
birthDate
|
|
1218
|
+
}
|
|
1219
|
+
ServiceRequestList(identifier: ${escaped}, _count: 5) {
|
|
1220
|
+
resourceType
|
|
1221
|
+
id
|
|
1222
|
+
identifier {
|
|
1223
|
+
system
|
|
1224
|
+
value
|
|
1225
|
+
}
|
|
1226
|
+
subject {
|
|
1227
|
+
display
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}`.replace(/\s+/g, ' ');
|
|
1256
1231
|
}
|
|
1257
|
-
|
|
1258
1232
|
/**
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1233
|
+
* Returns a de-duped and sorted list of resources from the search response.
|
|
1234
|
+
* The search request is actually 3+ separate searches, which can include duplicates.
|
|
1235
|
+
* This function combines the results, de-dupes, and sorts by relevance.
|
|
1236
|
+
* @param response The response from a search query.
|
|
1237
|
+
* @param query The user entered search query.
|
|
1238
|
+
* @returns The resources to display in the autocomplete.
|
|
1262
1239
|
*/
|
|
1263
|
-
function
|
|
1264
|
-
|
|
1265
|
-
|
|
1240
|
+
function getResourcesFromResponse(response, query) {
|
|
1241
|
+
const resources = [];
|
|
1242
|
+
if (response.data.Patients1) {
|
|
1243
|
+
resources.push(...response.data.Patients1);
|
|
1266
1244
|
}
|
|
1245
|
+
if (response.data.Patients2) {
|
|
1246
|
+
resources.push(...response.data.Patients2);
|
|
1247
|
+
}
|
|
1248
|
+
if (response.data.ServiceRequestList) {
|
|
1249
|
+
resources.push(...response.data.ServiceRequestList);
|
|
1250
|
+
}
|
|
1251
|
+
return sortByRelevance(dedupeResources(resources), query).slice(0, 5);
|
|
1267
1252
|
}
|
|
1268
1253
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
* @param
|
|
1271
|
-
* @returns
|
|
1254
|
+
* Removes duplicate resources from an array by ID.
|
|
1255
|
+
* @param resources The array of resources with possible duplicates.
|
|
1256
|
+
* @returns The array of resources with no duplicates.
|
|
1272
1257
|
*/
|
|
1273
|
-
function
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
});
|
|
1258
|
+
function dedupeResources(resources) {
|
|
1259
|
+
const ids = new Set();
|
|
1260
|
+
const result = [];
|
|
1261
|
+
for (const resource of resources) {
|
|
1262
|
+
if (!ids.has(resource.id)) {
|
|
1263
|
+
ids.add(resource.id);
|
|
1264
|
+
result.push(resource);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
return result;
|
|
1284
1268
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1269
|
+
/**
|
|
1270
|
+
* Sorts an array of resources by relevance.
|
|
1271
|
+
* @param resources The candidate resources.
|
|
1272
|
+
* @param query The user entered search string.
|
|
1273
|
+
* @returns The sorted array of resources.
|
|
1274
|
+
*/
|
|
1275
|
+
function sortByRelevance(resources, query) {
|
|
1276
|
+
return resources.sort((a, b) => {
|
|
1277
|
+
return getResourceScore(b, query) - getResourceScore(a, query);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Calculates a relevance score of a candidate resource.
|
|
1282
|
+
* Higher scores are better.
|
|
1283
|
+
* @param resource The candidate resource.
|
|
1284
|
+
* @param query The user entered search string.
|
|
1285
|
+
* @returns The relevance score of the candidate resource.
|
|
1286
|
+
*/
|
|
1287
|
+
function getResourceScore(resource, query) {
|
|
1288
|
+
let bestScore = 0;
|
|
1289
|
+
if (resource.identifier) {
|
|
1290
|
+
for (const identifier of resource.identifier) {
|
|
1291
|
+
bestScore = Math.max(bestScore, getStringScore(identifier.value, query));
|
|
1295
1292
|
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
googleClientId && (React.createElement(React.Fragment, null,
|
|
1321
|
-
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
1322
|
-
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: async (response) => {
|
|
1323
|
-
try {
|
|
1324
|
-
props.handleAuthResponse(await medplum.startGoogleLogin({
|
|
1325
|
-
googleClientId: response.clientId,
|
|
1326
|
-
googleCredential: response.credential,
|
|
1327
|
-
createUser: true,
|
|
1328
|
-
}));
|
|
1329
|
-
}
|
|
1330
|
-
catch (err) {
|
|
1331
|
-
setOutcome(err);
|
|
1332
|
-
}
|
|
1333
|
-
} })),
|
|
1334
|
-
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
1335
|
-
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
1336
|
-
React.createElement(core$1.TextInput, { name: "firstName", type: "text", label: "First name", placeholder: "First name", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
1337
|
-
React.createElement(core$1.TextInput, { name: "lastName", type: "text", label: "Last name", placeholder: "Last name", required: true, error: getErrorsForInput(outcome, 'lastName') }),
|
|
1338
|
-
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, error: getErrorsForInput(outcome, 'email') }),
|
|
1339
|
-
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') }),
|
|
1340
|
-
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
1341
|
-
"By clicking submit you agree to the Medplum",
|
|
1342
|
-
' ',
|
|
1343
|
-
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
1344
|
-
' and ',
|
|
1345
|
-
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
1346
|
-
"."),
|
|
1347
|
-
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
1348
|
-
"This site is protected by reCAPTCHA and the Google",
|
|
1349
|
-
' ',
|
|
1350
|
-
React.createElement(core$1.Anchor, { href: "https://policies.google.com/privacy" }, "Privacy\u00A0Policy"),
|
|
1351
|
-
' and ',
|
|
1352
|
-
React.createElement(core$1.Anchor, { href: "https://policies.google.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
1353
|
-
" apply.")),
|
|
1354
|
-
React.createElement(core$1.Group, { position: "apart", mt: "xl", noWrap: true },
|
|
1355
|
-
React.createElement(core$1.Checkbox, { name: "remember", label: "Remember me", size: "xs" }),
|
|
1356
|
-
React.createElement(core$1.Button, { type: "submit" }, "Create account"))));
|
|
1293
|
+
}
|
|
1294
|
+
if (resource.resourceType === 'Patient' && resource.name) {
|
|
1295
|
+
for (const name of resource.name) {
|
|
1296
|
+
bestScore = Math.max(bestScore, getStringScore(core.formatHumanName(name), query));
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return bestScore;
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Calculates a relevance score of a candidate display string.
|
|
1303
|
+
* Higher scores are better.
|
|
1304
|
+
* @param str The candidate display string.
|
|
1305
|
+
* @param query The user entered search string.
|
|
1306
|
+
* @returns The relevance score of the candidate string.
|
|
1307
|
+
*/
|
|
1308
|
+
function getStringScore(str, query) {
|
|
1309
|
+
if (!str) {
|
|
1310
|
+
return 0;
|
|
1311
|
+
}
|
|
1312
|
+
const index = str.toLowerCase().indexOf(query.toLowerCase());
|
|
1313
|
+
if (index < 0) {
|
|
1314
|
+
return 0;
|
|
1315
|
+
}
|
|
1316
|
+
return 100 - index;
|
|
1357
1317
|
}
|
|
1358
1318
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1319
|
+
const useStyles$g = core$1.createStyles((theme) => ({
|
|
1320
|
+
logoButton: {
|
|
1321
|
+
padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
|
|
1322
|
+
borderRadius: theme.radius.sm,
|
|
1323
|
+
transition: 'background-color 100ms ease',
|
|
1324
|
+
'&:hover': {
|
|
1325
|
+
backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
|
|
1326
|
+
},
|
|
1327
|
+
},
|
|
1328
|
+
user: {
|
|
1329
|
+
padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
|
|
1330
|
+
borderRadius: theme.radius.sm,
|
|
1331
|
+
transition: 'background-color 100ms ease',
|
|
1332
|
+
'&:hover': {
|
|
1333
|
+
backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
|
|
1334
|
+
},
|
|
1335
|
+
},
|
|
1336
|
+
userName: {
|
|
1337
|
+
fontWeight: 500,
|
|
1338
|
+
lineHeight: 1,
|
|
1339
|
+
marginRight: 3,
|
|
1340
|
+
[theme.fn.smallerThan('xs')]: {
|
|
1341
|
+
display: 'none',
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
userActive: {
|
|
1345
|
+
backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
|
|
1346
|
+
},
|
|
1347
|
+
}));
|
|
1348
|
+
function Header(props) {
|
|
1349
|
+
const context = useMedplumContext();
|
|
1350
|
+
const { medplum, profile, navigate } = context;
|
|
1351
|
+
const logins = medplum.getLogins();
|
|
1352
|
+
const { classes, cx } = useStyles$g();
|
|
1353
|
+
const [userMenuOpened, setUserMenuOpened] = React.useState(false);
|
|
1354
|
+
return (React.createElement(core$1.Header, { height: 60, p: 8, style: { zIndex: 101 } },
|
|
1355
|
+
React.createElement(core$1.Group, { position: "apart" },
|
|
1356
|
+
React.createElement(core$1.Group, { spacing: "xs" },
|
|
1357
|
+
React.createElement(core$1.UnstyledButton, { className: classes.logoButton, onClick: props.navbarToggle }, props.logo),
|
|
1358
|
+
React.createElement(HeaderSearchInput, null)),
|
|
1359
|
+
React.createElement(core$1.Menu, { width: 260, shadow: "xl", position: "bottom-end", transitionProps: { transition: 'pop-top-right' }, opened: userMenuOpened, onClose: () => setUserMenuOpened(false) },
|
|
1360
|
+
React.createElement(core$1.Menu.Target, null,
|
|
1361
|
+
React.createElement(core$1.UnstyledButton, { className: cx(classes.user, { [classes.userActive]: userMenuOpened }), onClick: () => setUserMenuOpened((o) => !o) },
|
|
1362
|
+
React.createElement(core$1.Group, { spacing: 7 },
|
|
1363
|
+
React.createElement(ResourceAvatar, { value: profile, radius: "xl", size: 24 }),
|
|
1364
|
+
React.createElement(core$1.Text, { size: "sm", className: classes.userName }, core.formatHumanName(profile?.name?.[0])),
|
|
1365
|
+
React.createElement(IconChevronDown, { size: 12, stroke: 1.5 })))),
|
|
1366
|
+
React.createElement(core$1.Menu.Dropdown, null,
|
|
1367
|
+
React.createElement(core$1.Stack, { align: "center", p: "xl" },
|
|
1368
|
+
React.createElement(ResourceAvatar, { size: "xl", radius: 100, value: context.profile }),
|
|
1369
|
+
React.createElement(HumanNameDisplay, { value: context.profile?.name?.[0] }),
|
|
1370
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, medplum.getActiveLogin()?.project?.display)),
|
|
1371
|
+
logins.length > 1 && React.createElement(core$1.Menu.Divider, null),
|
|
1372
|
+
logins.map((login) => login.profile?.reference !== core.getReferenceString(context.profile) && (React.createElement(core$1.Menu.Item, { key: login.profile?.reference, onClick: () => {
|
|
1373
|
+
medplum
|
|
1374
|
+
.setActiveLogin(login)
|
|
1375
|
+
.then(() => window.location.reload())
|
|
1376
|
+
.catch(console.log);
|
|
1377
|
+
} },
|
|
1378
|
+
React.createElement(core$1.Group, null,
|
|
1379
|
+
React.createElement(core$1.Avatar, { radius: "xl" }),
|
|
1380
|
+
React.createElement("div", { style: { flex: 1 } },
|
|
1381
|
+
React.createElement(core$1.Text, { size: "sm", weight: 500 }, login.profile?.display),
|
|
1382
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, login.project?.display)))))),
|
|
1383
|
+
React.createElement(core$1.Menu.Divider, null),
|
|
1384
|
+
React.createElement(core$1.Menu.Item, { icon: React.createElement(IconSwitchHorizontal, { size: 14, stroke: 1.5 }), onClick: () => navigate('/signin') }, "Add another account"),
|
|
1385
|
+
React.createElement(core$1.Menu.Item, { icon: React.createElement(IconSettings, { size: 14, stroke: 1.5 }), onClick: () => navigate(`/${core.getReferenceString(profile)}`) }, "Account settings"),
|
|
1386
|
+
React.createElement(core$1.Menu.Item, { icon: React.createElement(IconLogout, { size: 14, stroke: 1.5 }), onClick: async () => {
|
|
1387
|
+
await medplum.signOut();
|
|
1388
|
+
navigate('/signin');
|
|
1389
|
+
} }, "Sign out"),
|
|
1390
|
+
React.createElement(core$1.Text, { size: "xs", color: "dimmed", align: "center" }, props.version))))));
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function toKey(element) {
|
|
1394
|
+
return element.code;
|
|
1395
|
+
}
|
|
1396
|
+
function toOption(element) {
|
|
1397
|
+
return {
|
|
1398
|
+
value: element.code,
|
|
1399
|
+
label: getDisplay(element),
|
|
1400
|
+
resource: element,
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function createValue(input) {
|
|
1404
|
+
return {
|
|
1405
|
+
code: input,
|
|
1406
|
+
display: input,
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* A low-level component to autocomplete based on a FHIR Valueset.
|
|
1411
|
+
*/
|
|
1412
|
+
function ValueSetAutocomplete(props) {
|
|
1361
1413
|
const medplum = useMedplum();
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
.
|
|
1371
|
-
|
|
1372
|
-
}, [medplum, type, projectId, login, onSuccess]);
|
|
1373
|
-
function handleAuthResponse(response) {
|
|
1374
|
-
if (response.code) {
|
|
1375
|
-
medplum
|
|
1376
|
-
.processCode(response.code)
|
|
1377
|
-
.then(() => onSuccess())
|
|
1378
|
-
.catch(console.log);
|
|
1414
|
+
const { elementDefinition, creatable, clearable, ...rest } = props;
|
|
1415
|
+
const loadValues = React.useCallback(async (input, signal) => {
|
|
1416
|
+
const system = elementDefinition.binding?.valueSet;
|
|
1417
|
+
const valueSet = await medplum.searchValueSet(system, input, { signal });
|
|
1418
|
+
const valueSetElements = valueSet.expansion?.contains;
|
|
1419
|
+
const newData = [];
|
|
1420
|
+
for (const valueSetElement of valueSetElements) {
|
|
1421
|
+
if (valueSetElement.code && !newData.some((item) => item.code === valueSetElement.code)) {
|
|
1422
|
+
newData.push(valueSetElement);
|
|
1423
|
+
}
|
|
1379
1424
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1425
|
+
return newData;
|
|
1426
|
+
}, [medplum, elementDefinition]);
|
|
1427
|
+
return (React.createElement(AsyncAutocomplete, { ...rest, creatable: creatable ?? true, clearable: clearable ?? true, toKey: toKey, toOption: toOption, loadOptions: loadValues, onCreate: createValue, getCreateLabel: creatable === false ? undefined : (query) => `+ Create ${query}` }));
|
|
1428
|
+
}
|
|
1429
|
+
function getDisplay(item) {
|
|
1430
|
+
return item.display || item.code || '';
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function CodeInput(props) {
|
|
1434
|
+
const [value, setValue] = React.useState(props.defaultValue);
|
|
1435
|
+
function handleChange(newValues) {
|
|
1436
|
+
const newValue = newValues[0];
|
|
1437
|
+
const newCode = valueSetElementToCode(newValue);
|
|
1438
|
+
setValue(newCode);
|
|
1439
|
+
if (props.onChange) {
|
|
1440
|
+
props.onChange(newCode);
|
|
1382
1441
|
}
|
|
1383
1442
|
}
|
|
1384
|
-
return (React.createElement(
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1443
|
+
return (React.createElement(ValueSetAutocomplete, { elementDefinition: props.property, name: props.name, placeholder: props.placeholder, defaultValue: codeToValueSetElement(value), onChange: handleChange, creatable: props.creatable, maxSelectedValues: props.maxSelectedValues, clearSearchOnChange: props.clearSearchOnChange, clearable: props.clearable }));
|
|
1444
|
+
}
|
|
1445
|
+
function codeToValueSetElement(code) {
|
|
1446
|
+
return code ? { code } : undefined;
|
|
1447
|
+
}
|
|
1448
|
+
function valueSetElementToCode(element) {
|
|
1449
|
+
return element?.code;
|
|
1388
1450
|
}
|
|
1389
1451
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1452
|
+
const useStyles$f = core$1.createStyles((theme) => {
|
|
1453
|
+
return {
|
|
1454
|
+
menuTitle: {
|
|
1455
|
+
margin: '20px 0 4px 6px',
|
|
1456
|
+
fontSize: '9px',
|
|
1457
|
+
fontWeight: 'normal',
|
|
1458
|
+
textTransform: 'uppercase',
|
|
1459
|
+
letterSpacing: '2px',
|
|
1460
|
+
},
|
|
1461
|
+
link: {
|
|
1462
|
+
...theme.fn.focusStyles(),
|
|
1463
|
+
display: 'flex',
|
|
1464
|
+
alignItems: 'center',
|
|
1465
|
+
textDecoration: 'none',
|
|
1466
|
+
fontSize: theme.fontSizes.sm,
|
|
1467
|
+
color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],
|
|
1468
|
+
padding: `8px 12px`,
|
|
1469
|
+
borderRadius: theme.radius.sm,
|
|
1470
|
+
fontWeight: 500,
|
|
1471
|
+
'&:hover': {
|
|
1472
|
+
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
|
1473
|
+
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
|
|
1474
|
+
textDecoration: 'none',
|
|
1475
|
+
[`& svg`]: {
|
|
1476
|
+
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
1479
|
+
'& svg': {
|
|
1480
|
+
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],
|
|
1481
|
+
marginRight: theme.spacing.sm,
|
|
1482
|
+
strokeWidth: 1.5,
|
|
1483
|
+
width: 18,
|
|
1484
|
+
height: 18,
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1487
|
+
linkActive: {
|
|
1488
|
+
'&, &:hover': {
|
|
1489
|
+
backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,
|
|
1490
|
+
color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,
|
|
1491
|
+
[`& svg`]: {
|
|
1492
|
+
color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
};
|
|
1497
|
+
});
|
|
1498
|
+
function Navbar(props) {
|
|
1499
|
+
const { classes } = useStyles$f();
|
|
1500
|
+
const navigate = useMedplumNavigate();
|
|
1501
|
+
function onLinkClick(e, to) {
|
|
1502
|
+
e.stopPropagation();
|
|
1503
|
+
e.preventDefault();
|
|
1504
|
+
navigate(to);
|
|
1505
|
+
if (window.innerWidth < 768) {
|
|
1506
|
+
props.closeNavbar();
|
|
1507
|
+
}
|
|
1394
1508
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1509
|
+
function navigateResourceType(resourceType) {
|
|
1510
|
+
if (resourceType) {
|
|
1511
|
+
navigate(`/${resourceType}`);
|
|
1512
|
+
}
|
|
1397
1513
|
}
|
|
1514
|
+
return (React.createElement(core$1.Navbar, { width: { sm: 250 }, p: "xs" },
|
|
1515
|
+
React.createElement(core$1.Navbar.Section, null,
|
|
1516
|
+
React.createElement(CodeInput, { key: window.location.pathname, name: "resourceType", placeholder: "Resource Type", property: {
|
|
1517
|
+
binding: {
|
|
1518
|
+
valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',
|
|
1519
|
+
},
|
|
1520
|
+
}, onChange: (newValue) => navigateResourceType(newValue), creatable: false, maxSelectedValues: 0, clearSearchOnChange: true, clearable: false })),
|
|
1521
|
+
props.menus && (React.createElement(core$1.Navbar.Section, { grow: true }, props.menus.map((menu) => (React.createElement(React.Fragment, { key: `menu-${menu.title}` },
|
|
1522
|
+
React.createElement(core$1.Text, { className: classes.menuTitle }, menu.title),
|
|
1523
|
+
menu.links?.map((link) => (React.createElement(NavbarLink, { key: link.href, to: link.href, onClick: (e) => onLinkClick(e, link.href) },
|
|
1524
|
+
React.createElement(NavLinkIcon, { to: link.href, icon: link.icon }),
|
|
1525
|
+
React.createElement("span", null, link.label)))))))))));
|
|
1526
|
+
}
|
|
1527
|
+
function NavbarLink(props) {
|
|
1528
|
+
const { classes, cx } = useStyles$f();
|
|
1529
|
+
const location = reactRouterDom.useLocation();
|
|
1530
|
+
const [searchParams] = reactRouterDom.useSearchParams();
|
|
1531
|
+
const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);
|
|
1532
|
+
const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);
|
|
1533
|
+
return (React.createElement(MedplumLink, { onClick: props.onClick, to: props.to, className: cx(classes.link, { [classes.linkActive]: isActive }) }, props.children));
|
|
1398
1534
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1535
|
+
/**
|
|
1536
|
+
* Returns true if the search params match.
|
|
1537
|
+
* @param searchParams The current search params.
|
|
1538
|
+
* @param toUrl The destination URL of the link.
|
|
1539
|
+
* @returns True if the search params match.
|
|
1540
|
+
*/
|
|
1541
|
+
function matchesParams(searchParams, toUrl) {
|
|
1542
|
+
for (const [key, value] of toUrl.searchParams.entries()) {
|
|
1543
|
+
if (searchParams.get(key) !== value) {
|
|
1405
1544
|
return false;
|
|
1406
1545
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
domain: authMethod.domain,
|
|
1410
|
-
});
|
|
1411
|
-
const url = new URL(authMethod.authorizeUrl);
|
|
1412
|
-
url.searchParams.set('state', state);
|
|
1413
|
-
window.location.assign(url.toString());
|
|
1414
|
-
return true;
|
|
1415
|
-
}, [medplum, baseLoginRequest]);
|
|
1416
|
-
const handleSubmit = React.useCallback(async (formData) => {
|
|
1417
|
-
const authMethod = await medplum.post('auth/method', { email: formData.email });
|
|
1418
|
-
if (!(await isExternalAuth(authMethod))) {
|
|
1419
|
-
setEmail(formData.email);
|
|
1420
|
-
}
|
|
1421
|
-
}, [medplum, isExternalAuth, setEmail]);
|
|
1422
|
-
const handleGoogleCredential = React.useCallback(async (response) => {
|
|
1423
|
-
const authResponse = await medplum.startGoogleLogin({
|
|
1424
|
-
...baseLoginRequest,
|
|
1425
|
-
googleCredential: response.credential,
|
|
1426
|
-
});
|
|
1427
|
-
if (!(await isExternalAuth(authResponse))) {
|
|
1428
|
-
handleAuthResponse(authResponse);
|
|
1429
|
-
}
|
|
1430
|
-
}, [medplum, baseLoginRequest, isExternalAuth, handleAuthResponse]);
|
|
1431
|
-
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
1432
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
1433
|
-
googleClientId && (React.createElement(React.Fragment, null,
|
|
1434
|
-
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
1435
|
-
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: handleGoogleCredential })),
|
|
1436
|
-
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
1437
|
-
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true }),
|
|
1438
|
-
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
1439
|
-
React.createElement("div", null, onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register"))),
|
|
1440
|
-
React.createElement(core$1.Button, { type: "submit" }, "Next"))));
|
|
1546
|
+
}
|
|
1547
|
+
return true;
|
|
1441
1548
|
}
|
|
1442
|
-
function
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
const handleSubmit = React.useCallback((formData) => {
|
|
1448
|
-
medplum
|
|
1449
|
-
.startLogin({
|
|
1450
|
-
...baseLoginRequest,
|
|
1451
|
-
password: formData.password,
|
|
1452
|
-
remember: formData.remember === 'on',
|
|
1453
|
-
})
|
|
1454
|
-
.then(handleAuthResponse)
|
|
1455
|
-
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
1456
|
-
}, [medplum, baseLoginRequest, handleAuthResponse]);
|
|
1457
|
-
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
1458
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
1459
|
-
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
1460
|
-
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
1461
|
-
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') })),
|
|
1462
|
-
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
1463
|
-
onForgotPassword && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onForgotPassword, size: "xs" }, "Forgot password")),
|
|
1464
|
-
React.createElement(core$1.Checkbox, { id: "remember", name: "remember", label: "Remember me", size: "xs", sx: { lineHeight: 1 } }),
|
|
1465
|
-
React.createElement(core$1.Button, { type: "submit" }, "Sign in"))));
|
|
1549
|
+
function NavLinkIcon(props) {
|
|
1550
|
+
if (props.icon) {
|
|
1551
|
+
return props.icon;
|
|
1552
|
+
}
|
|
1553
|
+
return React.createElement(core$1.Space, { w: 30 });
|
|
1466
1554
|
}
|
|
1467
1555
|
|
|
1468
|
-
function
|
|
1556
|
+
function AppShell(props) {
|
|
1557
|
+
const theme = core$1.useMantineTheme();
|
|
1558
|
+
const [navbarOpen, setNavbarOpen] = React.useState(localStorage['navbarOpen'] === 'true');
|
|
1469
1559
|
const medplum = useMedplum();
|
|
1470
|
-
const
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1560
|
+
const profile = useMedplumProfile();
|
|
1561
|
+
function setNavbarOpenWrapper(open) {
|
|
1562
|
+
localStorage['navbarOpen'] = open.toString();
|
|
1563
|
+
setNavbarOpen(open);
|
|
1564
|
+
}
|
|
1565
|
+
function closeNavbar() {
|
|
1566
|
+
setNavbarOpenWrapper(false);
|
|
1567
|
+
}
|
|
1568
|
+
function toggleNavbar() {
|
|
1569
|
+
setNavbarOpenWrapper(!navbarOpen);
|
|
1570
|
+
}
|
|
1571
|
+
if (medplum.isLoading()) {
|
|
1572
|
+
return React.createElement(Loading, null);
|
|
1573
|
+
}
|
|
1574
|
+
return (React.createElement(core$1.AppShell, { styles: {
|
|
1575
|
+
main: {
|
|
1576
|
+
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],
|
|
1577
|
+
},
|
|
1578
|
+
}, padding: 0, fixed: true, header: profile && React.createElement(Header, { logo: props.logo, version: props.version, navbarToggle: toggleNavbar }), navbar: profile && navbarOpen ? React.createElement(Navbar, { menus: props.menus, closeNavbar: closeNavbar }) : undefined },
|
|
1579
|
+
React.createElement(ErrorBoundary, null,
|
|
1580
|
+
React.createElement(React.Suspense, { fallback: React.createElement(Loading, null) }, props.children))));
|
|
1490
1581
|
}
|
|
1491
1582
|
|
|
1492
|
-
function
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
} },
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
React.createElement(core$1.Title, null, "Choose scope")),
|
|
1507
|
-
React.createElement(core$1.Stack, null, (props.scope || 'openid').split(' ').map((scopeName) => (React.createElement(core$1.Checkbox, { key: scopeName, id: scopeName, name: scopeName, label: scopeName, defaultChecked: true })))),
|
|
1508
|
-
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
1509
|
-
React.createElement(core$1.Button, { type: "submit" }, "Set scope")))));
|
|
1583
|
+
function AttachmentDisplay(props) {
|
|
1584
|
+
const value = props.value;
|
|
1585
|
+
const { contentType, url, title } = value ?? {};
|
|
1586
|
+
if (!url) {
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
return (React.createElement("div", { "data-testid": "attachment-display" },
|
|
1590
|
+
contentType?.startsWith('image/') && (React.createElement("img", { "data-testid": "attachment-image", style: { maxWidth: props.maxWidth }, src: url, alt: value?.title })),
|
|
1591
|
+
contentType?.startsWith('video/') && (React.createElement("video", { "data-testid": "attachment-video", style: { maxWidth: props.maxWidth }, controls: true },
|
|
1592
|
+
React.createElement("source", { type: contentType, src: url }))),
|
|
1593
|
+
contentType === 'application/pdf' && !title?.endsWith('.pdf') && (React.createElement("div", { "data-testid": "attachment-pdf", style: { maxWidth: props.maxWidth, minHeight: 400 } },
|
|
1594
|
+
React.createElement("iframe", { width: "100%", height: "400", src: url + '#navpanes=0', allowFullScreen: true, frameBorder: 0, seamless: true }))),
|
|
1595
|
+
React.createElement("div", { "data-testid": "download-link", style: { padding: '2px 16px 16px 16px' } },
|
|
1596
|
+
React.createElement(core$1.Anchor, { href: value?.url, "data-testid": "attachment-details", target: "_blank", rel: "noopener noreferrer" }, value?.title || 'Download'))));
|
|
1510
1597
|
}
|
|
1511
1598
|
|
|
1512
|
-
function
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
setErrorMessage(undefined);
|
|
1517
|
-
medplum
|
|
1518
|
-
.post('auth/mfa/verify', {
|
|
1519
|
-
login: props.login,
|
|
1520
|
-
token: formData.token,
|
|
1521
|
-
})
|
|
1522
|
-
.then(props.handleAuthResponse)
|
|
1523
|
-
.catch((err) => setErrorMessage(core.normalizeErrorString(err)));
|
|
1524
|
-
} },
|
|
1525
|
-
React.createElement(core$1.Stack, null,
|
|
1526
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
1527
|
-
React.createElement(Logo, { size: 32 }),
|
|
1528
|
-
React.createElement(core$1.Title, null, "Enter MFA code")),
|
|
1529
|
-
errorMessage && (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Error", color: "red" }, errorMessage)),
|
|
1530
|
-
React.createElement(core$1.Stack, null,
|
|
1531
|
-
React.createElement(core$1.TextInput, { name: "token", label: "MFA code", required: true })),
|
|
1532
|
-
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
1533
|
-
React.createElement(core$1.Button, { type: "submit" }, "Submit code")))));
|
|
1599
|
+
function AttachmentArrayDisplay(props) {
|
|
1600
|
+
return (React.createElement("div", null, props.values &&
|
|
1601
|
+
props.values.map((v, index) => (React.createElement("div", { key: 'attatchment-' + index },
|
|
1602
|
+
React.createElement(AttachmentDisplay, { value: v, maxWidth: props.maxWidth }))))));
|
|
1534
1603
|
}
|
|
1535
1604
|
|
|
1536
|
-
|
|
1537
|
-
* The SignInForm component allows users to sign in to Medplum.
|
|
1538
|
-
*
|
|
1539
|
-
* "Signing in" is a multi-step process:
|
|
1540
|
-
* 1) Authentication - identify the user
|
|
1541
|
-
* 2) MFA - If MFA is enabled, prompt for MFA code
|
|
1542
|
-
* 3) Choose profile - If the user has multiple profiles, prompt to choose one
|
|
1543
|
-
* 4) Choose scope - If the user has multiple scopes, prompt to choose one
|
|
1544
|
-
* 5) Success - Return to the caller with either a code or a redirect
|
|
1545
|
-
*/
|
|
1546
|
-
function SignInForm(props) {
|
|
1547
|
-
const { chooseScopes, onSuccess, onForgotPassword, onRegister, onCode, ...baseLoginRequest } = props;
|
|
1605
|
+
function AttachmentButton(props) {
|
|
1548
1606
|
const medplum = useMedplum();
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
.then(() => {
|
|
1560
|
-
if (onSuccess) {
|
|
1561
|
-
onSuccess();
|
|
1562
|
-
}
|
|
1563
|
-
})
|
|
1564
|
-
.catch(console.log);
|
|
1565
|
-
}
|
|
1566
|
-
}, [medplum, onCode, onSuccess]);
|
|
1567
|
-
const handleAuthResponse = React.useCallback((response) => {
|
|
1568
|
-
setAuthenticatorRequired(!!response.mfaRequired);
|
|
1569
|
-
if (response.login) {
|
|
1570
|
-
setLogin(response.login);
|
|
1571
|
-
}
|
|
1572
|
-
if (response.memberships) {
|
|
1573
|
-
setMemberships(response.memberships);
|
|
1574
|
-
}
|
|
1575
|
-
if (response.code) {
|
|
1576
|
-
if (chooseScopes) {
|
|
1577
|
-
setMemberships(undefined);
|
|
1578
|
-
}
|
|
1579
|
-
else {
|
|
1580
|
-
handleCode(response.code);
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
}, [chooseScopes, handleCode]);
|
|
1584
|
-
const handleScopeResponse = React.useCallback((response) => {
|
|
1585
|
-
handleCode(response.code);
|
|
1586
|
-
}, [handleCode]);
|
|
1587
|
-
React.useEffect(() => {
|
|
1588
|
-
if (props.login) {
|
|
1589
|
-
medplum
|
|
1590
|
-
.get('auth/login/' + props.login)
|
|
1591
|
-
.then(handleAuthResponse)
|
|
1592
|
-
.catch(console.error);
|
|
1593
|
-
}
|
|
1594
|
-
}, [medplum, props, handleAuthResponse]);
|
|
1595
|
-
return (React.createElement(Document, { width: 450 }, (() => {
|
|
1596
|
-
if (!login) {
|
|
1597
|
-
return (React.createElement(AuthenticationForm, { onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, disableGoogleAuth: props.disableGoogleAuth, ...baseLoginRequest }, props.children));
|
|
1598
|
-
}
|
|
1599
|
-
else if (mfaRequired) {
|
|
1600
|
-
return React.createElement(MfaForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
1601
|
-
}
|
|
1602
|
-
else if (memberships) {
|
|
1603
|
-
return React.createElement(ChooseProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
|
|
1607
|
+
const fileInputRef = React.useRef(null);
|
|
1608
|
+
function onClick(e) {
|
|
1609
|
+
killEvent(e);
|
|
1610
|
+
fileInputRef.current?.click();
|
|
1611
|
+
}
|
|
1612
|
+
function onFileChange(e) {
|
|
1613
|
+
killEvent(e);
|
|
1614
|
+
const files = e.target.files;
|
|
1615
|
+
if (files) {
|
|
1616
|
+
Array.from(files).forEach(processFile);
|
|
1604
1617
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Processes a single file.
|
|
1621
|
+
*
|
|
1622
|
+
* @param {File} file The file descriptor.
|
|
1623
|
+
*/
|
|
1624
|
+
function processFile(file) {
|
|
1625
|
+
if (!file) {
|
|
1626
|
+
return;
|
|
1607
1627
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1628
|
+
const fileName = file.name;
|
|
1629
|
+
if (!fileName) {
|
|
1630
|
+
return;
|
|
1610
1631
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1632
|
+
if (props.onUploadStart) {
|
|
1633
|
+
props.onUploadStart();
|
|
1613
1634
|
}
|
|
1614
|
-
|
|
1635
|
+
const filename = file.name;
|
|
1636
|
+
const contentType = file.type || 'application/octet-stream';
|
|
1637
|
+
medplum
|
|
1638
|
+
.createBinary(file, filename, contentType, props.onUploadProgress)
|
|
1639
|
+
.then((binary) => {
|
|
1640
|
+
props.onUpload({
|
|
1641
|
+
contentType: binary.contentType,
|
|
1642
|
+
url: binary.url,
|
|
1643
|
+
title: filename,
|
|
1644
|
+
});
|
|
1645
|
+
})
|
|
1646
|
+
.catch((outcome) => {
|
|
1647
|
+
alert(outcome?.issue?.[0]?.details?.text);
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
return (React.createElement(React.Fragment, null,
|
|
1651
|
+
React.createElement("input", { type: "file", "data-testid": "upload-file-input", style: { display: 'none' }, ref: fileInputRef, onChange: (e) => onFileChange(e) }),
|
|
1652
|
+
props.children({ onClick })));
|
|
1615
1653
|
}
|
|
1616
1654
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
}
|
|
1655
|
+
function AttachmentArrayInput(props) {
|
|
1656
|
+
const [values, setValues] = React.useState(props.defaultValue ?? []);
|
|
1657
|
+
const valuesRef = React.useRef();
|
|
1658
|
+
valuesRef.current = values;
|
|
1659
|
+
function setValuesWrapper(newValues) {
|
|
1660
|
+
setValues(newValues);
|
|
1661
|
+
if (props.onChange) {
|
|
1662
|
+
props.onChange(newValues);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return (React.createElement("table", { style: { width: '100%' } },
|
|
1666
|
+
React.createElement("colgroup", null,
|
|
1667
|
+
React.createElement("col", { width: "97%" }),
|
|
1668
|
+
React.createElement("col", { width: "3%" })),
|
|
1669
|
+
React.createElement("tbody", null,
|
|
1670
|
+
values.map((v, index) => (React.createElement("tr", { key: `${index}-${values.length}` },
|
|
1671
|
+
React.createElement("td", null,
|
|
1672
|
+
React.createElement(AttachmentDisplay, { value: v, maxWidth: 200 })),
|
|
1673
|
+
React.createElement("td", null,
|
|
1674
|
+
React.createElement(core$1.ActionIcon, { title: "Remove", size: "sm", onClick: (e) => {
|
|
1675
|
+
killEvent(e);
|
|
1676
|
+
const copy = values.slice();
|
|
1677
|
+
copy.splice(index, 1);
|
|
1678
|
+
setValuesWrapper(copy);
|
|
1679
|
+
} },
|
|
1680
|
+
React.createElement(IconCircleMinus, null)))))),
|
|
1681
|
+
React.createElement("tr", null,
|
|
1682
|
+
React.createElement("td", null),
|
|
1683
|
+
React.createElement("td", null,
|
|
1684
|
+
React.createElement(AttachmentButton, { onUpload: (attachment) => {
|
|
1685
|
+
setValuesWrapper([...valuesRef.current, attachment]);
|
|
1686
|
+
} }, (props) => (React.createElement(core$1.ActionIcon, { ...props, title: "Add", size: "sm", color: "green" },
|
|
1687
|
+
React.createElement(IconCloudUpload, { size: 16 })))))))));
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
function AttachmentInput(props) {
|
|
1691
|
+
const [value, setValue] = React.useState(props.defaultValue);
|
|
1692
|
+
function setValueWrapper(newValue) {
|
|
1693
|
+
setValue(newValue);
|
|
1694
|
+
if (props.onChange) {
|
|
1695
|
+
props.onChange(newValue);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (value) {
|
|
1699
|
+
return (React.createElement(React.Fragment, null,
|
|
1700
|
+
React.createElement(AttachmentDisplay, { value: value, maxWidth: 200 }),
|
|
1701
|
+
React.createElement(core$1.Button, { onClick: (e) => {
|
|
1702
|
+
killEvent(e);
|
|
1703
|
+
setValueWrapper(undefined);
|
|
1704
|
+
} }, "Remove")));
|
|
1705
|
+
}
|
|
1706
|
+
return (React.createElement(AttachmentButton, { onUpload: setValueWrapper }, (props) => React.createElement(core$1.Button, { ...props }, "Upload...")));
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
const DEFAULT_IGNORED_PROPERTIES = [
|
|
1710
|
+
'meta',
|
|
1711
|
+
'implicitRules',
|
|
1712
|
+
'language',
|
|
1713
|
+
'text',
|
|
1714
|
+
'contained',
|
|
1715
|
+
'extension',
|
|
1716
|
+
'modifierExtension',
|
|
1717
|
+
];
|
|
1718
|
+
|
|
1719
|
+
const useStyles$e = core$1.createStyles((theme) => ({
|
|
1720
|
+
root: {
|
|
1721
|
+
display: 'grid',
|
|
1722
|
+
gridTemplateColumns: '30% 70%',
|
|
1723
|
+
margin: 0,
|
|
1724
|
+
'& > dt, & > dd': {
|
|
1725
|
+
padding: `${theme.spacing.sm} ${theme.spacing.sm}`,
|
|
1726
|
+
borderTop: `0.1px solid ${theme.colors.gray[3]}`,
|
|
1727
|
+
margin: 0,
|
|
1728
|
+
},
|
|
1729
|
+
},
|
|
1730
|
+
compact: {
|
|
1731
|
+
gridTemplateColumns: '20% 80%',
|
|
1732
|
+
'& > dt, & > dd': {
|
|
1733
|
+
padding: `0 ${theme.spacing.xs} ${theme.spacing.xs} 0`,
|
|
1734
|
+
border: 0,
|
|
1735
|
+
},
|
|
1736
|
+
},
|
|
1737
|
+
}));
|
|
1738
|
+
function DescriptionList(props) {
|
|
1739
|
+
const { children, compact } = props;
|
|
1740
|
+
const { classes, cx } = useStyles$e();
|
|
1741
|
+
return React.createElement("dl", { className: cx(classes.root, { [classes.compact]: compact }) }, children);
|
|
1742
|
+
}
|
|
1651
1743
|
function DescriptionListEntry(props) {
|
|
1652
1744
|
return (React.createElement(React.Fragment, null,
|
|
1653
1745
|
React.createElement("dt", null, props.term),
|
|
@@ -1698,14 +1790,6 @@
|
|
|
1698
1790
|
contactDetail.telecom?.map((telecom, index) => (React.createElement(ContactPointDisplay, { key: 'telecom-' + index, value: telecom })))));
|
|
1699
1791
|
}
|
|
1700
1792
|
|
|
1701
|
-
function HumanNameDisplay(props) {
|
|
1702
|
-
const name = props.value;
|
|
1703
|
-
if (!name) {
|
|
1704
|
-
return null;
|
|
1705
|
-
}
|
|
1706
|
-
return React.createElement(React.Fragment, null, core.formatHumanName(name, props.options));
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
1793
|
function IdentifierDisplay(props) {
|
|
1710
1794
|
return (React.createElement("div", null,
|
|
1711
1795
|
props.value?.system,
|
|
@@ -1736,50 +1820,6 @@
|
|
|
1736
1820
|
React.createElement(QuantityDisplay, { value: value.denominator })));
|
|
1737
1821
|
}
|
|
1738
1822
|
|
|
1739
|
-
function MedplumLink(props) {
|
|
1740
|
-
const navigate = useMedplumNavigate();
|
|
1741
|
-
const { to, suffix, label, onClick, children, ...rest } = props;
|
|
1742
|
-
let href = getHref(to);
|
|
1743
|
-
if (suffix) {
|
|
1744
|
-
href += '/' + suffix;
|
|
1745
|
-
}
|
|
1746
|
-
return (React.createElement(core$1.Anchor, { href: href, "aria-label": label, onClick: (e) => {
|
|
1747
|
-
killEvent(e);
|
|
1748
|
-
if (onClick) {
|
|
1749
|
-
onClick();
|
|
1750
|
-
}
|
|
1751
|
-
else if (to) {
|
|
1752
|
-
navigate(href);
|
|
1753
|
-
}
|
|
1754
|
-
}, ...rest }, children));
|
|
1755
|
-
}
|
|
1756
|
-
function getHref(to) {
|
|
1757
|
-
if (to) {
|
|
1758
|
-
if (typeof to === 'string') {
|
|
1759
|
-
return getStringHref(to);
|
|
1760
|
-
}
|
|
1761
|
-
else if (core.isResource(to)) {
|
|
1762
|
-
return getResourceHref(to);
|
|
1763
|
-
}
|
|
1764
|
-
else if (core.isReference(to)) {
|
|
1765
|
-
return getReferenceHref(to);
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
return '#';
|
|
1769
|
-
}
|
|
1770
|
-
function getStringHref(to) {
|
|
1771
|
-
if (to.startsWith('http://') || to.startsWith('https://') || to.startsWith('/')) {
|
|
1772
|
-
return to;
|
|
1773
|
-
}
|
|
1774
|
-
return '/' + to;
|
|
1775
|
-
}
|
|
1776
|
-
function getResourceHref(to) {
|
|
1777
|
-
return `/${to.resourceType}/${to.id}`;
|
|
1778
|
-
}
|
|
1779
|
-
function getReferenceHref(to) {
|
|
1780
|
-
return `/${to.reference}`;
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
1823
|
function ReferenceDisplay(props) {
|
|
1784
1824
|
if (!props.value) {
|
|
1785
1825
|
return null;
|
|
@@ -1946,69 +1986,36 @@
|
|
|
1946
1986
|
React.createElement(core$1.Input.Wrapper, { id: props.htmlFor, label: props.title, description: props.description, withAsterisk: props.withAsterisk }, (() => null)()))));
|
|
1947
1987
|
}
|
|
1948
1988
|
|
|
1949
|
-
function
|
|
1950
|
-
return
|
|
1989
|
+
function getErrorsForInput(outcome, expression) {
|
|
1990
|
+
return outcome?.issue
|
|
1991
|
+
?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression))
|
|
1992
|
+
?.map((issue) => issue.details?.text)
|
|
1993
|
+
?.join('\n');
|
|
1951
1994
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
* React Hook to use a FHIR reference.
|
|
1955
|
-
* Handles the complexity of resolving references and caching resources.
|
|
1956
|
-
* @param value The resource or reference to resource.
|
|
1957
|
-
* @returns The resolved resource.
|
|
1958
|
-
*/
|
|
1959
|
-
function useResource(value, setOutcome) {
|
|
1960
|
-
const medplum = useMedplum();
|
|
1961
|
-
const [resource, setResource] = React.useState(getInitialResource(medplum, value));
|
|
1962
|
-
const setResourceIfChanged = React.useCallback((r) => {
|
|
1963
|
-
if (!core.deepEquals(r, resource)) {
|
|
1964
|
-
setResource(r);
|
|
1965
|
-
}
|
|
1966
|
-
}, [resource, setResource]);
|
|
1967
|
-
React.useEffect(() => {
|
|
1968
|
-
setResourceIfChanged(getInitialResource(medplum, value));
|
|
1969
|
-
}, [medplum, value, setResourceIfChanged]);
|
|
1970
|
-
React.useEffect(() => {
|
|
1971
|
-
let subscribed = true;
|
|
1972
|
-
if (core.isReference(value)) {
|
|
1973
|
-
medplum
|
|
1974
|
-
.readReference(value)
|
|
1975
|
-
.then((r) => {
|
|
1976
|
-
if (subscribed) {
|
|
1977
|
-
setResourceIfChanged(r);
|
|
1978
|
-
}
|
|
1979
|
-
})
|
|
1980
|
-
.catch((err) => {
|
|
1981
|
-
if (subscribed) {
|
|
1982
|
-
setResourceIfChanged(undefined);
|
|
1983
|
-
if (setOutcome) {
|
|
1984
|
-
setOutcome(core.normalizeOperationOutcome(err));
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
});
|
|
1988
|
-
}
|
|
1989
|
-
return (() => (subscribed = false));
|
|
1990
|
-
}, [medplum, resource, value, setResourceIfChanged, setOutcome]);
|
|
1991
|
-
return resource;
|
|
1995
|
+
function getIssuesForExpression(outcome, expression) {
|
|
1996
|
+
return outcome?.issue?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression));
|
|
1992
1997
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
* Otherwise, returns undefined.
|
|
1998
|
-
* @param medplum The medplum client.
|
|
1999
|
-
* @param value The resource or reference to resource.
|
|
2000
|
-
* @returns An initial resource if available; undefined otherwise.
|
|
2001
|
-
*/
|
|
2002
|
-
function getInitialResource(medplum, value) {
|
|
2003
|
-
if (value) {
|
|
2004
|
-
if (core.isResource(value)) {
|
|
2005
|
-
return value;
|
|
2006
|
-
}
|
|
2007
|
-
if (core.isReference(value)) {
|
|
2008
|
-
return medplum.getCachedReference(value);
|
|
2009
|
-
}
|
|
1998
|
+
function isExpressionMatch(expr1, expr2) {
|
|
1999
|
+
// Expression can be either "fieldName" or "resourceType.fieldName"
|
|
2000
|
+
if (expr1 === expr2) {
|
|
2001
|
+
return true;
|
|
2010
2002
|
}
|
|
2011
|
-
|
|
2003
|
+
if (!expr1 || !expr2) {
|
|
2004
|
+
return false;
|
|
2005
|
+
}
|
|
2006
|
+
const dot1 = expr1.indexOf('.');
|
|
2007
|
+
if (dot1 >= 0 && expr1.substring(dot1 + 1) === expr2) {
|
|
2008
|
+
return true;
|
|
2009
|
+
}
|
|
2010
|
+
const dot2 = expr2.indexOf('.');
|
|
2011
|
+
if (dot2 >= 0 && expr2.substring(dot2 + 1) === expr1) {
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
return false;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
function FormSection(props) {
|
|
2018
|
+
return (React.createElement(core$1.Input.Wrapper, { id: props.htmlFor, label: props.title, description: props.description, withAsterisk: props.withAsterisk, error: getErrorsForInput(props.outcome, props.htmlFor) }, props.children));
|
|
2012
2019
|
}
|
|
2013
2020
|
|
|
2014
2021
|
function ResourceForm(props) {
|
|
@@ -2057,95 +2064,36 @@
|
|
|
2057
2064
|
return obj;
|
|
2058
2065
|
}
|
|
2059
2066
|
|
|
2060
|
-
function
|
|
2061
|
-
|
|
2067
|
+
function CodeableConceptInput(props) {
|
|
2068
|
+
const [value, setValue] = React.useState(props.defaultValue);
|
|
2069
|
+
function handleChange(newValues) {
|
|
2070
|
+
const newConcept = valueSetElementToCodeableConcept(newValues);
|
|
2071
|
+
setValue(newConcept);
|
|
2072
|
+
if (props.onChange) {
|
|
2073
|
+
props.onChange(newConcept);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return (React.createElement(ValueSetAutocomplete, { elementDefinition: props.property, name: props.name, placeholder: props.placeholder, defaultValue: value && codeableConceptToValueSetElement(value), onChange: handleChange }));
|
|
2062
2077
|
}
|
|
2063
|
-
function
|
|
2078
|
+
function codeableConceptToValueSetElement(concept) {
|
|
2079
|
+
return concept.coding?.map((c) => ({
|
|
2080
|
+
system: c.system,
|
|
2081
|
+
code: c.code,
|
|
2082
|
+
display: c.display,
|
|
2083
|
+
}));
|
|
2084
|
+
}
|
|
2085
|
+
function valueSetElementToCodeableConcept(elements) {
|
|
2086
|
+
if (elements.length === 0) {
|
|
2087
|
+
return undefined;
|
|
2088
|
+
}
|
|
2064
2089
|
return {
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2090
|
+
coding: elements.map((e) => ({
|
|
2091
|
+
system: e.system,
|
|
2092
|
+
code: e.code,
|
|
2093
|
+
display: e.display,
|
|
2094
|
+
})),
|
|
2068
2095
|
};
|
|
2069
2096
|
}
|
|
2070
|
-
function createValue(input) {
|
|
2071
|
-
return {
|
|
2072
|
-
code: input,
|
|
2073
|
-
display: input,
|
|
2074
|
-
};
|
|
2075
|
-
}
|
|
2076
|
-
/**
|
|
2077
|
-
* A low-level component to autocomplete based on a FHIR Valueset.
|
|
2078
|
-
*/
|
|
2079
|
-
function ValueSetAutocomplete(props) {
|
|
2080
|
-
const medplum = useMedplum();
|
|
2081
|
-
const { elementDefinition, ...rest } = props;
|
|
2082
|
-
const loadValues = React.useCallback(async (input, signal) => {
|
|
2083
|
-
const system = elementDefinition.binding?.valueSet;
|
|
2084
|
-
const valueSet = await medplum.searchValueSet(system, input, { signal });
|
|
2085
|
-
const valueSetElements = valueSet.expansion?.contains;
|
|
2086
|
-
const newData = [];
|
|
2087
|
-
for (const valueSetElement of valueSetElements) {
|
|
2088
|
-
if (valueSetElement.code && !newData.some((item) => item.code === valueSetElement.code)) {
|
|
2089
|
-
newData.push(valueSetElement);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
return newData;
|
|
2093
|
-
}, [medplum, elementDefinition]);
|
|
2094
|
-
return (React.createElement(AsyncAutocomplete, { ...rest, creatable: true, clearable: true, toKey: toKey, toOption: toOption, loadOptions: loadValues, getCreateLabel: (query) => `+ Create ${query}`, onCreate: createValue }));
|
|
2095
|
-
}
|
|
2096
|
-
function getDisplay(item) {
|
|
2097
|
-
return item.display || item.code || '';
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
function CodeableConceptInput(props) {
|
|
2101
|
-
const [value, setValue] = React.useState(props.defaultValue);
|
|
2102
|
-
function handleChange(newValues) {
|
|
2103
|
-
const newConcept = valueSetElementToCodeableConcept(newValues);
|
|
2104
|
-
setValue(newConcept);
|
|
2105
|
-
if (props.onChange) {
|
|
2106
|
-
props.onChange(newConcept);
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
return (React.createElement(ValueSetAutocomplete, { elementDefinition: props.property, name: props.name, placeholder: props.placeholder, defaultValue: value && codeableConceptToValueSetElement(value), onChange: handleChange }));
|
|
2110
|
-
}
|
|
2111
|
-
function codeableConceptToValueSetElement(concept) {
|
|
2112
|
-
return concept.coding?.map((c) => ({
|
|
2113
|
-
system: c.system,
|
|
2114
|
-
code: c.code,
|
|
2115
|
-
display: c.display,
|
|
2116
|
-
}));
|
|
2117
|
-
}
|
|
2118
|
-
function valueSetElementToCodeableConcept(elements) {
|
|
2119
|
-
if (elements.length === 0) {
|
|
2120
|
-
return undefined;
|
|
2121
|
-
}
|
|
2122
|
-
return {
|
|
2123
|
-
coding: elements.map((e) => ({
|
|
2124
|
-
system: e.system,
|
|
2125
|
-
code: e.code,
|
|
2126
|
-
display: e.display,
|
|
2127
|
-
})),
|
|
2128
|
-
};
|
|
2129
|
-
}
|
|
2130
|
-
|
|
2131
|
-
function CodeInput(props) {
|
|
2132
|
-
const [value, setValue] = React.useState(props.defaultValue);
|
|
2133
|
-
function handleChange(newValues) {
|
|
2134
|
-
const newValue = newValues[0];
|
|
2135
|
-
const newCode = valueSetElementToCode(newValue);
|
|
2136
|
-
setValue(newCode);
|
|
2137
|
-
if (props.onChange) {
|
|
2138
|
-
props.onChange(newCode);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
return (React.createElement(ValueSetAutocomplete, { elementDefinition: props.property, name: props.name, placeholder: props.placeholder, defaultValue: codeToValueSetElement(value), onChange: handleChange }));
|
|
2142
|
-
}
|
|
2143
|
-
function codeToValueSetElement(code) {
|
|
2144
|
-
return code ? { code } : undefined;
|
|
2145
|
-
}
|
|
2146
|
-
function valueSetElementToCode(element) {
|
|
2147
|
-
return element?.code;
|
|
2148
|
-
}
|
|
2149
2097
|
|
|
2150
2098
|
function CodingInput(props) {
|
|
2151
2099
|
const [value, setValue] = React.useState(props.defaultValue);
|
|
@@ -2517,21 +2465,6 @@
|
|
|
2517
2465
|
}) })));
|
|
2518
2466
|
}
|
|
2519
2467
|
|
|
2520
|
-
function ResourceAvatar(props) {
|
|
2521
|
-
const resource = useResource(props.value);
|
|
2522
|
-
const text = resource ? core.getDisplayString(resource) : props.alt ?? '';
|
|
2523
|
-
const imageUrl = (resource && core.getImageSrc(resource)) ?? props.src;
|
|
2524
|
-
const radius = props.radius ?? 'xl';
|
|
2525
|
-
const avatarProps = { ...props };
|
|
2526
|
-
delete avatarProps.value;
|
|
2527
|
-
delete avatarProps.link;
|
|
2528
|
-
if (props.link) {
|
|
2529
|
-
return (React.createElement(MedplumLink, { to: resource },
|
|
2530
|
-
React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps })));
|
|
2531
|
-
}
|
|
2532
|
-
return React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps });
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
2468
|
/**
|
|
2536
2469
|
* Defines which search parameters will be used by the type ahead to search for each resourceType
|
|
2537
2470
|
*/
|
|
@@ -2923,7 +2856,7 @@
|
|
|
2923
2856
|
})));
|
|
2924
2857
|
}
|
|
2925
2858
|
|
|
2926
|
-
const useStyles$
|
|
2859
|
+
const useStyles$d = core$1.createStyles((theme) => ({
|
|
2927
2860
|
table: {
|
|
2928
2861
|
width: 350,
|
|
2929
2862
|
'& th': {
|
|
@@ -2968,7 +2901,7 @@
|
|
|
2968
2901
|
return date.toLocaleString('default', { month: 'long' }) + ' ' + date.getFullYear();
|
|
2969
2902
|
}
|
|
2970
2903
|
function CalendarInput(props) {
|
|
2971
|
-
const { classes } = useStyles$
|
|
2904
|
+
const { classes } = useStyles$d();
|
|
2972
2905
|
const { onChangeMonth, onClick } = props;
|
|
2973
2906
|
const [month, setMonth] = React.useState(getStartMonth);
|
|
2974
2907
|
function moveMonth(delta) {
|
|
@@ -3051,13 +2984,27 @@
|
|
|
3051
2984
|
return false;
|
|
3052
2985
|
}
|
|
3053
2986
|
|
|
3054
|
-
const useStyles$
|
|
2987
|
+
const useStyles$c = core$1.createStyles(() => ({
|
|
2988
|
+
root: {
|
|
2989
|
+
'@media (max-width: 800px)': {
|
|
2990
|
+
paddingLeft: 4,
|
|
2991
|
+
paddingRight: 4,
|
|
2992
|
+
},
|
|
2993
|
+
},
|
|
2994
|
+
}));
|
|
2995
|
+
function Container(props) {
|
|
2996
|
+
const { children, ...others } = props;
|
|
2997
|
+
const { classes } = useStyles$c();
|
|
2998
|
+
return (React.createElement(core$1.Container, { className: classes.root, ...others }, children));
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
const useStyles$b = core$1.createStyles((theme) => ({
|
|
3055
3002
|
noteBody: { fontSize: theme.fontSizes.sm },
|
|
3056
3003
|
noteCite: { fontSize: theme.fontSizes.xs, marginBlockStart: 3 },
|
|
3057
3004
|
noteRoot: { padding: 5 },
|
|
3058
3005
|
}));
|
|
3059
3006
|
function NoteDisplay({ value }) {
|
|
3060
|
-
const { classes } = useStyles$
|
|
3007
|
+
const { classes } = useStyles$b();
|
|
3061
3008
|
if (!value) {
|
|
3062
3009
|
return null;
|
|
3063
3010
|
}
|
|
@@ -3150,7 +3097,7 @@
|
|
|
3150
3097
|
return React.createElement(core$1.Badge, { color: statusToColor[props.status] }, props.status);
|
|
3151
3098
|
}
|
|
3152
3099
|
|
|
3153
|
-
const useStyles$
|
|
3100
|
+
const useStyles$a = core$1.createStyles((theme) => ({
|
|
3154
3101
|
table: {
|
|
3155
3102
|
border: `0.1px solid ${theme.colors.gray[5]}`,
|
|
3156
3103
|
borderCollapse: 'collapse',
|
|
@@ -3245,7 +3192,7 @@
|
|
|
3245
3192
|
core.formatDateTime(specimen.receivedTime)))))))));
|
|
3246
3193
|
}
|
|
3247
3194
|
function ObservationTable(props) {
|
|
3248
|
-
const { classes } = useStyles$
|
|
3195
|
+
const { classes } = useStyles$a();
|
|
3249
3196
|
return (React.createElement("table", { className: classes.table },
|
|
3250
3197
|
React.createElement("thead", null,
|
|
3251
3198
|
React.createElement("tr", null,
|
|
@@ -3259,7 +3206,7 @@
|
|
|
3259
3206
|
React.createElement("tbody", null, props.value?.map((observation, index) => (React.createElement(ObservationRow, { key: `obs-${observation.id}-${index}`, hideObservationNotes: props.hideObservationNotes, value: observation }))))));
|
|
3260
3207
|
}
|
|
3261
3208
|
function ObservationRow(props) {
|
|
3262
|
-
const { classes, cx } = useStyles$
|
|
3209
|
+
const { classes, cx } = useStyles$a();
|
|
3263
3210
|
const observation = useResource(props.value);
|
|
3264
3211
|
if (!observation) {
|
|
3265
3212
|
return null;
|
|
@@ -3309,6 +3256,92 @@
|
|
|
3309
3256
|
return code === 'AA' || code === 'LL' || code === 'HH' || code === 'A';
|
|
3310
3257
|
}
|
|
3311
3258
|
|
|
3259
|
+
/**
|
|
3260
|
+
* Parses an HTML form and returns the result as a JavaScript object.
|
|
3261
|
+
* @param form The HTML form element.
|
|
3262
|
+
*/
|
|
3263
|
+
function parseForm(form) {
|
|
3264
|
+
const result = {};
|
|
3265
|
+
for (const element of Array.from(form.elements)) {
|
|
3266
|
+
if (element instanceof HTMLInputElement) {
|
|
3267
|
+
parseInputElement(result, element);
|
|
3268
|
+
}
|
|
3269
|
+
else if (element instanceof HTMLTextAreaElement) {
|
|
3270
|
+
result[element.name] = element.value;
|
|
3271
|
+
}
|
|
3272
|
+
else if (element instanceof HTMLSelectElement) {
|
|
3273
|
+
parseSelectElement(result, element);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
return result;
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Parses an HTML input element.
|
|
3280
|
+
* Sets the name/value pair in the result,
|
|
3281
|
+
* but only if the element is enabled and checked.
|
|
3282
|
+
* @param el The input element.
|
|
3283
|
+
* @param result The result builder.
|
|
3284
|
+
*/
|
|
3285
|
+
function parseInputElement(result, el) {
|
|
3286
|
+
if (el.disabled) {
|
|
3287
|
+
// Ignore disabled elements
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked) {
|
|
3291
|
+
// Ignore unchecked radio or checkbox elements
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
result[el.name] = el.value;
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* Parses an HTML select element.
|
|
3298
|
+
* Sets the name/value pair if one is selected.
|
|
3299
|
+
* @param result The result builder.
|
|
3300
|
+
* @param el The select element.
|
|
3301
|
+
*/
|
|
3302
|
+
function parseSelectElement(result, el) {
|
|
3303
|
+
result[el.name] = el.value;
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
function Form(props) {
|
|
3307
|
+
return (React.createElement("form", { style: props.style, "data-testid": props.testid, onSubmit: (e) => {
|
|
3308
|
+
e.preventDefault();
|
|
3309
|
+
const formData = parseForm(e.target);
|
|
3310
|
+
if (props.onSubmit) {
|
|
3311
|
+
props.onSubmit(formData);
|
|
3312
|
+
}
|
|
3313
|
+
} }, props.children));
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
const useStyles$9 = core$1.createStyles((theme, { width, fill }) => ({
|
|
3317
|
+
paper: {
|
|
3318
|
+
maxWidth: width,
|
|
3319
|
+
margin: `${theme.spacing.xl} auto`,
|
|
3320
|
+
padding: fill ? 0 : theme.spacing.md,
|
|
3321
|
+
'@media (max-width: 800px)': {
|
|
3322
|
+
padding: fill ? 0 : 8,
|
|
3323
|
+
},
|
|
3324
|
+
'& img': {
|
|
3325
|
+
width: '100%',
|
|
3326
|
+
maxWidth: '100%',
|
|
3327
|
+
},
|
|
3328
|
+
'& video': {
|
|
3329
|
+
width: '100%',
|
|
3330
|
+
maxWidth: '100%',
|
|
3331
|
+
},
|
|
3332
|
+
},
|
|
3333
|
+
}));
|
|
3334
|
+
const defaultProps$1 = {
|
|
3335
|
+
shadow: 'xs',
|
|
3336
|
+
radius: 'md',
|
|
3337
|
+
withBorder: true,
|
|
3338
|
+
};
|
|
3339
|
+
function Panel(props) {
|
|
3340
|
+
const { className, children, width, fill, unstyled, ...others } = core$1.useComponentDefaultProps('Panel', defaultProps$1, props);
|
|
3341
|
+
const { classes, cx } = useStyles$9({ width, fill }, { name: 'Panel', unstyled });
|
|
3342
|
+
return (React.createElement(core$1.Paper, { className: cx(classes.paper, className), ...others }, children));
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3312
3345
|
const useStyles$8 = core$1.createStyles((theme) => ({
|
|
3313
3346
|
root: {
|
|
3314
3347
|
borderCollapse: 'collapse',
|
|
@@ -3399,29 +3432,6 @@
|
|
|
3399
3432
|
}, ignoreMissingValues: props.ignoreMissingValues }));
|
|
3400
3433
|
}
|
|
3401
3434
|
|
|
3402
|
-
/**
|
|
3403
|
-
* ErrorBoundary is a React component that handles errors in its child components.
|
|
3404
|
-
* See: https://reactjs.org/docs/error-boundaries.html
|
|
3405
|
-
*/
|
|
3406
|
-
class ErrorBoundary extends React.Component {
|
|
3407
|
-
constructor(props) {
|
|
3408
|
-
super(props);
|
|
3409
|
-
this.state = {};
|
|
3410
|
-
}
|
|
3411
|
-
static getDerivedStateFromError(error) {
|
|
3412
|
-
return { error };
|
|
3413
|
-
}
|
|
3414
|
-
componentDidCatch(error, errorInfo) {
|
|
3415
|
-
console.error('Uncaught error:', error, errorInfo);
|
|
3416
|
-
}
|
|
3417
|
-
render() {
|
|
3418
|
-
if (this.state.error) {
|
|
3419
|
-
return (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Something went wrong", color: "red" }, core.normalizeErrorString(this.state.error)));
|
|
3420
|
-
}
|
|
3421
|
-
return this.props.children;
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
|
|
3425
3435
|
function Timeline(props) {
|
|
3426
3436
|
return React.createElement(Container, null, props.children);
|
|
3427
3437
|
}
|
|
@@ -3786,6 +3796,12 @@
|
|
|
3786
3796
|
} }));
|
|
3787
3797
|
}
|
|
3788
3798
|
|
|
3799
|
+
function Document(props) {
|
|
3800
|
+
const { children, ...others } = props;
|
|
3801
|
+
return (React.createElement(Container, null,
|
|
3802
|
+
React.createElement(Panel, { ...others }, children)));
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3789
3805
|
function EncounterTimeline(props) {
|
|
3790
3806
|
return (React.createElement(ResourceTimeline, { value: props.encounter, loadTimelineResources: async (medplum, _resourceType, id) => {
|
|
3791
3807
|
return Promise.allSettled([
|
|
@@ -5055,8 +5071,8 @@
|
|
|
5055
5071
|
return (React.createElement("div", { className: classes.root, "data-testid": "search-control" },
|
|
5056
5072
|
!props.hideToolbar && (React.createElement(core$1.Group, { position: "apart", mb: "xl" },
|
|
5057
5073
|
React.createElement(core$1.Group, { spacing: 2 },
|
|
5058
|
-
React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(
|
|
5059
|
-
React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(
|
|
5074
|
+
React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconColumns, { size: iconSize }), onClick: () => setState({ ...stateRef.current, fieldEditorVisible: true }) }, "Fields"),
|
|
5075
|
+
React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconFilter, { size: iconSize }), onClick: () => setState({ ...stateRef.current, filterEditorVisible: true }) }, "Filters"),
|
|
5060
5076
|
props.onNew && (React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconFilePlus, { size: iconSize }), onClick: props.onNew }, "New...")),
|
|
5061
5077
|
!isMobile && isExportPassed() && (React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconTableExport, { size: iconSize }), onClick: props.onExport ? props.onExport : () => setState({ ...stateRef.current, exportDialogVisible: true }) }, "Export...")),
|
|
5062
5078
|
!isMobile && props.onDelete && (React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconTrash, { size: iconSize }), onClick: () => props.onDelete(Object.keys(state.selected)) }, "Delete...")),
|
|
@@ -5292,6 +5308,24 @@
|
|
|
5292
5308
|
}
|
|
5293
5309
|
const MemoizedFhirPathTable = React.memo(FhirPathTable);
|
|
5294
5310
|
|
|
5311
|
+
function Logo(props) {
|
|
5312
|
+
return (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 491 491", style: { width: props.size, height: props.size } },
|
|
5313
|
+
React.createElement("title", null, "Medplum Logo"),
|
|
5314
|
+
React.createElement("path", { fill: props.fill || '#ad7136', d: "M282 67c6-16 16-29 29-40L289 0c-22 17-37 41-43 68l17 23 19-24z" }),
|
|
5315
|
+
React.createElement("path", { fill: props.fill || '#946af9', d: "M311 63c-17 0-33 4-48 11-16-7-32-11-49-11-87 0-158 96-158 214s71 214 158 214c17 0 33-4 49-11 15 7 31 11 48 11 87 0 158-96 158-214S398 63 311 63z" }),
|
|
5316
|
+
React.createElement("path", { fill: props.fill || '#7857c5', d: "M231 489l-17 2c-87 0-158-96-158-214S127 63 214 63l17 1c-39 12-70 102-70 213s31 201 70 212z" }),
|
|
5317
|
+
React.createElement("path", { fill: props.fill || '#40bc26', d: "M207 220a176 176 0 01-177 43A176 176 0 01251 43l1 5c17 59 2 125-45 172z" }),
|
|
5318
|
+
React.createElement("path", { fill: props.fill || '#33961e', d: "M252 48A421 421 0 0057 270l-27-7A176 176 0 01251 43l1 5z" })));
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5321
|
+
function OperationOutcomeAlert(props) {
|
|
5322
|
+
const issues = props.outcome?.issue || props.issues;
|
|
5323
|
+
if (!issues) {
|
|
5324
|
+
return null;
|
|
5325
|
+
}
|
|
5326
|
+
return (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), color: "red" }, issues.map((issue) => (React.createElement("div", { "data-testid": "text-field-error", key: issue.details?.text }, issue.details?.text)))));
|
|
5327
|
+
}
|
|
5328
|
+
|
|
5295
5329
|
function PatientTimeline(props) {
|
|
5296
5330
|
const loadTimelineResources = React.useCallback((medplum, _resourceType, id) => {
|
|
5297
5331
|
return Promise.allSettled([
|
|
@@ -6917,9 +6951,455 @@
|
|
|
6917
6951
|
}) }));
|
|
6918
6952
|
}
|
|
6919
6953
|
|
|
6954
|
+
function NewProjectForm(props) {
|
|
6955
|
+
const medplum = useMedplum();
|
|
6956
|
+
const [outcome, setOutcome] = React.useState();
|
|
6957
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: async (formData) => {
|
|
6958
|
+
try {
|
|
6959
|
+
props.handleAuthResponse(await medplum.startNewProject({
|
|
6960
|
+
login: props.login,
|
|
6961
|
+
projectName: formData.projectName,
|
|
6962
|
+
}));
|
|
6963
|
+
}
|
|
6964
|
+
catch (err) {
|
|
6965
|
+
setOutcome(err);
|
|
6966
|
+
}
|
|
6967
|
+
} },
|
|
6968
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
6969
|
+
React.createElement(Logo, { size: 32 }),
|
|
6970
|
+
React.createElement(core$1.Title, null, "Create project")),
|
|
6971
|
+
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
6972
|
+
React.createElement(core$1.TextInput, { name: "projectName", label: "Project Name", placeholder: "My Project", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
6973
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
6974
|
+
"By clicking submit you agree to the Medplum",
|
|
6975
|
+
' ',
|
|
6976
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
6977
|
+
' and ',
|
|
6978
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
6979
|
+
".")),
|
|
6980
|
+
React.createElement(core$1.Group, { position: "right", mt: "xl", noWrap: true },
|
|
6981
|
+
React.createElement(core$1.Button, { type: "submit" }, "Create project"))));
|
|
6982
|
+
}
|
|
6983
|
+
|
|
6984
|
+
/**
|
|
6985
|
+
* Dynamically creates a script tag for the specified JavaScript file.
|
|
6986
|
+
* @param src The JavaScript file URL.
|
|
6987
|
+
*/
|
|
6988
|
+
function createScriptTag(src, onload) {
|
|
6989
|
+
const head = document.getElementsByTagName('head')[0];
|
|
6990
|
+
const script = document.createElement('script');
|
|
6991
|
+
script.async = true;
|
|
6992
|
+
script.src = src;
|
|
6993
|
+
script.onload = onload || null;
|
|
6994
|
+
head.appendChild(script);
|
|
6995
|
+
}
|
|
6996
|
+
|
|
6997
|
+
function GoogleButton(props) {
|
|
6998
|
+
const medplum = useMedplum();
|
|
6999
|
+
const { googleClientId, handleGoogleCredential } = props;
|
|
7000
|
+
const parentRef = React.useRef(null);
|
|
7001
|
+
const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
|
|
7002
|
+
const [initialized, setInitialized] = React.useState(false);
|
|
7003
|
+
const [buttonRendered, setButtonRendered] = React.useState(false);
|
|
7004
|
+
React.useEffect(() => {
|
|
7005
|
+
if (typeof google === 'undefined') {
|
|
7006
|
+
createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
|
|
7007
|
+
return;
|
|
7008
|
+
}
|
|
7009
|
+
if (!initialized) {
|
|
7010
|
+
google.accounts.id.initialize({
|
|
7011
|
+
client_id: googleClientId,
|
|
7012
|
+
callback: handleGoogleCredential,
|
|
7013
|
+
});
|
|
7014
|
+
setInitialized(true);
|
|
7015
|
+
}
|
|
7016
|
+
if (parentRef.current && !buttonRendered) {
|
|
7017
|
+
google.accounts.id.renderButton(parentRef.current, {});
|
|
7018
|
+
setButtonRendered(true);
|
|
7019
|
+
}
|
|
7020
|
+
}, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
|
|
7021
|
+
if (!googleClientId) {
|
|
7022
|
+
return null;
|
|
7023
|
+
}
|
|
7024
|
+
return React.createElement("div", { ref: parentRef });
|
|
7025
|
+
}
|
|
7026
|
+
function getGoogleClientId(clientId) {
|
|
7027
|
+
if (clientId) {
|
|
7028
|
+
return clientId;
|
|
7029
|
+
}
|
|
7030
|
+
if (typeof window !== 'undefined') {
|
|
7031
|
+
const origin = window.location.protocol + '//' + window.location.host;
|
|
7032
|
+
const authorizedOrigins = "undefined"?.split(',') ?? [];
|
|
7033
|
+
if (authorizedOrigins.includes(origin)) {
|
|
7034
|
+
return "undefined";
|
|
7035
|
+
}
|
|
7036
|
+
}
|
|
7037
|
+
return undefined;
|
|
7038
|
+
}
|
|
7039
|
+
|
|
7040
|
+
/**
|
|
7041
|
+
* Dynamically loads the recaptcha script.
|
|
7042
|
+
* We do not want to load the script on page load unless the user needs it.
|
|
7043
|
+
* @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
|
|
7044
|
+
*/
|
|
7045
|
+
function initRecaptcha(siteKey) {
|
|
7046
|
+
if (typeof grecaptcha === 'undefined') {
|
|
7047
|
+
createScriptTag('https://www.google.com/recaptcha/api.js?render=' + siteKey);
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
7050
|
+
/**
|
|
7051
|
+
* Starts a request to generate a recapcha token.
|
|
7052
|
+
* @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
|
|
7053
|
+
* @returns Promise to a recaptcha token for the current user.
|
|
7054
|
+
*/
|
|
7055
|
+
function getRecaptcha(siteKey) {
|
|
7056
|
+
return new Promise((resolve, reject) => {
|
|
7057
|
+
grecaptcha.ready(async () => {
|
|
7058
|
+
try {
|
|
7059
|
+
resolve(await grecaptcha.execute(siteKey, { action: 'submit' }));
|
|
7060
|
+
}
|
|
7061
|
+
catch (err) {
|
|
7062
|
+
reject(err);
|
|
7063
|
+
}
|
|
7064
|
+
});
|
|
7065
|
+
});
|
|
7066
|
+
}
|
|
7067
|
+
|
|
7068
|
+
function NewUserForm(props) {
|
|
7069
|
+
const googleClientId = getGoogleClientId(props.googleClientId);
|
|
7070
|
+
const recaptchaSiteKey = props.recaptchaSiteKey;
|
|
7071
|
+
const medplum = useMedplum();
|
|
7072
|
+
const [outcome, setOutcome] = React.useState();
|
|
7073
|
+
const issues = getIssuesForExpression(outcome, undefined);
|
|
7074
|
+
React.useEffect(() => {
|
|
7075
|
+
if (recaptchaSiteKey) {
|
|
7076
|
+
initRecaptcha(recaptchaSiteKey);
|
|
7077
|
+
}
|
|
7078
|
+
}, [recaptchaSiteKey]);
|
|
7079
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: async (formData) => {
|
|
7080
|
+
try {
|
|
7081
|
+
let recaptchaToken = '';
|
|
7082
|
+
if (recaptchaSiteKey) {
|
|
7083
|
+
recaptchaToken = await getRecaptcha(recaptchaSiteKey);
|
|
7084
|
+
}
|
|
7085
|
+
props.handleAuthResponse(await medplum.startNewUser({
|
|
7086
|
+
projectId: props.projectId,
|
|
7087
|
+
firstName: formData.firstName,
|
|
7088
|
+
lastName: formData.lastName,
|
|
7089
|
+
email: formData.email,
|
|
7090
|
+
password: formData.password,
|
|
7091
|
+
remember: formData.remember === 'true',
|
|
7092
|
+
recaptchaSiteKey,
|
|
7093
|
+
recaptchaToken,
|
|
7094
|
+
}));
|
|
7095
|
+
}
|
|
7096
|
+
catch (err) {
|
|
7097
|
+
setOutcome(err);
|
|
7098
|
+
}
|
|
7099
|
+
} },
|
|
7100
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, props.children),
|
|
7101
|
+
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
7102
|
+
googleClientId && (React.createElement(React.Fragment, null,
|
|
7103
|
+
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
7104
|
+
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: async (response) => {
|
|
7105
|
+
try {
|
|
7106
|
+
props.handleAuthResponse(await medplum.startGoogleLogin({
|
|
7107
|
+
googleClientId: response.clientId,
|
|
7108
|
+
googleCredential: response.credential,
|
|
7109
|
+
createUser: true,
|
|
7110
|
+
}));
|
|
7111
|
+
}
|
|
7112
|
+
catch (err) {
|
|
7113
|
+
setOutcome(err);
|
|
7114
|
+
}
|
|
7115
|
+
} })),
|
|
7116
|
+
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
7117
|
+
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
7118
|
+
React.createElement(core$1.TextInput, { name: "firstName", type: "text", label: "First name", placeholder: "First name", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
7119
|
+
React.createElement(core$1.TextInput, { name: "lastName", type: "text", label: "Last name", placeholder: "Last name", required: true, error: getErrorsForInput(outcome, 'lastName') }),
|
|
7120
|
+
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, error: getErrorsForInput(outcome, 'email') }),
|
|
7121
|
+
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') }),
|
|
7122
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
7123
|
+
"By clicking submit you agree to the Medplum",
|
|
7124
|
+
' ',
|
|
7125
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
7126
|
+
' and ',
|
|
7127
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
7128
|
+
"."),
|
|
7129
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
7130
|
+
"This site is protected by reCAPTCHA and the Google",
|
|
7131
|
+
' ',
|
|
7132
|
+
React.createElement(core$1.Anchor, { href: "https://policies.google.com/privacy" }, "Privacy\u00A0Policy"),
|
|
7133
|
+
' and ',
|
|
7134
|
+
React.createElement(core$1.Anchor, { href: "https://policies.google.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
7135
|
+
" apply.")),
|
|
7136
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", noWrap: true },
|
|
7137
|
+
React.createElement(core$1.Checkbox, { name: "remember", label: "Remember me", size: "xs" }),
|
|
7138
|
+
React.createElement(core$1.Button, { type: "submit" }, "Create account"))));
|
|
7139
|
+
}
|
|
7140
|
+
|
|
7141
|
+
function RegisterForm(props) {
|
|
7142
|
+
const { type, projectId, googleClientId, recaptchaSiteKey, onSuccess } = props;
|
|
7143
|
+
const medplum = useMedplum();
|
|
7144
|
+
const [login, setLogin] = React.useState(undefined);
|
|
7145
|
+
const [outcome, setOutcome] = React.useState();
|
|
7146
|
+
React.useEffect(() => {
|
|
7147
|
+
if (type === 'patient' && login) {
|
|
7148
|
+
medplum
|
|
7149
|
+
.startNewPatient({ login, projectId: projectId })
|
|
7150
|
+
.then((response) => medplum.processCode(response.code))
|
|
7151
|
+
.then(() => onSuccess())
|
|
7152
|
+
.catch((err) => setOutcome(err));
|
|
7153
|
+
}
|
|
7154
|
+
}, [medplum, type, projectId, login, onSuccess]);
|
|
7155
|
+
function handleAuthResponse(response) {
|
|
7156
|
+
if (response.code) {
|
|
7157
|
+
medplum
|
|
7158
|
+
.processCode(response.code)
|
|
7159
|
+
.then(() => onSuccess())
|
|
7160
|
+
.catch(console.log);
|
|
7161
|
+
}
|
|
7162
|
+
else if (response.login) {
|
|
7163
|
+
setLogin(response.login);
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
return (React.createElement(Document, { width: 450 },
|
|
7167
|
+
outcome && React.createElement("pre", null, JSON.stringify(outcome, null, 2)),
|
|
7168
|
+
!login && (React.createElement(NewUserForm, { projectId: projectId, googleClientId: googleClientId, recaptchaSiteKey: recaptchaSiteKey, handleAuthResponse: handleAuthResponse }, props.children)),
|
|
7169
|
+
login && type === 'project' && React.createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse })));
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7172
|
+
function AuthenticationForm(props) {
|
|
7173
|
+
const [email, setEmail] = React.useState();
|
|
7174
|
+
if (!email) {
|
|
7175
|
+
return React.createElement(EmailForm, { setEmail: setEmail, ...props });
|
|
7176
|
+
}
|
|
7177
|
+
else {
|
|
7178
|
+
return React.createElement(PasswordForm, { email: email, ...props });
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7181
|
+
function EmailForm(props) {
|
|
7182
|
+
const { setEmail, onRegister, handleAuthResponse, children, ...baseLoginRequest } = props;
|
|
7183
|
+
const medplum = useMedplum();
|
|
7184
|
+
const googleClientId = !props.disableGoogleAuth && getGoogleClientId(props.googleClientId);
|
|
7185
|
+
const isExternalAuth = React.useCallback(async (authMethod) => {
|
|
7186
|
+
if (!authMethod.authorizeUrl) {
|
|
7187
|
+
return false;
|
|
7188
|
+
}
|
|
7189
|
+
const state = JSON.stringify({
|
|
7190
|
+
...(await medplum.ensureCodeChallenge(baseLoginRequest)),
|
|
7191
|
+
domain: authMethod.domain,
|
|
7192
|
+
});
|
|
7193
|
+
const url = new URL(authMethod.authorizeUrl);
|
|
7194
|
+
url.searchParams.set('state', state);
|
|
7195
|
+
window.location.assign(url.toString());
|
|
7196
|
+
return true;
|
|
7197
|
+
}, [medplum, baseLoginRequest]);
|
|
7198
|
+
const handleSubmit = React.useCallback(async (formData) => {
|
|
7199
|
+
const authMethod = await medplum.post('auth/method', { email: formData.email });
|
|
7200
|
+
if (!(await isExternalAuth(authMethod))) {
|
|
7201
|
+
setEmail(formData.email);
|
|
7202
|
+
}
|
|
7203
|
+
}, [medplum, isExternalAuth, setEmail]);
|
|
7204
|
+
const handleGoogleCredential = React.useCallback(async (response) => {
|
|
7205
|
+
const authResponse = await medplum.startGoogleLogin({
|
|
7206
|
+
...baseLoginRequest,
|
|
7207
|
+
googleCredential: response.credential,
|
|
7208
|
+
});
|
|
7209
|
+
if (!(await isExternalAuth(authResponse))) {
|
|
7210
|
+
handleAuthResponse(authResponse);
|
|
7211
|
+
}
|
|
7212
|
+
}, [medplum, baseLoginRequest, isExternalAuth, handleAuthResponse]);
|
|
7213
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
7214
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
7215
|
+
googleClientId && (React.createElement(React.Fragment, null,
|
|
7216
|
+
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
7217
|
+
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: handleGoogleCredential })),
|
|
7218
|
+
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
7219
|
+
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true }),
|
|
7220
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
7221
|
+
React.createElement("div", null, onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register"))),
|
|
7222
|
+
React.createElement(core$1.Button, { type: "submit" }, "Next"))));
|
|
7223
|
+
}
|
|
7224
|
+
function PasswordForm(props) {
|
|
7225
|
+
const { onForgotPassword, handleAuthResponse, children, ...baseLoginRequest } = props;
|
|
7226
|
+
const medplum = useMedplum();
|
|
7227
|
+
const [outcome, setOutcome] = React.useState();
|
|
7228
|
+
const issues = getIssuesForExpression(outcome, undefined);
|
|
7229
|
+
const handleSubmit = React.useCallback((formData) => {
|
|
7230
|
+
medplum
|
|
7231
|
+
.startLogin({
|
|
7232
|
+
...baseLoginRequest,
|
|
7233
|
+
password: formData.password,
|
|
7234
|
+
remember: formData.remember === 'on',
|
|
7235
|
+
})
|
|
7236
|
+
.then(handleAuthResponse)
|
|
7237
|
+
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
7238
|
+
}, [medplum, baseLoginRequest, handleAuthResponse]);
|
|
7239
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
7240
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
7241
|
+
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
7242
|
+
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
7243
|
+
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') })),
|
|
7244
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
7245
|
+
onForgotPassword && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onForgotPassword, size: "xs" }, "Forgot password")),
|
|
7246
|
+
React.createElement(core$1.Checkbox, { id: "remember", name: "remember", label: "Remember me", size: "xs", sx: { lineHeight: 1 } }),
|
|
7247
|
+
React.createElement(core$1.Button, { type: "submit" }, "Sign in"))));
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
function ChooseProfileForm(props) {
|
|
7251
|
+
const medplum = useMedplum();
|
|
7252
|
+
const [outcome, setOutcome] = React.useState();
|
|
7253
|
+
return (React.createElement(core$1.Stack, null,
|
|
7254
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7255
|
+
React.createElement(Logo, { size: 32 }),
|
|
7256
|
+
React.createElement(core$1.Title, { order: 3 }, "Choose profile")),
|
|
7257
|
+
React.createElement(OperationOutcomeAlert, { outcome: outcome }),
|
|
7258
|
+
props.memberships.map((membership) => (React.createElement(core$1.UnstyledButton, { key: membership.id, onClick: () => {
|
|
7259
|
+
medplum
|
|
7260
|
+
.post('auth/profile', {
|
|
7261
|
+
login: props.login,
|
|
7262
|
+
profile: membership.id,
|
|
7263
|
+
})
|
|
7264
|
+
.then(props.handleAuthResponse)
|
|
7265
|
+
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
7266
|
+
} },
|
|
7267
|
+
React.createElement(core$1.Group, null,
|
|
7268
|
+
React.createElement(core$1.Avatar, { radius: "xl" }),
|
|
7269
|
+
React.createElement("div", { style: { flex: 1 } },
|
|
7270
|
+
React.createElement(core$1.Text, { size: "sm", weight: 500 }, membership.profile?.display),
|
|
7271
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, membership.project?.display))))))));
|
|
7272
|
+
}
|
|
7273
|
+
|
|
7274
|
+
function ChooseScopeForm(props) {
|
|
7275
|
+
const medplum = useMedplum();
|
|
7276
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
|
|
7277
|
+
medplum
|
|
7278
|
+
.post('auth/scope', {
|
|
7279
|
+
login: props.login,
|
|
7280
|
+
scope: Object.keys(formData).join(' '),
|
|
7281
|
+
})
|
|
7282
|
+
.then(props.handleAuthResponse)
|
|
7283
|
+
.catch(console.log);
|
|
7284
|
+
} },
|
|
7285
|
+
React.createElement(core$1.Stack, null,
|
|
7286
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7287
|
+
React.createElement(Logo, { size: 32 }),
|
|
7288
|
+
React.createElement(core$1.Title, null, "Choose scope")),
|
|
7289
|
+
React.createElement(core$1.Stack, null, (props.scope || 'openid').split(' ').map((scopeName) => (React.createElement(core$1.Checkbox, { key: scopeName, id: scopeName, name: scopeName, label: scopeName, defaultChecked: true })))),
|
|
7290
|
+
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
7291
|
+
React.createElement(core$1.Button, { type: "submit" }, "Set scope")))));
|
|
7292
|
+
}
|
|
7293
|
+
|
|
7294
|
+
function MfaForm(props) {
|
|
7295
|
+
const medplum = useMedplum();
|
|
7296
|
+
const [errorMessage, setErrorMessage] = React.useState(undefined);
|
|
7297
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
|
|
7298
|
+
setErrorMessage(undefined);
|
|
7299
|
+
medplum
|
|
7300
|
+
.post('auth/mfa/verify', {
|
|
7301
|
+
login: props.login,
|
|
7302
|
+
token: formData.token,
|
|
7303
|
+
})
|
|
7304
|
+
.then(props.handleAuthResponse)
|
|
7305
|
+
.catch((err) => setErrorMessage(core.normalizeErrorString(err)));
|
|
7306
|
+
} },
|
|
7307
|
+
React.createElement(core$1.Stack, null,
|
|
7308
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7309
|
+
React.createElement(Logo, { size: 32 }),
|
|
7310
|
+
React.createElement(core$1.Title, null, "Enter MFA code")),
|
|
7311
|
+
errorMessage && (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Error", color: "red" }, errorMessage)),
|
|
7312
|
+
React.createElement(core$1.Stack, null,
|
|
7313
|
+
React.createElement(core$1.TextInput, { name: "token", label: "MFA code", required: true })),
|
|
7314
|
+
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
7315
|
+
React.createElement(core$1.Button, { type: "submit" }, "Submit code")))));
|
|
7316
|
+
}
|
|
7317
|
+
|
|
7318
|
+
/**
|
|
7319
|
+
* The SignInForm component allows users to sign in to Medplum.
|
|
7320
|
+
*
|
|
7321
|
+
* "Signing in" is a multi-step process:
|
|
7322
|
+
* 1) Authentication - identify the user
|
|
7323
|
+
* 2) MFA - If MFA is enabled, prompt for MFA code
|
|
7324
|
+
* 3) Choose profile - If the user has multiple profiles, prompt to choose one
|
|
7325
|
+
* 4) Choose scope - If the user has multiple scopes, prompt to choose one
|
|
7326
|
+
* 5) Success - Return to the caller with either a code or a redirect
|
|
7327
|
+
*/
|
|
7328
|
+
function SignInForm(props) {
|
|
7329
|
+
const { chooseScopes, onSuccess, onForgotPassword, onRegister, onCode, ...baseLoginRequest } = props;
|
|
7330
|
+
const medplum = useMedplum();
|
|
7331
|
+
const [login, setLogin] = React.useState(undefined);
|
|
7332
|
+
const [mfaRequired, setAuthenticatorRequired] = React.useState(false);
|
|
7333
|
+
const [memberships, setMemberships] = React.useState(undefined);
|
|
7334
|
+
const handleCode = React.useCallback((code) => {
|
|
7335
|
+
if (onCode) {
|
|
7336
|
+
onCode(code);
|
|
7337
|
+
}
|
|
7338
|
+
else {
|
|
7339
|
+
medplum
|
|
7340
|
+
.processCode(code)
|
|
7341
|
+
.then(() => {
|
|
7342
|
+
if (onSuccess) {
|
|
7343
|
+
onSuccess();
|
|
7344
|
+
}
|
|
7345
|
+
})
|
|
7346
|
+
.catch(console.log);
|
|
7347
|
+
}
|
|
7348
|
+
}, [medplum, onCode, onSuccess]);
|
|
7349
|
+
const handleAuthResponse = React.useCallback((response) => {
|
|
7350
|
+
setAuthenticatorRequired(!!response.mfaRequired);
|
|
7351
|
+
if (response.login) {
|
|
7352
|
+
setLogin(response.login);
|
|
7353
|
+
}
|
|
7354
|
+
if (response.memberships) {
|
|
7355
|
+
setMemberships(response.memberships);
|
|
7356
|
+
}
|
|
7357
|
+
if (response.code) {
|
|
7358
|
+
if (chooseScopes) {
|
|
7359
|
+
setMemberships(undefined);
|
|
7360
|
+
}
|
|
7361
|
+
else {
|
|
7362
|
+
handleCode(response.code);
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
}, [chooseScopes, handleCode]);
|
|
7366
|
+
const handleScopeResponse = React.useCallback((response) => {
|
|
7367
|
+
handleCode(response.code);
|
|
7368
|
+
}, [handleCode]);
|
|
7369
|
+
React.useEffect(() => {
|
|
7370
|
+
if (props.login) {
|
|
7371
|
+
medplum
|
|
7372
|
+
.get('auth/login/' + props.login)
|
|
7373
|
+
.then(handleAuthResponse)
|
|
7374
|
+
.catch(console.error);
|
|
7375
|
+
}
|
|
7376
|
+
}, [medplum, props, handleAuthResponse]);
|
|
7377
|
+
return (React.createElement(Document, { width: 450 }, (() => {
|
|
7378
|
+
if (!login) {
|
|
7379
|
+
return (React.createElement(AuthenticationForm, { onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, disableGoogleAuth: props.disableGoogleAuth, ...baseLoginRequest }, props.children));
|
|
7380
|
+
}
|
|
7381
|
+
else if (mfaRequired) {
|
|
7382
|
+
return React.createElement(MfaForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
7383
|
+
}
|
|
7384
|
+
else if (memberships) {
|
|
7385
|
+
return React.createElement(ChooseProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
|
|
7386
|
+
}
|
|
7387
|
+
else if (props.projectId === 'new') {
|
|
7388
|
+
return React.createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
7389
|
+
}
|
|
7390
|
+
else if (props.chooseScopes) {
|
|
7391
|
+
return React.createElement(ChooseScopeForm, { login: login, scope: props.scope, handleAuthResponse: handleScopeResponse });
|
|
7392
|
+
}
|
|
7393
|
+
else {
|
|
7394
|
+
return React.createElement("div", null, "Success");
|
|
7395
|
+
}
|
|
7396
|
+
})()));
|
|
7397
|
+
}
|
|
7398
|
+
|
|
6920
7399
|
exports.AddressDisplay = AddressDisplay;
|
|
6921
7400
|
exports.AddressInput = AddressInput;
|
|
6922
7401
|
exports.AnnotationInput = AnnotationInput;
|
|
7402
|
+
exports.AppShell = AppShell;
|
|
6923
7403
|
exports.AsyncAutocomplete = AsyncAutocomplete;
|
|
6924
7404
|
exports.AttachmentArrayDisplay = AttachmentArrayDisplay;
|
|
6925
7405
|
exports.AttachmentArrayInput = AttachmentArrayInput;
|
|
@@ -6953,10 +7433,12 @@
|
|
|
6953
7433
|
exports.FhirPathTable = FhirPathTable;
|
|
6954
7434
|
exports.Form = Form;
|
|
6955
7435
|
exports.FormSection = FormSection;
|
|
7436
|
+
exports.Header = Header;
|
|
6956
7437
|
exports.HumanNameDisplay = HumanNameDisplay;
|
|
6957
7438
|
exports.HumanNameInput = HumanNameInput;
|
|
6958
7439
|
exports.IdentifierDisplay = IdentifierDisplay;
|
|
6959
7440
|
exports.IdentifierInput = IdentifierInput;
|
|
7441
|
+
exports.Loading = Loading;
|
|
6960
7442
|
exports.Logo = Logo;
|
|
6961
7443
|
exports.MedplumLink = MedplumLink;
|
|
6962
7444
|
exports.MedplumProvider = MedplumProvider;
|
|
@@ -6964,6 +7446,7 @@
|
|
|
6964
7446
|
exports.MemoizedSearchControl = MemoizedSearchControl;
|
|
6965
7447
|
exports.MoneyDisplay = MoneyDisplay;
|
|
6966
7448
|
exports.MoneyInput = MoneyInput;
|
|
7449
|
+
exports.Navbar = Navbar;
|
|
6967
7450
|
exports.ObservationTable = ObservationTable;
|
|
6968
7451
|
exports.OperationOutcomeAlert = OperationOutcomeAlert;
|
|
6969
7452
|
exports.Panel = Panel;
|