@medplum/react 2.0.16 → 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 +1546 -1064
- 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/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/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 +0 -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 +0 -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 +0 -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 +10 -10
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,169 +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, creatable, ...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 && creatable !== false) {
|
|
248
|
-
item = onCreate(value);
|
|
249
|
-
}
|
|
250
|
-
if (item)
|
|
251
|
-
result.push(item);
|
|
252
|
-
}
|
|
253
|
-
onChange(result);
|
|
254
|
-
}, [creatable, onChange, onCreate]);
|
|
255
|
-
const handleKeyDown = React.useCallback((e) => {
|
|
256
|
-
if (e.key === 'Enter') {
|
|
257
|
-
if (!timerRef.current && !abortControllerRef.current) {
|
|
258
|
-
killEvent(e);
|
|
259
|
-
if (optionsRef.current && optionsRef.current.length > 0) {
|
|
260
|
-
setOptions(optionsRef.current.slice(0, 1));
|
|
261
|
-
handleChange([optionsRef.current[0].value]);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
// The user pressed enter, but we don't have results yet.
|
|
266
|
-
// We need to wait for the results to come in.
|
|
267
|
-
setAutoSubmit(true);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}, [handleChange]);
|
|
271
|
-
const handleCreate = React.useCallback((input) => {
|
|
272
|
-
const option = toOption(onCreate(input));
|
|
273
|
-
setOptions([...optionsRef.current, option]);
|
|
274
|
-
return option;
|
|
275
|
-
}, [onCreate, setOptions, toOption]);
|
|
276
|
-
const handleFilter = React.useCallback((_value, selected) => !selected, []);
|
|
277
|
-
React.useEffect(() => {
|
|
278
|
-
return () => {
|
|
279
|
-
if (abortControllerRef.current) {
|
|
280
|
-
abortControllerRef.current.abort();
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
}, []);
|
|
284
|
-
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 }));
|
|
285
|
-
}
|
|
286
|
-
function toDefaultItems(defaultValue) {
|
|
287
|
-
if (!defaultValue) {
|
|
288
|
-
return [];
|
|
289
|
-
}
|
|
290
|
-
if (Array.isArray(defaultValue)) {
|
|
291
|
-
return defaultValue;
|
|
292
|
-
}
|
|
293
|
-
return [defaultValue];
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function AttachmentDisplay(props) {
|
|
297
|
-
const value = props.value;
|
|
298
|
-
const { contentType, url, title } = value ?? {};
|
|
299
|
-
if (!url) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
return (React.createElement("div", { "data-testid": "attachment-display" },
|
|
303
|
-
contentType?.startsWith('image/') && (React.createElement("img", { "data-testid": "attachment-image", style: { maxWidth: props.maxWidth }, src: url, alt: value?.title })),
|
|
304
|
-
contentType?.startsWith('video/') && (React.createElement("video", { "data-testid": "attachment-video", style: { maxWidth: props.maxWidth }, controls: true },
|
|
305
|
-
React.createElement("source", { type: contentType, src: url }))),
|
|
306
|
-
contentType === 'application/pdf' && !title?.endsWith('.pdf') && (React.createElement("div", { "data-testid": "attachment-pdf", style: { maxWidth: props.maxWidth, minHeight: 400 } },
|
|
307
|
-
React.createElement("iframe", { width: "100%", height: "400", src: url + '#navpanes=0', allowFullScreen: true, frameBorder: 0, seamless: true }))),
|
|
308
|
-
React.createElement("div", { "data-testid": "download-link", style: { padding: '2px 16px 16px 16px' } },
|
|
309
|
-
React.createElement(core$1.Anchor, { href: value?.url, "data-testid": "attachment-details", target: "_blank", rel: "noopener noreferrer" }, value?.title || 'Download'))));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function AttachmentArrayDisplay(props) {
|
|
313
|
-
return (React.createElement("div", null, props.values &&
|
|
314
|
-
props.values.map((v, index) => (React.createElement("div", { key: 'attatchment-' + index },
|
|
315
|
-
React.createElement(AttachmentDisplay, { value: v, maxWidth: props.maxWidth }))))));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* @tabler/icons-react v2.16.0 - MIT
|
|
157
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
320
158
|
*/
|
|
321
159
|
|
|
322
160
|
var defaultAttributes = {
|
|
@@ -332,7 +170,7 @@
|
|
|
332
170
|
};
|
|
333
171
|
|
|
334
172
|
/**
|
|
335
|
-
* @tabler/icons-react v2.
|
|
173
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
336
174
|
*/
|
|
337
175
|
|
|
338
176
|
var __defProp = Object.defineProperty;
|
|
@@ -395,7 +233,7 @@
|
|
|
395
233
|
};
|
|
396
234
|
|
|
397
235
|
/**
|
|
398
|
-
* @tabler/icons-react v2.
|
|
236
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
399
237
|
*/
|
|
400
238
|
|
|
401
239
|
var IconAdjustmentsHorizontal = createReactComponent(
|
|
@@ -415,7 +253,7 @@
|
|
|
415
253
|
);
|
|
416
254
|
|
|
417
255
|
/**
|
|
418
|
-
* @tabler/icons-react v2.
|
|
256
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
419
257
|
*/
|
|
420
258
|
|
|
421
259
|
var IconAlertCircle = createReactComponent("alert-circle", "IconAlertCircle", [
|
|
@@ -425,7 +263,7 @@
|
|
|
425
263
|
]);
|
|
426
264
|
|
|
427
265
|
/**
|
|
428
|
-
* @tabler/icons-react v2.
|
|
266
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
429
267
|
*/
|
|
430
268
|
|
|
431
269
|
var IconBleachOff = createReactComponent("bleach-off", "IconBleachOff", [
|
|
@@ -440,7 +278,7 @@
|
|
|
440
278
|
]);
|
|
441
279
|
|
|
442
280
|
/**
|
|
443
|
-
* @tabler/icons-react v2.
|
|
281
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
444
282
|
*/
|
|
445
283
|
|
|
446
284
|
var IconBleach = createReactComponent("bleach", "IconBleach", [
|
|
@@ -454,7 +292,7 @@
|
|
|
454
292
|
]);
|
|
455
293
|
|
|
456
294
|
/**
|
|
457
|
-
* @tabler/icons-react v2.
|
|
295
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
458
296
|
*/
|
|
459
297
|
|
|
460
298
|
var IconBoxMultiple = createReactComponent("box-multiple", "IconBoxMultiple", [
|
|
@@ -475,7 +313,7 @@
|
|
|
475
313
|
]);
|
|
476
314
|
|
|
477
315
|
/**
|
|
478
|
-
* @tabler/icons-react v2.
|
|
316
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
479
317
|
*/
|
|
480
318
|
|
|
481
319
|
var IconBracketsContain = createReactComponent("brackets-contain", "IconBracketsContain", [
|
|
@@ -487,7 +325,7 @@
|
|
|
487
325
|
]);
|
|
488
326
|
|
|
489
327
|
/**
|
|
490
|
-
* @tabler/icons-react v2.
|
|
328
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
491
329
|
*/
|
|
492
330
|
|
|
493
331
|
var IconBucketOff = createReactComponent("bucket-off", "IconBucketOff", [
|
|
@@ -509,7 +347,7 @@
|
|
|
509
347
|
]);
|
|
510
348
|
|
|
511
349
|
/**
|
|
512
|
-
* @tabler/icons-react v2.
|
|
350
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
513
351
|
*/
|
|
514
352
|
|
|
515
353
|
var IconBucket = createReactComponent("bucket", "IconBucket", [
|
|
@@ -524,7 +362,7 @@
|
|
|
524
362
|
]);
|
|
525
363
|
|
|
526
364
|
/**
|
|
527
|
-
* @tabler/icons-react v2.
|
|
365
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
528
366
|
*/
|
|
529
367
|
|
|
530
368
|
var IconCalendar = createReactComponent("calendar", "IconCalendar", [
|
|
@@ -543,7 +381,7 @@
|
|
|
543
381
|
]);
|
|
544
382
|
|
|
545
383
|
/**
|
|
546
|
-
* @tabler/icons-react v2.
|
|
384
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
547
385
|
*/
|
|
548
386
|
|
|
549
387
|
var IconCheck = createReactComponent("check", "IconCheck", [
|
|
@@ -551,7 +389,7 @@
|
|
|
551
389
|
]);
|
|
552
390
|
|
|
553
391
|
/**
|
|
554
|
-
* @tabler/icons-react v2.
|
|
392
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
555
393
|
*/
|
|
556
394
|
|
|
557
395
|
var IconCheckbox = createReactComponent("checkbox", "IconCheckbox", [
|
|
@@ -566,7 +404,15 @@
|
|
|
566
404
|
]);
|
|
567
405
|
|
|
568
406
|
/**
|
|
569
|
-
* @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
|
|
570
416
|
*/
|
|
571
417
|
|
|
572
418
|
var IconCircleMinus = createReactComponent("circle-minus", "IconCircleMinus", [
|
|
@@ -575,7 +421,7 @@
|
|
|
575
421
|
]);
|
|
576
422
|
|
|
577
423
|
/**
|
|
578
|
-
* @tabler/icons-react v2.
|
|
424
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
579
425
|
*/
|
|
580
426
|
|
|
581
427
|
var IconCirclePlus = createReactComponent("circle-plus", "IconCirclePlus", [
|
|
@@ -585,7 +431,7 @@
|
|
|
585
431
|
]);
|
|
586
432
|
|
|
587
433
|
/**
|
|
588
|
-
* @tabler/icons-react v2.
|
|
434
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
589
435
|
*/
|
|
590
436
|
|
|
591
437
|
var IconCloudUpload = createReactComponent("cloud-upload", "IconCloudUpload", [
|
|
@@ -601,7 +447,7 @@
|
|
|
601
447
|
]);
|
|
602
448
|
|
|
603
449
|
/**
|
|
604
|
-
* @tabler/icons-react v2.
|
|
450
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
605
451
|
*/
|
|
606
452
|
|
|
607
453
|
var IconColumns = createReactComponent("columns", "IconColumns", [
|
|
@@ -616,7 +462,7 @@
|
|
|
616
462
|
]);
|
|
617
463
|
|
|
618
464
|
/**
|
|
619
|
-
* @tabler/icons-react v2.
|
|
465
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
620
466
|
*/
|
|
621
467
|
|
|
622
468
|
var IconCurrencyDollar = createReactComponent("currency-dollar", "IconCurrencyDollar", [
|
|
@@ -631,7 +477,7 @@
|
|
|
631
477
|
]);
|
|
632
478
|
|
|
633
479
|
/**
|
|
634
|
-
* @tabler/icons-react v2.
|
|
480
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
635
481
|
*/
|
|
636
482
|
|
|
637
483
|
var IconDots = createReactComponent("dots", "IconDots", [
|
|
@@ -641,7 +487,7 @@
|
|
|
641
487
|
]);
|
|
642
488
|
|
|
643
489
|
/**
|
|
644
|
-
* @tabler/icons-react v2.
|
|
490
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
645
491
|
*/
|
|
646
492
|
|
|
647
493
|
var IconEdit = createReactComponent("edit", "IconEdit", [
|
|
@@ -663,7 +509,7 @@
|
|
|
663
509
|
]);
|
|
664
510
|
|
|
665
511
|
/**
|
|
666
|
-
* @tabler/icons-react v2.
|
|
512
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
667
513
|
*/
|
|
668
514
|
|
|
669
515
|
var IconEqualNot = createReactComponent("equal-not", "IconEqualNot", [
|
|
@@ -673,7 +519,7 @@
|
|
|
673
519
|
]);
|
|
674
520
|
|
|
675
521
|
/**
|
|
676
|
-
* @tabler/icons-react v2.
|
|
522
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
677
523
|
*/
|
|
678
524
|
|
|
679
525
|
var IconEqual = createReactComponent("equal", "IconEqual", [
|
|
@@ -682,7 +528,7 @@
|
|
|
682
528
|
]);
|
|
683
529
|
|
|
684
530
|
/**
|
|
685
|
-
* @tabler/icons-react v2.
|
|
531
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
686
532
|
*/
|
|
687
533
|
|
|
688
534
|
var IconFileAlert = createReactComponent("file-alert", "IconFileAlert", [
|
|
@@ -699,7 +545,7 @@
|
|
|
699
545
|
]);
|
|
700
546
|
|
|
701
547
|
/**
|
|
702
|
-
* @tabler/icons-react v2.
|
|
548
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
703
549
|
*/
|
|
704
550
|
|
|
705
551
|
var IconFilePlus = createReactComponent("file-plus", "IconFilePlus", [
|
|
@@ -716,7 +562,7 @@
|
|
|
716
562
|
]);
|
|
717
563
|
|
|
718
564
|
/**
|
|
719
|
-
* @tabler/icons-react v2.
|
|
565
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
720
566
|
*/
|
|
721
567
|
|
|
722
568
|
var IconFilter = createReactComponent("filter", "IconFilter", [
|
|
@@ -730,7 +576,7 @@
|
|
|
730
576
|
]);
|
|
731
577
|
|
|
732
578
|
/**
|
|
733
|
-
* @tabler/icons-react v2.
|
|
579
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
734
580
|
*/
|
|
735
581
|
|
|
736
582
|
var IconListDetails = createReactComponent("list-details", "IconListDetails", [
|
|
@@ -755,7 +601,22 @@
|
|
|
755
601
|
]);
|
|
756
602
|
|
|
757
603
|
/**
|
|
758
|
-
* @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
|
|
759
620
|
*/
|
|
760
621
|
|
|
761
622
|
var IconMathGreater = createReactComponent("math-greater", "IconMathGreater", [
|
|
@@ -763,7 +624,7 @@
|
|
|
763
624
|
]);
|
|
764
625
|
|
|
765
626
|
/**
|
|
766
|
-
* @tabler/icons-react v2.
|
|
627
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
767
628
|
*/
|
|
768
629
|
|
|
769
630
|
var IconMathLower = createReactComponent("math-lower", "IconMathLower", [
|
|
@@ -771,7 +632,7 @@
|
|
|
771
632
|
]);
|
|
772
633
|
|
|
773
634
|
/**
|
|
774
|
-
* @tabler/icons-react v2.
|
|
635
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
775
636
|
*/
|
|
776
637
|
|
|
777
638
|
var IconMessage = createReactComponent("message", "IconMessage", [
|
|
@@ -787,7 +648,7 @@
|
|
|
787
648
|
]);
|
|
788
649
|
|
|
789
650
|
/**
|
|
790
|
-
* @tabler/icons-react v2.
|
|
651
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
791
652
|
*/
|
|
792
653
|
|
|
793
654
|
var IconPin = createReactComponent("pin", "IconPin", [
|
|
@@ -803,7 +664,7 @@
|
|
|
803
664
|
]);
|
|
804
665
|
|
|
805
666
|
/**
|
|
806
|
-
* @tabler/icons-react v2.
|
|
667
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
807
668
|
*/
|
|
808
669
|
|
|
809
670
|
var IconPinnedOff = createReactComponent("pinned-off", "IconPinnedOff", [
|
|
@@ -820,7 +681,16 @@
|
|
|
820
681
|
]);
|
|
821
682
|
|
|
822
683
|
/**
|
|
823
|
-
* @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
|
|
824
694
|
*/
|
|
825
695
|
|
|
826
696
|
var IconSettings = createReactComponent("settings", "IconSettings", [
|
|
@@ -835,7 +705,7 @@
|
|
|
835
705
|
]);
|
|
836
706
|
|
|
837
707
|
/**
|
|
838
|
-
* @tabler/icons-react v2.
|
|
708
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
839
709
|
*/
|
|
840
710
|
|
|
841
711
|
var IconSortAscending = createReactComponent("sort-ascending", "IconSortAscending", [
|
|
@@ -847,7 +717,7 @@
|
|
|
847
717
|
]);
|
|
848
718
|
|
|
849
719
|
/**
|
|
850
|
-
* @tabler/icons-react v2.
|
|
720
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
851
721
|
*/
|
|
852
722
|
|
|
853
723
|
var IconSortDescending = createReactComponent("sort-descending", "IconSortDescending", [
|
|
@@ -859,7 +729,7 @@
|
|
|
859
729
|
]);
|
|
860
730
|
|
|
861
731
|
/**
|
|
862
|
-
* @tabler/icons-react v2.
|
|
732
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
863
733
|
*/
|
|
864
734
|
|
|
865
735
|
var IconSquare = createReactComponent("square", "IconSquare", [
|
|
@@ -873,7 +743,22 @@
|
|
|
873
743
|
]);
|
|
874
744
|
|
|
875
745
|
/**
|
|
876
|
-
* @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
|
|
877
762
|
*/
|
|
878
763
|
|
|
879
764
|
var IconTableExport = createReactComponent("table-export", "IconTableExport", [
|
|
@@ -891,7 +776,7 @@
|
|
|
891
776
|
]);
|
|
892
777
|
|
|
893
778
|
/**
|
|
894
|
-
* @tabler/icons-react v2.
|
|
779
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
895
780
|
*/
|
|
896
781
|
|
|
897
782
|
var IconTrash = createReactComponent("trash", "IconTrash", [
|
|
@@ -906,7 +791,7 @@
|
|
|
906
791
|
]);
|
|
907
792
|
|
|
908
793
|
/**
|
|
909
|
-
* @tabler/icons-react v2.
|
|
794
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
910
795
|
*/
|
|
911
796
|
|
|
912
797
|
var IconX = createReactComponent("x", "IconX", [
|
|
@@ -914,730 +799,936 @@
|
|
|
914
799
|
["path", { d: "M6 6l12 12", key: "svg-1" }]
|
|
915
800
|
]);
|
|
916
801
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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 = {};
|
|
923
810
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const files = e.target.files;
|
|
927
|
-
if (files) {
|
|
928
|
-
Array.from(files).forEach(processFile);
|
|
929
|
-
}
|
|
811
|
+
static getDerivedStateFromError(error) {
|
|
812
|
+
return { error };
|
|
930
813
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (!file) {
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
const fileName = file.name;
|
|
941
|
-
if (!fileName) {
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
|
-
if (props.onUploadStart) {
|
|
945
|
-
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)));
|
|
946
820
|
}
|
|
947
|
-
|
|
948
|
-
const contentType = file.type || 'application/octet-stream';
|
|
949
|
-
medplum
|
|
950
|
-
.createBinary(file, filename, contentType, props.onUploadProgress)
|
|
951
|
-
.then((binary) => {
|
|
952
|
-
props.onUpload({
|
|
953
|
-
contentType: binary.contentType,
|
|
954
|
-
url: binary.url,
|
|
955
|
-
title: filename,
|
|
956
|
-
});
|
|
957
|
-
})
|
|
958
|
-
.catch((outcome) => {
|
|
959
|
-
alert(outcome?.issue?.[0]?.details?.text);
|
|
960
|
-
});
|
|
821
|
+
return this.props.children;
|
|
961
822
|
}
|
|
962
|
-
return (React.createElement(React.Fragment, null,
|
|
963
|
-
React.createElement("input", { type: "file", "data-testid": "upload-file-input", style: { display: 'none' }, ref: fileInputRef, onChange: (e) => onFileChange(e) }),
|
|
964
|
-
props.children({ onClick })));
|
|
965
823
|
}
|
|
966
824
|
|
|
967
|
-
function
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
valuesRef.current = values;
|
|
971
|
-
function setValuesWrapper(newValues) {
|
|
972
|
-
setValues(newValues);
|
|
973
|
-
if (props.onChange) {
|
|
974
|
-
props.onChange(newValues);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
return (React.createElement("table", { style: { width: '100%' } },
|
|
978
|
-
React.createElement("colgroup", null,
|
|
979
|
-
React.createElement("col", { width: "97%" }),
|
|
980
|
-
React.createElement("col", { width: "3%" })),
|
|
981
|
-
React.createElement("tbody", null,
|
|
982
|
-
values.map((v, index) => (React.createElement("tr", { key: `${index}-${values.length}` },
|
|
983
|
-
React.createElement("td", null,
|
|
984
|
-
React.createElement(AttachmentDisplay, { value: v, maxWidth: 200 })),
|
|
985
|
-
React.createElement("td", null,
|
|
986
|
-
React.createElement(core$1.ActionIcon, { title: "Remove", size: "sm", onClick: (e) => {
|
|
987
|
-
killEvent(e);
|
|
988
|
-
const copy = values.slice();
|
|
989
|
-
copy.splice(index, 1);
|
|
990
|
-
setValuesWrapper(copy);
|
|
991
|
-
} },
|
|
992
|
-
React.createElement(IconCircleMinus, null)))))),
|
|
993
|
-
React.createElement("tr", null,
|
|
994
|
-
React.createElement("td", null),
|
|
995
|
-
React.createElement("td", null,
|
|
996
|
-
React.createElement(AttachmentButton, { onUpload: (attachment) => {
|
|
997
|
-
setValuesWrapper([...valuesRef.current, attachment]);
|
|
998
|
-
} }, (props) => (React.createElement(core$1.ActionIcon, { ...props, title: "Add", size: "sm", color: "green" },
|
|
999
|
-
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)));
|
|
1000
828
|
}
|
|
1001
829
|
|
|
1002
|
-
function
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
if (props.onChange) {
|
|
1007
|
-
props.onChange(newValue);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
if (value) {
|
|
1011
|
-
return (React.createElement(React.Fragment, null,
|
|
1012
|
-
React.createElement(AttachmentDisplay, { value: value, maxWidth: 200 }),
|
|
1013
|
-
React.createElement(core$1.Button, { onClick: (e) => {
|
|
1014
|
-
killEvent(e);
|
|
1015
|
-
setValueWrapper(undefined);
|
|
1016
|
-
} }, "Remove")));
|
|
830
|
+
function HumanNameDisplay(props) {
|
|
831
|
+
const name = props.value;
|
|
832
|
+
if (!name) {
|
|
833
|
+
return null;
|
|
1017
834
|
}
|
|
1018
|
-
return
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
const useStyles$e = core$1.createStyles(() => ({
|
|
1022
|
-
root: {
|
|
1023
|
-
'@media (max-width: 800px)': {
|
|
1024
|
-
paddingLeft: 4,
|
|
1025
|
-
paddingRight: 4,
|
|
1026
|
-
},
|
|
1027
|
-
},
|
|
1028
|
-
}));
|
|
1029
|
-
function Container(props) {
|
|
1030
|
-
const { children, ...others } = props;
|
|
1031
|
-
const { classes } = useStyles$e();
|
|
1032
|
-
return (React.createElement(core$1.Container, { className: classes.root, ...others }, children));
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
const useStyles$d = core$1.createStyles((theme, { width, fill }) => ({
|
|
1036
|
-
paper: {
|
|
1037
|
-
maxWidth: width,
|
|
1038
|
-
margin: `${theme.spacing.xl} auto`,
|
|
1039
|
-
padding: fill ? 0 : theme.spacing.md,
|
|
1040
|
-
'@media (max-width: 800px)': {
|
|
1041
|
-
padding: fill ? 0 : 8,
|
|
1042
|
-
},
|
|
1043
|
-
'& img': {
|
|
1044
|
-
width: '100%',
|
|
1045
|
-
maxWidth: '100%',
|
|
1046
|
-
},
|
|
1047
|
-
'& video': {
|
|
1048
|
-
width: '100%',
|
|
1049
|
-
maxWidth: '100%',
|
|
1050
|
-
},
|
|
1051
|
-
},
|
|
1052
|
-
}));
|
|
1053
|
-
const defaultProps$1 = {
|
|
1054
|
-
shadow: 'xs',
|
|
1055
|
-
radius: 'md',
|
|
1056
|
-
withBorder: true,
|
|
1057
|
-
};
|
|
1058
|
-
function Panel(props) {
|
|
1059
|
-
const { className, children, width, fill, unstyled, ...others } = core$1.useComponentDefaultProps('Panel', defaultProps$1, props);
|
|
1060
|
-
const { classes, cx } = useStyles$d({ width, fill }, { name: 'Panel', unstyled });
|
|
1061
|
-
return (React.createElement(core$1.Paper, { className: cx(classes.paper, className), ...others }, children));
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
function Document(props) {
|
|
1065
|
-
const { children, ...others } = props;
|
|
1066
|
-
return (React.createElement(Container, null,
|
|
1067
|
-
React.createElement(Panel, { ...others }, children)));
|
|
835
|
+
return React.createElement(React.Fragment, null, core.formatHumanName(name, props.options));
|
|
1068
836
|
}
|
|
1069
837
|
|
|
1070
838
|
/**
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
839
|
+
* Kills a browser event.
|
|
840
|
+
* Prevents default behavior.
|
|
841
|
+
* Stops event propagation.
|
|
842
|
+
* @param e The event.
|
|
1073
843
|
*/
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (element instanceof HTMLInputElement) {
|
|
1078
|
-
parseInputElement(result, element);
|
|
1079
|
-
}
|
|
1080
|
-
else if (element instanceof HTMLTextAreaElement) {
|
|
1081
|
-
result[element.name] = element.value;
|
|
1082
|
-
}
|
|
1083
|
-
else if (element instanceof HTMLSelectElement) {
|
|
1084
|
-
parseSelectElement(result, element);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
return result;
|
|
844
|
+
function killEvent(e) {
|
|
845
|
+
e.preventDefault();
|
|
846
|
+
e.stopPropagation();
|
|
1088
847
|
}
|
|
1089
848
|
/**
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
* @
|
|
1094
|
-
* @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.
|
|
1095
853
|
*/
|
|
1096
|
-
function
|
|
1097
|
-
if (el
|
|
1098
|
-
|
|
1099
|
-
return;
|
|
854
|
+
function isCheckboxCell(el) {
|
|
855
|
+
if (isCheckboxElement(el)) {
|
|
856
|
+
return true;
|
|
1100
857
|
}
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1103
|
-
|
|
858
|
+
if (el instanceof HTMLTableCellElement) {
|
|
859
|
+
const children = el.children;
|
|
860
|
+
if (children.length === 1 && isCheckboxElement(children[0])) {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
1104
863
|
}
|
|
1105
|
-
|
|
864
|
+
return false;
|
|
1106
865
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
* Sets the name/value pair if one is selected.
|
|
1110
|
-
* @param result The result builder.
|
|
1111
|
-
* @param el The select element.
|
|
1112
|
-
*/
|
|
1113
|
-
function parseSelectElement(result, el) {
|
|
1114
|
-
result[el.name] = el.value;
|
|
866
|
+
function isCheckboxElement(el) {
|
|
867
|
+
return el instanceof HTMLInputElement && el.type === 'checkbox';
|
|
1115
868
|
}
|
|
1116
869
|
|
|
1117
|
-
function
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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);
|
|
1123
881
|
}
|
|
1124
|
-
|
|
882
|
+
else if (to) {
|
|
883
|
+
navigate(href);
|
|
884
|
+
}
|
|
885
|
+
}, ...rest }, children));
|
|
1125
886
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return '#';
|
|
1135
900
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
?.join('\n');
|
|
901
|
+
function getStringHref(to) {
|
|
902
|
+
if (to.startsWith('http://') || to.startsWith('https://') || to.startsWith('/')) {
|
|
903
|
+
return to;
|
|
904
|
+
}
|
|
905
|
+
return '/' + to;
|
|
1142
906
|
}
|
|
1143
|
-
function
|
|
1144
|
-
return
|
|
907
|
+
function getResourceHref(to) {
|
|
908
|
+
return `/${to.resourceType}/${to.id}`;
|
|
1145
909
|
}
|
|
1146
|
-
function
|
|
1147
|
-
|
|
1148
|
-
if (expr1 === expr2) {
|
|
1149
|
-
return true;
|
|
1150
|
-
}
|
|
1151
|
-
if (!expr1 || !expr2) {
|
|
1152
|
-
return false;
|
|
1153
|
-
}
|
|
1154
|
-
const dot1 = expr1.indexOf('.');
|
|
1155
|
-
if (dot1 >= 0 && expr1.substring(dot1 + 1) === expr2) {
|
|
1156
|
-
return true;
|
|
1157
|
-
}
|
|
1158
|
-
const dot2 = expr2.indexOf('.');
|
|
1159
|
-
if (dot2 >= 0 && expr2.substring(dot2 + 1) === expr1) {
|
|
1160
|
-
return true;
|
|
1161
|
-
}
|
|
1162
|
-
return false;
|
|
910
|
+
function getReferenceHref(to) {
|
|
911
|
+
return `/${to.reference}`;
|
|
1163
912
|
}
|
|
1164
913
|
|
|
1165
|
-
|
|
914
|
+
/**
|
|
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.
|
|
919
|
+
*/
|
|
920
|
+
function useResource(value, setOutcome) {
|
|
1166
921
|
const medplum = useMedplum();
|
|
1167
|
-
const [
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
922
|
+
const [resource, setResource] = React.useState(getInitialResource(medplum, value));
|
|
923
|
+
const setResourceIfChanged = React.useCallback((r) => {
|
|
924
|
+
if (!core.deepEquals(r, resource)) {
|
|
925
|
+
setResource(r);
|
|
926
|
+
}
|
|
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
|
+
}
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
return (() => (subscribed = false));
|
|
951
|
+
}, [medplum, resource, value, setResourceIfChanged, setOutcome]);
|
|
952
|
+
return resource;
|
|
1193
953
|
}
|
|
1194
|
-
|
|
1195
954
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
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.
|
|
1198
962
|
*/
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return undefined;
|
|
1206
973
|
}
|
|
1207
974
|
|
|
1208
|
-
function
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
const
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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 })));
|
|
986
|
+
}
|
|
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
|
|
1218
1014
|
return;
|
|
1219
1015
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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);
|
|
1226
1038
|
}
|
|
1227
|
-
if (
|
|
1228
|
-
|
|
1229
|
-
setButtonRendered(true);
|
|
1039
|
+
if (timerRef.current !== undefined) {
|
|
1040
|
+
window.clearTimeout(timerRef.current);
|
|
1230
1041
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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 }));
|
|
1236
1087
|
}
|
|
1237
|
-
function
|
|
1238
|
-
if (
|
|
1239
|
-
return
|
|
1088
|
+
function toDefaultItems(defaultValue) {
|
|
1089
|
+
if (!defaultValue) {
|
|
1090
|
+
return [];
|
|
1240
1091
|
}
|
|
1241
|
-
if (
|
|
1242
|
-
|
|
1243
|
-
const authorizedOrigins = "http://localhost:3000,http://127.0.0.1:3000,http://localhost:6006,http://127.0.0.1:6006,https://app.medplum.com,https://docs.medplum.com,https://storybook.medplum.com,https://graphiql.medplum.com,https://www.medplum.com"?.split(',') ?? [];
|
|
1244
|
-
if (authorizedOrigins.includes(origin)) {
|
|
1245
|
-
return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
|
|
1246
|
-
}
|
|
1092
|
+
if (Array.isArray(defaultValue)) {
|
|
1093
|
+
return defaultValue;
|
|
1247
1094
|
}
|
|
1248
|
-
return
|
|
1095
|
+
return [defaultValue];
|
|
1249
1096
|
}
|
|
1250
1097
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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;
|
|
1255
1151
|
}
|
|
1256
|
-
|
|
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, ' ');
|
|
1257
1231
|
}
|
|
1258
|
-
|
|
1259
1232
|
/**
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
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.
|
|
1263
1239
|
*/
|
|
1264
|
-
function
|
|
1265
|
-
|
|
1266
|
-
|
|
1240
|
+
function getResourcesFromResponse(response, query) {
|
|
1241
|
+
const resources = [];
|
|
1242
|
+
if (response.data.Patients1) {
|
|
1243
|
+
resources.push(...response.data.Patients1);
|
|
1244
|
+
}
|
|
1245
|
+
if (response.data.Patients2) {
|
|
1246
|
+
resources.push(...response.data.Patients2);
|
|
1267
1247
|
}
|
|
1248
|
+
if (response.data.ServiceRequestList) {
|
|
1249
|
+
resources.push(...response.data.ServiceRequestList);
|
|
1250
|
+
}
|
|
1251
|
+
return sortByRelevance(dedupeResources(resources), query).slice(0, 5);
|
|
1268
1252
|
}
|
|
1269
1253
|
/**
|
|
1270
|
-
*
|
|
1271
|
-
* @param
|
|
1272
|
-
* @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.
|
|
1273
1257
|
*/
|
|
1274
|
-
function
|
|
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;
|
|
1268
|
+
}
|
|
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);
|
|
1284
1278
|
});
|
|
1285
1279
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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));
|
|
1296
1292
|
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
googleClientId && (React.createElement(React.Fragment, null,
|
|
1322
|
-
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
1323
|
-
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: async (response) => {
|
|
1324
|
-
try {
|
|
1325
|
-
props.handleAuthResponse(await medplum.startGoogleLogin({
|
|
1326
|
-
googleClientId: response.clientId,
|
|
1327
|
-
googleCredential: response.credential,
|
|
1328
|
-
createUser: true,
|
|
1329
|
-
}));
|
|
1330
|
-
}
|
|
1331
|
-
catch (err) {
|
|
1332
|
-
setOutcome(err);
|
|
1333
|
-
}
|
|
1334
|
-
} })),
|
|
1335
|
-
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
1336
|
-
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
1337
|
-
React.createElement(core$1.TextInput, { name: "firstName", type: "text", label: "First name", placeholder: "First name", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
1338
|
-
React.createElement(core$1.TextInput, { name: "lastName", type: "text", label: "Last name", placeholder: "Last name", required: true, error: getErrorsForInput(outcome, 'lastName') }),
|
|
1339
|
-
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, error: getErrorsForInput(outcome, 'email') }),
|
|
1340
|
-
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') }),
|
|
1341
|
-
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
1342
|
-
"By clicking submit you agree to the Medplum",
|
|
1343
|
-
' ',
|
|
1344
|
-
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
1345
|
-
' and ',
|
|
1346
|
-
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
1347
|
-
"."),
|
|
1348
|
-
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
1349
|
-
"This site is protected by reCAPTCHA and the Google",
|
|
1350
|
-
' ',
|
|
1351
|
-
React.createElement(core$1.Anchor, { href: "https://policies.google.com/privacy" }, "Privacy\u00A0Policy"),
|
|
1352
|
-
' and ',
|
|
1353
|
-
React.createElement(core$1.Anchor, { href: "https://policies.google.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
1354
|
-
" apply.")),
|
|
1355
|
-
React.createElement(core$1.Group, { position: "apart", mt: "xl", noWrap: true },
|
|
1356
|
-
React.createElement(core$1.Checkbox, { name: "remember", label: "Remember me", size: "xs" }),
|
|
1357
|
-
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;
|
|
1358
1317
|
}
|
|
1359
1318
|
|
|
1360
|
-
|
|
1361
|
-
|
|
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) {
|
|
1362
1413
|
const medplum = useMedplum();
|
|
1363
|
-
const
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
.
|
|
1372
|
-
|
|
1373
|
-
}, [medplum, type, projectId, login, onSuccess]);
|
|
1374
|
-
function handleAuthResponse(response) {
|
|
1375
|
-
if (response.code) {
|
|
1376
|
-
medplum
|
|
1377
|
-
.processCode(response.code)
|
|
1378
|
-
.then(() => onSuccess())
|
|
1379
|
-
.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
|
+
}
|
|
1380
1424
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
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);
|
|
1383
1441
|
}
|
|
1384
1442
|
}
|
|
1385
|
-
return (React.createElement(
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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;
|
|
1389
1450
|
}
|
|
1390
1451
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
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
|
+
}
|
|
1395
1508
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1509
|
+
function navigateResourceType(resourceType) {
|
|
1510
|
+
if (resourceType) {
|
|
1511
|
+
navigate(`/${resourceType}`);
|
|
1512
|
+
}
|
|
1398
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));
|
|
1399
1534
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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) {
|
|
1406
1544
|
return false;
|
|
1407
1545
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
domain: authMethod.domain,
|
|
1411
|
-
});
|
|
1412
|
-
const url = new URL(authMethod.authorizeUrl);
|
|
1413
|
-
url.searchParams.set('state', state);
|
|
1414
|
-
window.location.assign(url.toString());
|
|
1415
|
-
return true;
|
|
1416
|
-
}, [medplum, baseLoginRequest]);
|
|
1417
|
-
const handleSubmit = React.useCallback(async (formData) => {
|
|
1418
|
-
const authMethod = await medplum.post('auth/method', { email: formData.email });
|
|
1419
|
-
if (!(await isExternalAuth(authMethod))) {
|
|
1420
|
-
setEmail(formData.email);
|
|
1421
|
-
}
|
|
1422
|
-
}, [medplum, isExternalAuth, setEmail]);
|
|
1423
|
-
const handleGoogleCredential = React.useCallback(async (response) => {
|
|
1424
|
-
const authResponse = await medplum.startGoogleLogin({
|
|
1425
|
-
...baseLoginRequest,
|
|
1426
|
-
googleCredential: response.credential,
|
|
1427
|
-
});
|
|
1428
|
-
if (!(await isExternalAuth(authResponse))) {
|
|
1429
|
-
handleAuthResponse(authResponse);
|
|
1430
|
-
}
|
|
1431
|
-
}, [medplum, baseLoginRequest, isExternalAuth, handleAuthResponse]);
|
|
1432
|
-
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
1433
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
1434
|
-
googleClientId && (React.createElement(React.Fragment, null,
|
|
1435
|
-
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
1436
|
-
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: handleGoogleCredential })),
|
|
1437
|
-
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
1438
|
-
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true }),
|
|
1439
|
-
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
1440
|
-
React.createElement("div", null, onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register"))),
|
|
1441
|
-
React.createElement(core$1.Button, { type: "submit" }, "Next"))));
|
|
1546
|
+
}
|
|
1547
|
+
return true;
|
|
1442
1548
|
}
|
|
1443
|
-
function
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const handleSubmit = React.useCallback((formData) => {
|
|
1449
|
-
medplum
|
|
1450
|
-
.startLogin({
|
|
1451
|
-
...baseLoginRequest,
|
|
1452
|
-
password: formData.password,
|
|
1453
|
-
remember: formData.remember === 'on',
|
|
1454
|
-
})
|
|
1455
|
-
.then(handleAuthResponse)
|
|
1456
|
-
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
1457
|
-
}, [medplum, baseLoginRequest, handleAuthResponse]);
|
|
1458
|
-
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
1459
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
1460
|
-
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
1461
|
-
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
1462
|
-
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') })),
|
|
1463
|
-
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
1464
|
-
onForgotPassword && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onForgotPassword, size: "xs" }, "Forgot password")),
|
|
1465
|
-
React.createElement(core$1.Checkbox, { id: "remember", name: "remember", label: "Remember me", size: "xs", sx: { lineHeight: 1 } }),
|
|
1466
|
-
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 });
|
|
1467
1554
|
}
|
|
1468
1555
|
|
|
1469
|
-
function
|
|
1556
|
+
function AppShell(props) {
|
|
1557
|
+
const theme = core$1.useMantineTheme();
|
|
1558
|
+
const [navbarOpen, setNavbarOpen] = React.useState(localStorage['navbarOpen'] === 'true');
|
|
1470
1559
|
const medplum = useMedplum();
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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))));
|
|
1491
1581
|
}
|
|
1492
1582
|
|
|
1493
|
-
function
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
} },
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
React.createElement(core$1.Title, null, "Choose scope")),
|
|
1508
|
-
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 })))),
|
|
1509
|
-
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
1510
|
-
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'))));
|
|
1511
1597
|
}
|
|
1512
1598
|
|
|
1513
|
-
function
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
setErrorMessage(undefined);
|
|
1518
|
-
medplum
|
|
1519
|
-
.post('auth/mfa/verify', {
|
|
1520
|
-
login: props.login,
|
|
1521
|
-
token: formData.token,
|
|
1522
|
-
})
|
|
1523
|
-
.then(props.handleAuthResponse)
|
|
1524
|
-
.catch((err) => setErrorMessage(core.normalizeErrorString(err)));
|
|
1525
|
-
} },
|
|
1526
|
-
React.createElement(core$1.Stack, null,
|
|
1527
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
1528
|
-
React.createElement(Logo, { size: 32 }),
|
|
1529
|
-
React.createElement(core$1.Title, null, "Enter MFA code")),
|
|
1530
|
-
errorMessage && (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Error", color: "red" }, errorMessage)),
|
|
1531
|
-
React.createElement(core$1.Stack, null,
|
|
1532
|
-
React.createElement(core$1.TextInput, { name: "token", label: "MFA code", required: true })),
|
|
1533
|
-
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
1534
|
-
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 }))))));
|
|
1535
1603
|
}
|
|
1536
1604
|
|
|
1537
|
-
|
|
1538
|
-
* The SignInForm component allows users to sign in to Medplum.
|
|
1539
|
-
*
|
|
1540
|
-
* "Signing in" is a multi-step process:
|
|
1541
|
-
* 1) Authentication - identify the user
|
|
1542
|
-
* 2) MFA - If MFA is enabled, prompt for MFA code
|
|
1543
|
-
* 3) Choose profile - If the user has multiple profiles, prompt to choose one
|
|
1544
|
-
* 4) Choose scope - If the user has multiple scopes, prompt to choose one
|
|
1545
|
-
* 5) Success - Return to the caller with either a code or a redirect
|
|
1546
|
-
*/
|
|
1547
|
-
function SignInForm(props) {
|
|
1548
|
-
const { chooseScopes, onSuccess, onForgotPassword, onRegister, onCode, ...baseLoginRequest } = props;
|
|
1605
|
+
function AttachmentButton(props) {
|
|
1549
1606
|
const medplum = useMedplum();
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
.then(() => {
|
|
1561
|
-
if (onSuccess) {
|
|
1562
|
-
onSuccess();
|
|
1563
|
-
}
|
|
1564
|
-
})
|
|
1565
|
-
.catch(console.log);
|
|
1566
|
-
}
|
|
1567
|
-
}, [medplum, onCode, onSuccess]);
|
|
1568
|
-
const handleAuthResponse = React.useCallback((response) => {
|
|
1569
|
-
setAuthenticatorRequired(!!response.mfaRequired);
|
|
1570
|
-
if (response.login) {
|
|
1571
|
-
setLogin(response.login);
|
|
1572
|
-
}
|
|
1573
|
-
if (response.memberships) {
|
|
1574
|
-
setMemberships(response.memberships);
|
|
1575
|
-
}
|
|
1576
|
-
if (response.code) {
|
|
1577
|
-
if (chooseScopes) {
|
|
1578
|
-
setMemberships(undefined);
|
|
1579
|
-
}
|
|
1580
|
-
else {
|
|
1581
|
-
handleCode(response.code);
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
}, [chooseScopes, handleCode]);
|
|
1585
|
-
const handleScopeResponse = React.useCallback((response) => {
|
|
1586
|
-
handleCode(response.code);
|
|
1587
|
-
}, [handleCode]);
|
|
1588
|
-
React.useEffect(() => {
|
|
1589
|
-
if (props.login) {
|
|
1590
|
-
medplum
|
|
1591
|
-
.get('auth/login/' + props.login)
|
|
1592
|
-
.then(handleAuthResponse)
|
|
1593
|
-
.catch(console.error);
|
|
1594
|
-
}
|
|
1595
|
-
}, [medplum, props, handleAuthResponse]);
|
|
1596
|
-
return (React.createElement(Document, { width: 450 }, (() => {
|
|
1597
|
-
if (!login) {
|
|
1598
|
-
return (React.createElement(AuthenticationForm, { onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, disableGoogleAuth: props.disableGoogleAuth, ...baseLoginRequest }, props.children));
|
|
1599
|
-
}
|
|
1600
|
-
else if (mfaRequired) {
|
|
1601
|
-
return React.createElement(MfaForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
1602
|
-
}
|
|
1603
|
-
else if (memberships) {
|
|
1604
|
-
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);
|
|
1605
1617
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
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;
|
|
1608
1627
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1628
|
+
const fileName = file.name;
|
|
1629
|
+
if (!fileName) {
|
|
1630
|
+
return;
|
|
1611
1631
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1632
|
+
if (props.onUploadStart) {
|
|
1633
|
+
props.onUploadStart();
|
|
1614
1634
|
}
|
|
1615
|
-
|
|
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 })));
|
|
1616
1653
|
}
|
|
1617
1654
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
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%',
|
|
1641
1732
|
'& > dt, & > dd': {
|
|
1642
1733
|
padding: `0 ${theme.spacing.xs} ${theme.spacing.xs} 0`,
|
|
1643
1734
|
border: 0,
|
|
@@ -1646,7 +1737,7 @@
|
|
|
1646
1737
|
}));
|
|
1647
1738
|
function DescriptionList(props) {
|
|
1648
1739
|
const { children, compact } = props;
|
|
1649
|
-
const { classes, cx } = useStyles$
|
|
1740
|
+
const { classes, cx } = useStyles$e();
|
|
1650
1741
|
return React.createElement("dl", { className: cx(classes.root, { [classes.compact]: compact }) }, children);
|
|
1651
1742
|
}
|
|
1652
1743
|
function DescriptionListEntry(props) {
|
|
@@ -1699,14 +1790,6 @@
|
|
|
1699
1790
|
contactDetail.telecom?.map((telecom, index) => (React.createElement(ContactPointDisplay, { key: 'telecom-' + index, value: telecom })))));
|
|
1700
1791
|
}
|
|
1701
1792
|
|
|
1702
|
-
function HumanNameDisplay(props) {
|
|
1703
|
-
const name = props.value;
|
|
1704
|
-
if (!name) {
|
|
1705
|
-
return null;
|
|
1706
|
-
}
|
|
1707
|
-
return React.createElement(React.Fragment, null, core.formatHumanName(name, props.options));
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
1793
|
function IdentifierDisplay(props) {
|
|
1711
1794
|
return (React.createElement("div", null,
|
|
1712
1795
|
props.value?.system,
|
|
@@ -1737,50 +1820,6 @@
|
|
|
1737
1820
|
React.createElement(QuantityDisplay, { value: value.denominator })));
|
|
1738
1821
|
}
|
|
1739
1822
|
|
|
1740
|
-
function MedplumLink(props) {
|
|
1741
|
-
const navigate = useMedplumNavigate();
|
|
1742
|
-
const { to, suffix, label, onClick, children, ...rest } = props;
|
|
1743
|
-
let href = getHref(to);
|
|
1744
|
-
if (suffix) {
|
|
1745
|
-
href += '/' + suffix;
|
|
1746
|
-
}
|
|
1747
|
-
return (React.createElement(core$1.Anchor, { href: href, "aria-label": label, onClick: (e) => {
|
|
1748
|
-
killEvent(e);
|
|
1749
|
-
if (onClick) {
|
|
1750
|
-
onClick();
|
|
1751
|
-
}
|
|
1752
|
-
else if (to) {
|
|
1753
|
-
navigate(href);
|
|
1754
|
-
}
|
|
1755
|
-
}, ...rest }, children));
|
|
1756
|
-
}
|
|
1757
|
-
function getHref(to) {
|
|
1758
|
-
if (to) {
|
|
1759
|
-
if (typeof to === 'string') {
|
|
1760
|
-
return getStringHref(to);
|
|
1761
|
-
}
|
|
1762
|
-
else if (core.isResource(to)) {
|
|
1763
|
-
return getResourceHref(to);
|
|
1764
|
-
}
|
|
1765
|
-
else if (core.isReference(to)) {
|
|
1766
|
-
return getReferenceHref(to);
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
return '#';
|
|
1770
|
-
}
|
|
1771
|
-
function getStringHref(to) {
|
|
1772
|
-
if (to.startsWith('http://') || to.startsWith('https://') || to.startsWith('/')) {
|
|
1773
|
-
return to;
|
|
1774
|
-
}
|
|
1775
|
-
return '/' + to;
|
|
1776
|
-
}
|
|
1777
|
-
function getResourceHref(to) {
|
|
1778
|
-
return `/${to.resourceType}/${to.id}`;
|
|
1779
|
-
}
|
|
1780
|
-
function getReferenceHref(to) {
|
|
1781
|
-
return `/${to.reference}`;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
1823
|
function ReferenceDisplay(props) {
|
|
1785
1824
|
if (!props.value) {
|
|
1786
1825
|
return null;
|
|
@@ -1947,69 +1986,36 @@
|
|
|
1947
1986
|
React.createElement(core$1.Input.Wrapper, { id: props.htmlFor, label: props.title, description: props.description, withAsterisk: props.withAsterisk }, (() => null)()))));
|
|
1948
1987
|
}
|
|
1949
1988
|
|
|
1950
|
-
function
|
|
1951
|
-
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');
|
|
1952
1994
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
* React Hook to use a FHIR reference.
|
|
1956
|
-
* Handles the complexity of resolving references and caching resources.
|
|
1957
|
-
* @param value The resource or reference to resource.
|
|
1958
|
-
* @returns The resolved resource.
|
|
1959
|
-
*/
|
|
1960
|
-
function useResource(value, setOutcome) {
|
|
1961
|
-
const medplum = useMedplum();
|
|
1962
|
-
const [resource, setResource] = React.useState(getInitialResource(medplum, value));
|
|
1963
|
-
const setResourceIfChanged = React.useCallback((r) => {
|
|
1964
|
-
if (!core.deepEquals(r, resource)) {
|
|
1965
|
-
setResource(r);
|
|
1966
|
-
}
|
|
1967
|
-
}, [resource, setResource]);
|
|
1968
|
-
React.useEffect(() => {
|
|
1969
|
-
setResourceIfChanged(getInitialResource(medplum, value));
|
|
1970
|
-
}, [medplum, value, setResourceIfChanged]);
|
|
1971
|
-
React.useEffect(() => {
|
|
1972
|
-
let subscribed = true;
|
|
1973
|
-
if (core.isReference(value)) {
|
|
1974
|
-
medplum
|
|
1975
|
-
.readReference(value)
|
|
1976
|
-
.then((r) => {
|
|
1977
|
-
if (subscribed) {
|
|
1978
|
-
setResourceIfChanged(r);
|
|
1979
|
-
}
|
|
1980
|
-
})
|
|
1981
|
-
.catch((err) => {
|
|
1982
|
-
if (subscribed) {
|
|
1983
|
-
setResourceIfChanged(undefined);
|
|
1984
|
-
if (setOutcome) {
|
|
1985
|
-
setOutcome(core.normalizeOperationOutcome(err));
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
});
|
|
1989
|
-
}
|
|
1990
|
-
return (() => (subscribed = false));
|
|
1991
|
-
}, [medplum, resource, value, setResourceIfChanged, setOutcome]);
|
|
1992
|
-
return resource;
|
|
1995
|
+
function getIssuesForExpression(outcome, expression) {
|
|
1996
|
+
return outcome?.issue?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression));
|
|
1993
1997
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
* Otherwise, returns undefined.
|
|
1999
|
-
* @param medplum The medplum client.
|
|
2000
|
-
* @param value The resource or reference to resource.
|
|
2001
|
-
* @returns An initial resource if available; undefined otherwise.
|
|
2002
|
-
*/
|
|
2003
|
-
function getInitialResource(medplum, value) {
|
|
2004
|
-
if (value) {
|
|
2005
|
-
if (core.isResource(value)) {
|
|
2006
|
-
return value;
|
|
2007
|
-
}
|
|
2008
|
-
if (core.isReference(value)) {
|
|
2009
|
-
return medplum.getCachedReference(value);
|
|
2010
|
-
}
|
|
1998
|
+
function isExpressionMatch(expr1, expr2) {
|
|
1999
|
+
// Expression can be either "fieldName" or "resourceType.fieldName"
|
|
2000
|
+
if (expr1 === expr2) {
|
|
2001
|
+
return true;
|
|
2011
2002
|
}
|
|
2012
|
-
|
|
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));
|
|
2013
2019
|
}
|
|
2014
2020
|
|
|
2015
2021
|
function ResourceForm(props) {
|
|
@@ -2058,46 +2064,6 @@
|
|
|
2058
2064
|
return obj;
|
|
2059
2065
|
}
|
|
2060
2066
|
|
|
2061
|
-
function toKey(element) {
|
|
2062
|
-
return element.code;
|
|
2063
|
-
}
|
|
2064
|
-
function toOption(element) {
|
|
2065
|
-
return {
|
|
2066
|
-
value: element.code,
|
|
2067
|
-
label: getDisplay(element),
|
|
2068
|
-
resource: element,
|
|
2069
|
-
};
|
|
2070
|
-
}
|
|
2071
|
-
function createValue(input) {
|
|
2072
|
-
return {
|
|
2073
|
-
code: input,
|
|
2074
|
-
display: input,
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
/**
|
|
2078
|
-
* A low-level component to autocomplete based on a FHIR Valueset.
|
|
2079
|
-
*/
|
|
2080
|
-
function ValueSetAutocomplete(props) {
|
|
2081
|
-
const medplum = useMedplum();
|
|
2082
|
-
const { elementDefinition, creatable, clearable, ...rest } = props;
|
|
2083
|
-
const loadValues = React.useCallback(async (input, signal) => {
|
|
2084
|
-
const system = elementDefinition.binding?.valueSet;
|
|
2085
|
-
const valueSet = await medplum.searchValueSet(system, input, { signal });
|
|
2086
|
-
const valueSetElements = valueSet.expansion?.contains;
|
|
2087
|
-
const newData = [];
|
|
2088
|
-
for (const valueSetElement of valueSetElements) {
|
|
2089
|
-
if (valueSetElement.code && !newData.some((item) => item.code === valueSetElement.code)) {
|
|
2090
|
-
newData.push(valueSetElement);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
return newData;
|
|
2094
|
-
}, [medplum, elementDefinition]);
|
|
2095
|
-
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}` }));
|
|
2096
|
-
}
|
|
2097
|
-
function getDisplay(item) {
|
|
2098
|
-
return item.display || item.code || '';
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
2067
|
function CodeableConceptInput(props) {
|
|
2102
2068
|
const [value, setValue] = React.useState(props.defaultValue);
|
|
2103
2069
|
function handleChange(newValues) {
|
|
@@ -2129,26 +2095,7 @@
|
|
|
2129
2095
|
};
|
|
2130
2096
|
}
|
|
2131
2097
|
|
|
2132
|
-
function
|
|
2133
|
-
const [value, setValue] = React.useState(props.defaultValue);
|
|
2134
|
-
function handleChange(newValues) {
|
|
2135
|
-
const newValue = newValues[0];
|
|
2136
|
-
const newCode = valueSetElementToCode(newValue);
|
|
2137
|
-
setValue(newCode);
|
|
2138
|
-
if (props.onChange) {
|
|
2139
|
-
props.onChange(newCode);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
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 }));
|
|
2143
|
-
}
|
|
2144
|
-
function codeToValueSetElement(code) {
|
|
2145
|
-
return code ? { code } : undefined;
|
|
2146
|
-
}
|
|
2147
|
-
function valueSetElementToCode(element) {
|
|
2148
|
-
return element?.code;
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
function CodingInput(props) {
|
|
2098
|
+
function CodingInput(props) {
|
|
2152
2099
|
const [value, setValue] = React.useState(props.defaultValue);
|
|
2153
2100
|
function handleChange(newValues) {
|
|
2154
2101
|
const newValue = newValues[0];
|
|
@@ -2518,21 +2465,6 @@
|
|
|
2518
2465
|
}) })));
|
|
2519
2466
|
}
|
|
2520
2467
|
|
|
2521
|
-
function ResourceAvatar(props) {
|
|
2522
|
-
const resource = useResource(props.value);
|
|
2523
|
-
const text = resource ? core.getDisplayString(resource) : props.alt ?? '';
|
|
2524
|
-
const imageUrl = (resource && core.getImageSrc(resource)) ?? props.src;
|
|
2525
|
-
const radius = props.radius ?? 'xl';
|
|
2526
|
-
const avatarProps = { ...props };
|
|
2527
|
-
delete avatarProps.value;
|
|
2528
|
-
delete avatarProps.link;
|
|
2529
|
-
if (props.link) {
|
|
2530
|
-
return (React.createElement(MedplumLink, { to: resource },
|
|
2531
|
-
React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps })));
|
|
2532
|
-
}
|
|
2533
|
-
return React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps });
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
2468
|
/**
|
|
2537
2469
|
* Defines which search parameters will be used by the type ahead to search for each resourceType
|
|
2538
2470
|
*/
|
|
@@ -2924,7 +2856,7 @@
|
|
|
2924
2856
|
})));
|
|
2925
2857
|
}
|
|
2926
2858
|
|
|
2927
|
-
const useStyles$
|
|
2859
|
+
const useStyles$d = core$1.createStyles((theme) => ({
|
|
2928
2860
|
table: {
|
|
2929
2861
|
width: 350,
|
|
2930
2862
|
'& th': {
|
|
@@ -2969,7 +2901,7 @@
|
|
|
2969
2901
|
return date.toLocaleString('default', { month: 'long' }) + ' ' + date.getFullYear();
|
|
2970
2902
|
}
|
|
2971
2903
|
function CalendarInput(props) {
|
|
2972
|
-
const { classes } = useStyles$
|
|
2904
|
+
const { classes } = useStyles$d();
|
|
2973
2905
|
const { onChangeMonth, onClick } = props;
|
|
2974
2906
|
const [month, setMonth] = React.useState(getStartMonth);
|
|
2975
2907
|
function moveMonth(delta) {
|
|
@@ -3052,13 +2984,27 @@
|
|
|
3052
2984
|
return false;
|
|
3053
2985
|
}
|
|
3054
2986
|
|
|
3055
|
-
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) => ({
|
|
3056
3002
|
noteBody: { fontSize: theme.fontSizes.sm },
|
|
3057
3003
|
noteCite: { fontSize: theme.fontSizes.xs, marginBlockStart: 3 },
|
|
3058
3004
|
noteRoot: { padding: 5 },
|
|
3059
3005
|
}));
|
|
3060
3006
|
function NoteDisplay({ value }) {
|
|
3061
|
-
const { classes } = useStyles$
|
|
3007
|
+
const { classes } = useStyles$b();
|
|
3062
3008
|
if (!value) {
|
|
3063
3009
|
return null;
|
|
3064
3010
|
}
|
|
@@ -3151,7 +3097,7 @@
|
|
|
3151
3097
|
return React.createElement(core$1.Badge, { color: statusToColor[props.status] }, props.status);
|
|
3152
3098
|
}
|
|
3153
3099
|
|
|
3154
|
-
const useStyles$
|
|
3100
|
+
const useStyles$a = core$1.createStyles((theme) => ({
|
|
3155
3101
|
table: {
|
|
3156
3102
|
border: `0.1px solid ${theme.colors.gray[5]}`,
|
|
3157
3103
|
borderCollapse: 'collapse',
|
|
@@ -3246,7 +3192,7 @@
|
|
|
3246
3192
|
core.formatDateTime(specimen.receivedTime)))))))));
|
|
3247
3193
|
}
|
|
3248
3194
|
function ObservationTable(props) {
|
|
3249
|
-
const { classes } = useStyles$
|
|
3195
|
+
const { classes } = useStyles$a();
|
|
3250
3196
|
return (React.createElement("table", { className: classes.table },
|
|
3251
3197
|
React.createElement("thead", null,
|
|
3252
3198
|
React.createElement("tr", null,
|
|
@@ -3260,7 +3206,7 @@
|
|
|
3260
3206
|
React.createElement("tbody", null, props.value?.map((observation, index) => (React.createElement(ObservationRow, { key: `obs-${observation.id}-${index}`, hideObservationNotes: props.hideObservationNotes, value: observation }))))));
|
|
3261
3207
|
}
|
|
3262
3208
|
function ObservationRow(props) {
|
|
3263
|
-
const { classes, cx } = useStyles$
|
|
3209
|
+
const { classes, cx } = useStyles$a();
|
|
3264
3210
|
const observation = useResource(props.value);
|
|
3265
3211
|
if (!observation) {
|
|
3266
3212
|
return null;
|
|
@@ -3310,6 +3256,92 @@
|
|
|
3310
3256
|
return code === 'AA' || code === 'LL' || code === 'HH' || code === 'A';
|
|
3311
3257
|
}
|
|
3312
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
|
+
|
|
3313
3345
|
const useStyles$8 = core$1.createStyles((theme) => ({
|
|
3314
3346
|
root: {
|
|
3315
3347
|
borderCollapse: 'collapse',
|
|
@@ -3400,29 +3432,6 @@
|
|
|
3400
3432
|
}, ignoreMissingValues: props.ignoreMissingValues }));
|
|
3401
3433
|
}
|
|
3402
3434
|
|
|
3403
|
-
/**
|
|
3404
|
-
* ErrorBoundary is a React component that handles errors in its child components.
|
|
3405
|
-
* See: https://reactjs.org/docs/error-boundaries.html
|
|
3406
|
-
*/
|
|
3407
|
-
class ErrorBoundary extends React.Component {
|
|
3408
|
-
constructor(props) {
|
|
3409
|
-
super(props);
|
|
3410
|
-
this.state = {};
|
|
3411
|
-
}
|
|
3412
|
-
static getDerivedStateFromError(error) {
|
|
3413
|
-
return { error };
|
|
3414
|
-
}
|
|
3415
|
-
componentDidCatch(error, errorInfo) {
|
|
3416
|
-
console.error('Uncaught error:', error, errorInfo);
|
|
3417
|
-
}
|
|
3418
|
-
render() {
|
|
3419
|
-
if (this.state.error) {
|
|
3420
|
-
return (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Something went wrong", color: "red" }, core.normalizeErrorString(this.state.error)));
|
|
3421
|
-
}
|
|
3422
|
-
return this.props.children;
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
3435
|
function Timeline(props) {
|
|
3427
3436
|
return React.createElement(Container, null, props.children);
|
|
3428
3437
|
}
|
|
@@ -3787,6 +3796,12 @@
|
|
|
3787
3796
|
} }));
|
|
3788
3797
|
}
|
|
3789
3798
|
|
|
3799
|
+
function Document(props) {
|
|
3800
|
+
const { children, ...others } = props;
|
|
3801
|
+
return (React.createElement(Container, null,
|
|
3802
|
+
React.createElement(Panel, { ...others }, children)));
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3790
3805
|
function EncounterTimeline(props) {
|
|
3791
3806
|
return (React.createElement(ResourceTimeline, { value: props.encounter, loadTimelineResources: async (medplum, _resourceType, id) => {
|
|
3792
3807
|
return Promise.allSettled([
|
|
@@ -5056,8 +5071,8 @@
|
|
|
5056
5071
|
return (React.createElement("div", { className: classes.root, "data-testid": "search-control" },
|
|
5057
5072
|
!props.hideToolbar && (React.createElement(core$1.Group, { position: "apart", mb: "xl" },
|
|
5058
5073
|
React.createElement(core$1.Group, { spacing: 2 },
|
|
5059
|
-
React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(
|
|
5060
|
-
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"),
|
|
5061
5076
|
props.onNew && (React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconFilePlus, { size: iconSize }), onClick: props.onNew }, "New...")),
|
|
5062
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...")),
|
|
5063
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...")),
|
|
@@ -5293,6 +5308,24 @@
|
|
|
5293
5308
|
}
|
|
5294
5309
|
const MemoizedFhirPathTable = React.memo(FhirPathTable);
|
|
5295
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
|
+
|
|
5296
5329
|
function PatientTimeline(props) {
|
|
5297
5330
|
const loadTimelineResources = React.useCallback((medplum, _resourceType, id) => {
|
|
5298
5331
|
return Promise.allSettled([
|
|
@@ -6918,9 +6951,455 @@
|
|
|
6918
6951
|
}) }));
|
|
6919
6952
|
}
|
|
6920
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
|
+
|
|
6921
7399
|
exports.AddressDisplay = AddressDisplay;
|
|
6922
7400
|
exports.AddressInput = AddressInput;
|
|
6923
7401
|
exports.AnnotationInput = AnnotationInput;
|
|
7402
|
+
exports.AppShell = AppShell;
|
|
6924
7403
|
exports.AsyncAutocomplete = AsyncAutocomplete;
|
|
6925
7404
|
exports.AttachmentArrayDisplay = AttachmentArrayDisplay;
|
|
6926
7405
|
exports.AttachmentArrayInput = AttachmentArrayInput;
|
|
@@ -6954,10 +7433,12 @@
|
|
|
6954
7433
|
exports.FhirPathTable = FhirPathTable;
|
|
6955
7434
|
exports.Form = Form;
|
|
6956
7435
|
exports.FormSection = FormSection;
|
|
7436
|
+
exports.Header = Header;
|
|
6957
7437
|
exports.HumanNameDisplay = HumanNameDisplay;
|
|
6958
7438
|
exports.HumanNameInput = HumanNameInput;
|
|
6959
7439
|
exports.IdentifierDisplay = IdentifierDisplay;
|
|
6960
7440
|
exports.IdentifierInput = IdentifierInput;
|
|
7441
|
+
exports.Loading = Loading;
|
|
6961
7442
|
exports.Logo = Logo;
|
|
6962
7443
|
exports.MedplumLink = MedplumLink;
|
|
6963
7444
|
exports.MedplumProvider = MedplumProvider;
|
|
@@ -6965,6 +7446,7 @@
|
|
|
6965
7446
|
exports.MemoizedSearchControl = MemoizedSearchControl;
|
|
6966
7447
|
exports.MoneyDisplay = MoneyDisplay;
|
|
6967
7448
|
exports.MoneyInput = MoneyInput;
|
|
7449
|
+
exports.Navbar = Navbar;
|
|
6968
7450
|
exports.ObservationTable = ObservationTable;
|
|
6969
7451
|
exports.OperationOutcomeAlert = OperationOutcomeAlert;
|
|
6970
7452
|
exports.Panel = Panel;
|