@medplum/react 2.0.16 → 2.0.18
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 +1642 -1095
- 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 +120 -0
- package/dist/esm/AppShell/Navbar.mjs.map +1 -0
- package/dist/esm/BookmarkDialog/BookmarkDialog.mjs +49 -0
- package/dist/esm/BookmarkDialog/BookmarkDialog.mjs.map +1 -0
- package/dist/esm/DefaultResourceTimeline/DefaultResourceTimeline.mjs +6 -1
- package/dist/esm/DefaultResourceTimeline/DefaultResourceTimeline.mjs.map +1 -1
- package/dist/esm/GoogleButton/GoogleButton.mjs +2 -2
- package/dist/esm/GoogleButton/GoogleButton.mjs.map +1 -1
- package/dist/esm/Loading/Loading.mjs +10 -0
- package/dist/esm/Loading/Loading.mjs.map +1 -0
- package/dist/esm/MedplumLink/MedplumLink.mjs +1 -1
- package/dist/esm/MedplumLink/MedplumLink.mjs.map +1 -1
- package/dist/esm/PatientTimeline/PatientTimeline.mjs +10 -7
- package/dist/esm/PatientTimeline/PatientTimeline.mjs.map +1 -1
- package/dist/esm/SearchControl/SearchControl.mjs +3 -3
- package/dist/esm/SearchControl/SearchControl.mjs.map +1 -1
- package/dist/esm/SearchControl/SearchUtils.mjs +1 -0
- package/dist/esm/SearchControl/SearchUtils.mjs.map +1 -1
- package/dist/esm/SearchFilterEditor/SearchFilterEditor.mjs +1 -1
- package/dist/esm/SearchFilterEditor/SearchFilterEditor.mjs.map +1 -1
- package/dist/esm/ServiceRequestTimeline/ServiceRequestTimeline.mjs +7 -4
- package/dist/esm/ServiceRequestTimeline/ServiceRequestTimeline.mjs.map +1 -1
- package/dist/esm/auth/AuthenticationForm.mjs +1 -1
- package/dist/esm/auth/AuthenticationForm.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/IconPlus.mjs +13 -0
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs.map +1 -0
- 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 +10 -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 +15 -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/BookmarkDialog/BookmarkDialog.d.ts +8 -0
- 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,25 @@
|
|
|
820
681
|
]);
|
|
821
682
|
|
|
822
683
|
/**
|
|
823
|
-
* @tabler/icons-react v2.
|
|
684
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
685
|
+
*/
|
|
686
|
+
|
|
687
|
+
var IconPlus = createReactComponent("plus", "IconPlus", [
|
|
688
|
+
["path", { d: "M12 5l0 14", key: "svg-0" }],
|
|
689
|
+
["path", { d: "M5 12l14 0", key: "svg-1" }]
|
|
690
|
+
]);
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
694
|
+
*/
|
|
695
|
+
|
|
696
|
+
var IconSearch = createReactComponent("search", "IconSearch", [
|
|
697
|
+
["path", { d: "M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0", key: "svg-0" }],
|
|
698
|
+
["path", { d: "M21 21l-6 -6", key: "svg-1" }]
|
|
699
|
+
]);
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
824
703
|
*/
|
|
825
704
|
|
|
826
705
|
var IconSettings = createReactComponent("settings", "IconSettings", [
|
|
@@ -835,7 +714,7 @@
|
|
|
835
714
|
]);
|
|
836
715
|
|
|
837
716
|
/**
|
|
838
|
-
* @tabler/icons-react v2.
|
|
717
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
839
718
|
*/
|
|
840
719
|
|
|
841
720
|
var IconSortAscending = createReactComponent("sort-ascending", "IconSortAscending", [
|
|
@@ -847,7 +726,7 @@
|
|
|
847
726
|
]);
|
|
848
727
|
|
|
849
728
|
/**
|
|
850
|
-
* @tabler/icons-react v2.
|
|
729
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
851
730
|
*/
|
|
852
731
|
|
|
853
732
|
var IconSortDescending = createReactComponent("sort-descending", "IconSortDescending", [
|
|
@@ -859,7 +738,7 @@
|
|
|
859
738
|
]);
|
|
860
739
|
|
|
861
740
|
/**
|
|
862
|
-
* @tabler/icons-react v2.
|
|
741
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
863
742
|
*/
|
|
864
743
|
|
|
865
744
|
var IconSquare = createReactComponent("square", "IconSquare", [
|
|
@@ -873,7 +752,22 @@
|
|
|
873
752
|
]);
|
|
874
753
|
|
|
875
754
|
/**
|
|
876
|
-
* @tabler/icons-react v2.
|
|
755
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
756
|
+
*/
|
|
757
|
+
|
|
758
|
+
var IconSwitchHorizontal = createReactComponent(
|
|
759
|
+
"switch-horizontal",
|
|
760
|
+
"IconSwitchHorizontal",
|
|
761
|
+
[
|
|
762
|
+
["path", { d: "M16 3l4 4l-4 4", key: "svg-0" }],
|
|
763
|
+
["path", { d: "M10 7l10 0", key: "svg-1" }],
|
|
764
|
+
["path", { d: "M8 13l-4 4l4 4", key: "svg-2" }],
|
|
765
|
+
["path", { d: "M4 17l9 0", key: "svg-3" }]
|
|
766
|
+
]
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
877
771
|
*/
|
|
878
772
|
|
|
879
773
|
var IconTableExport = createReactComponent("table-export", "IconTableExport", [
|
|
@@ -891,7 +785,7 @@
|
|
|
891
785
|
]);
|
|
892
786
|
|
|
893
787
|
/**
|
|
894
|
-
* @tabler/icons-react v2.
|
|
788
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
895
789
|
*/
|
|
896
790
|
|
|
897
791
|
var IconTrash = createReactComponent("trash", "IconTrash", [
|
|
@@ -906,7 +800,7 @@
|
|
|
906
800
|
]);
|
|
907
801
|
|
|
908
802
|
/**
|
|
909
|
-
* @tabler/icons-react v2.
|
|
803
|
+
* @tabler/icons-react v2.17.0 - MIT
|
|
910
804
|
*/
|
|
911
805
|
|
|
912
806
|
var IconX = createReactComponent("x", "IconX", [
|
|
@@ -914,751 +808,1058 @@
|
|
|
914
808
|
["path", { d: "M6 6l12 12", key: "svg-1" }]
|
|
915
809
|
]);
|
|
916
810
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
811
|
+
/**
|
|
812
|
+
* ErrorBoundary is a React component that handles errors in its child components.
|
|
813
|
+
* See: https://reactjs.org/docs/error-boundaries.html
|
|
814
|
+
*/
|
|
815
|
+
class ErrorBoundary extends React.Component {
|
|
816
|
+
constructor(props) {
|
|
817
|
+
super(props);
|
|
818
|
+
this.state = {};
|
|
923
819
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const files = e.target.files;
|
|
927
|
-
if (files) {
|
|
928
|
-
Array.from(files).forEach(processFile);
|
|
929
|
-
}
|
|
820
|
+
static getDerivedStateFromError(error) {
|
|
821
|
+
return { error };
|
|
930
822
|
}
|
|
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();
|
|
823
|
+
componentDidCatch(error, errorInfo) {
|
|
824
|
+
console.error('Uncaught error:', error, errorInfo);
|
|
825
|
+
}
|
|
826
|
+
render() {
|
|
827
|
+
if (this.state.error) {
|
|
828
|
+
return (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Something went wrong", color: "red" }, core.normalizeErrorString(this.state.error)));
|
|
946
829
|
}
|
|
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
|
-
});
|
|
830
|
+
return this.props.children;
|
|
961
831
|
}
|
|
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
832
|
}
|
|
966
833
|
|
|
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 })))))))));
|
|
834
|
+
function Loading() {
|
|
835
|
+
return (React.createElement(core$1.Center, { style: { width: '100%', height: '100vh' } },
|
|
836
|
+
React.createElement(core$1.Loader, null)));
|
|
1000
837
|
}
|
|
1001
838
|
|
|
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")));
|
|
839
|
+
function HumanNameDisplay(props) {
|
|
840
|
+
const name = props.value;
|
|
841
|
+
if (!name) {
|
|
842
|
+
return null;
|
|
1017
843
|
}
|
|
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)));
|
|
844
|
+
return React.createElement(React.Fragment, null, core.formatHumanName(name, props.options));
|
|
1068
845
|
}
|
|
1069
846
|
|
|
1070
847
|
/**
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
848
|
+
* Kills a browser event.
|
|
849
|
+
* Prevents default behavior.
|
|
850
|
+
* Stops event propagation.
|
|
851
|
+
* @param e The event.
|
|
1073
852
|
*/
|
|
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;
|
|
853
|
+
function killEvent(e) {
|
|
854
|
+
e.preventDefault();
|
|
855
|
+
e.stopPropagation();
|
|
1088
856
|
}
|
|
1089
857
|
/**
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
* @
|
|
1094
|
-
* @param result The result builder.
|
|
858
|
+
* Returns true if the element is a checkbox or a table cell containing a checkbox.
|
|
859
|
+
* Table cells containing checkboxes are commonly accidentally clicked.
|
|
860
|
+
* @param el The HTML DOM element.
|
|
861
|
+
* @returns True if the element is a checkbox or a table cell containing a checkbox.
|
|
1095
862
|
*/
|
|
1096
|
-
function
|
|
1097
|
-
if (el
|
|
1098
|
-
|
|
1099
|
-
return;
|
|
863
|
+
function isCheckboxCell(el) {
|
|
864
|
+
if (isCheckboxElement(el)) {
|
|
865
|
+
return true;
|
|
1100
866
|
}
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1103
|
-
|
|
867
|
+
if (el instanceof HTMLTableCellElement) {
|
|
868
|
+
const children = el.children;
|
|
869
|
+
if (children.length === 1 && isCheckboxElement(children[0])) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
1104
872
|
}
|
|
1105
|
-
|
|
873
|
+
return false;
|
|
1106
874
|
}
|
|
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;
|
|
875
|
+
function isCheckboxElement(el) {
|
|
876
|
+
return el instanceof HTMLInputElement && el.type === 'checkbox';
|
|
1115
877
|
}
|
|
1116
878
|
|
|
1117
|
-
function
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
879
|
+
function MedplumLink(props) {
|
|
880
|
+
const navigate = useMedplumNavigate();
|
|
881
|
+
const { to, suffix, label, onClick, children, ...rest } = props;
|
|
882
|
+
let href = getHref(to);
|
|
883
|
+
if (suffix) {
|
|
884
|
+
href += '/' + suffix;
|
|
885
|
+
}
|
|
886
|
+
return (React.createElement(core$1.Anchor, { href: href, "aria-label": label, onClick: (e) => {
|
|
887
|
+
killEvent(e);
|
|
888
|
+
if (onClick) {
|
|
889
|
+
onClick(e);
|
|
1123
890
|
}
|
|
1124
|
-
|
|
891
|
+
else if (to) {
|
|
892
|
+
navigate(href);
|
|
893
|
+
}
|
|
894
|
+
}, ...rest }, children));
|
|
1125
895
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
896
|
+
function getHref(to) {
|
|
897
|
+
if (to) {
|
|
898
|
+
if (typeof to === 'string') {
|
|
899
|
+
return getStringHref(to);
|
|
900
|
+
}
|
|
901
|
+
else if (core.isResource(to)) {
|
|
902
|
+
return getResourceHref(to);
|
|
903
|
+
}
|
|
904
|
+
else if (core.isReference(to)) {
|
|
905
|
+
return getReferenceHref(to);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return '#';
|
|
1135
909
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
?.join('\n');
|
|
910
|
+
function getStringHref(to) {
|
|
911
|
+
if (to.startsWith('http://') || to.startsWith('https://') || to.startsWith('/')) {
|
|
912
|
+
return to;
|
|
913
|
+
}
|
|
914
|
+
return '/' + to;
|
|
1142
915
|
}
|
|
1143
|
-
function
|
|
1144
|
-
return
|
|
916
|
+
function getResourceHref(to) {
|
|
917
|
+
return `/${to.resourceType}/${to.id}`;
|
|
1145
918
|
}
|
|
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;
|
|
919
|
+
function getReferenceHref(to) {
|
|
920
|
+
return `/${to.reference}`;
|
|
1163
921
|
}
|
|
1164
922
|
|
|
1165
|
-
|
|
923
|
+
/**
|
|
924
|
+
* React Hook to use a FHIR reference.
|
|
925
|
+
* Handles the complexity of resolving references and caching resources.
|
|
926
|
+
* @param value The resource or reference to resource.
|
|
927
|
+
* @returns The resolved resource.
|
|
928
|
+
*/
|
|
929
|
+
function useResource(value, setOutcome) {
|
|
1166
930
|
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
|
-
|
|
931
|
+
const [resource, setResource] = React.useState(getInitialResource(medplum, value));
|
|
932
|
+
const setResourceIfChanged = React.useCallback((r) => {
|
|
933
|
+
if (!core.deepEquals(r, resource)) {
|
|
934
|
+
setResource(r);
|
|
935
|
+
}
|
|
936
|
+
}, [resource, setResource]);
|
|
937
|
+
React.useEffect(() => {
|
|
938
|
+
setResourceIfChanged(getInitialResource(medplum, value));
|
|
939
|
+
}, [medplum, value, setResourceIfChanged]);
|
|
940
|
+
React.useEffect(() => {
|
|
941
|
+
let subscribed = true;
|
|
942
|
+
if (core.isReference(value)) {
|
|
943
|
+
medplum
|
|
944
|
+
.readReference(value)
|
|
945
|
+
.then((r) => {
|
|
946
|
+
if (subscribed) {
|
|
947
|
+
setResourceIfChanged(r);
|
|
948
|
+
}
|
|
949
|
+
})
|
|
950
|
+
.catch((err) => {
|
|
951
|
+
if (subscribed) {
|
|
952
|
+
setResourceIfChanged(undefined);
|
|
953
|
+
if (setOutcome) {
|
|
954
|
+
setOutcome(core.normalizeOperationOutcome(err));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return (() => (subscribed = false));
|
|
960
|
+
}, [medplum, resource, value, setResourceIfChanged, setOutcome]);
|
|
961
|
+
return resource;
|
|
1193
962
|
}
|
|
1194
|
-
|
|
1195
963
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
964
|
+
* Returns the initial resource value based on the input value.
|
|
965
|
+
* If the input value is a resource, returns the resource.
|
|
966
|
+
* If the input value is a reference to a resource available in the cache, returns the resource.
|
|
967
|
+
* Otherwise, returns undefined.
|
|
968
|
+
* @param medplum The medplum client.
|
|
969
|
+
* @param value The resource or reference to resource.
|
|
970
|
+
* @returns An initial resource if available; undefined otherwise.
|
|
1198
971
|
*/
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
972
|
+
function getInitialResource(medplum, value) {
|
|
973
|
+
if (value) {
|
|
974
|
+
if (core.isResource(value)) {
|
|
975
|
+
return value;
|
|
976
|
+
}
|
|
977
|
+
if (core.isReference(value)) {
|
|
978
|
+
return medplum.getCachedReference(value);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return undefined;
|
|
1206
982
|
}
|
|
1207
983
|
|
|
1208
|
-
function
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
const
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
984
|
+
function ResourceAvatar(props) {
|
|
985
|
+
const resource = useResource(props.value);
|
|
986
|
+
const text = resource ? core.getDisplayString(resource) : props.alt ?? '';
|
|
987
|
+
const imageUrl = (resource && core.getImageSrc(resource)) ?? props.src;
|
|
988
|
+
const radius = props.radius ?? 'xl';
|
|
989
|
+
const avatarProps = { ...props };
|
|
990
|
+
delete avatarProps.value;
|
|
991
|
+
delete avatarProps.link;
|
|
992
|
+
if (props.link) {
|
|
993
|
+
return (React.createElement(MedplumLink, { to: resource },
|
|
994
|
+
React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps })));
|
|
995
|
+
}
|
|
996
|
+
return React.createElement(core$1.Avatar, { src: imageUrl, alt: text, radius: radius, ...avatarProps });
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function AsyncAutocomplete(props) {
|
|
1000
|
+
const { defaultValue, toKey, toOption, loadOptions, onChange, onCreate, creatable, ...rest } = props;
|
|
1001
|
+
const defaultItems = toDefaultItems(defaultValue);
|
|
1002
|
+
const inputRef = React.useRef(null);
|
|
1003
|
+
const [lastValue, setLastValue] = React.useState(undefined);
|
|
1004
|
+
const [timer, setTimer] = React.useState();
|
|
1005
|
+
const [abortController, setAbortController] = React.useState();
|
|
1006
|
+
const [autoSubmit, setAutoSubmit] = React.useState();
|
|
1007
|
+
const [options, setOptions] = React.useState(defaultItems?.map(toOption));
|
|
1008
|
+
const lastValueRef = React.useRef();
|
|
1009
|
+
lastValueRef.current = lastValue;
|
|
1010
|
+
const timerRef = React.useRef();
|
|
1011
|
+
timerRef.current = timer;
|
|
1012
|
+
const abortControllerRef = React.useRef();
|
|
1013
|
+
abortControllerRef.current = abortController;
|
|
1014
|
+
const autoSubmitRef = React.useRef();
|
|
1015
|
+
autoSubmitRef.current = autoSubmit;
|
|
1016
|
+
const optionsRef = React.useRef();
|
|
1017
|
+
optionsRef.current = options;
|
|
1018
|
+
const handleTimer = React.useCallback(() => {
|
|
1019
|
+
setTimer(undefined);
|
|
1020
|
+
const value = inputRef.current?.value?.trim() || '';
|
|
1021
|
+
if (value === lastValueRef.current) {
|
|
1022
|
+
// Nothing has changed, move on
|
|
1218
1023
|
return;
|
|
1219
1024
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1025
|
+
setLastValue(value);
|
|
1026
|
+
const newAbortController = new AbortController();
|
|
1027
|
+
setAbortController(newAbortController);
|
|
1028
|
+
loadOptions(value, newAbortController.signal)
|
|
1029
|
+
.then((newValues) => {
|
|
1030
|
+
if (!newAbortController.signal.aborted) {
|
|
1031
|
+
setOptions(newValues.map(toOption));
|
|
1032
|
+
setAbortController(undefined);
|
|
1033
|
+
if (autoSubmitRef.current) {
|
|
1034
|
+
if (newValues.length > 0) {
|
|
1035
|
+
onChange(newValues.slice(0, 1));
|
|
1036
|
+
}
|
|
1037
|
+
setAutoSubmit(false);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
})
|
|
1041
|
+
.catch(console.log);
|
|
1042
|
+
}, [loadOptions, onChange, toOption]);
|
|
1043
|
+
const handleSearchChange = React.useCallback(() => {
|
|
1044
|
+
if (abortControllerRef.current) {
|
|
1045
|
+
abortControllerRef.current.abort();
|
|
1046
|
+
setAbortController(undefined);
|
|
1226
1047
|
}
|
|
1227
|
-
if (
|
|
1228
|
-
|
|
1229
|
-
setButtonRendered(true);
|
|
1048
|
+
if (timerRef.current !== undefined) {
|
|
1049
|
+
window.clearTimeout(timerRef.current);
|
|
1230
1050
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1051
|
+
const newTimer = window.setTimeout(() => handleTimer(), 100);
|
|
1052
|
+
setTimer(newTimer);
|
|
1053
|
+
}, [handleTimer]);
|
|
1054
|
+
const handleChange = React.useCallback((values) => {
|
|
1055
|
+
const result = [];
|
|
1056
|
+
for (const value of values) {
|
|
1057
|
+
let item = optionsRef.current?.find((option) => option.value === value)?.resource;
|
|
1058
|
+
if (!item && creatable !== false) {
|
|
1059
|
+
item = onCreate(value);
|
|
1060
|
+
}
|
|
1061
|
+
if (item)
|
|
1062
|
+
result.push(item);
|
|
1063
|
+
}
|
|
1064
|
+
onChange(result);
|
|
1065
|
+
}, [creatable, onChange, onCreate]);
|
|
1066
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
1067
|
+
if (e.key === 'Enter') {
|
|
1068
|
+
if (!timerRef.current && !abortControllerRef.current) {
|
|
1069
|
+
killEvent(e);
|
|
1070
|
+
if (optionsRef.current && optionsRef.current.length > 0) {
|
|
1071
|
+
setOptions(optionsRef.current.slice(0, 1));
|
|
1072
|
+
handleChange([optionsRef.current[0].value]);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
// The user pressed enter, but we don't have results yet.
|
|
1077
|
+
// We need to wait for the results to come in.
|
|
1078
|
+
setAutoSubmit(true);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}, [handleChange]);
|
|
1082
|
+
const handleCreate = React.useCallback((input) => {
|
|
1083
|
+
const option = toOption(onCreate(input));
|
|
1084
|
+
setOptions([...optionsRef.current, option]);
|
|
1085
|
+
return option;
|
|
1086
|
+
}, [onCreate, setOptions, toOption]);
|
|
1087
|
+
const handleFilter = React.useCallback((_value, selected) => !selected, []);
|
|
1088
|
+
React.useEffect(() => {
|
|
1089
|
+
return () => {
|
|
1090
|
+
if (abortControllerRef.current) {
|
|
1091
|
+
abortControllerRef.current.abort();
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
}, []);
|
|
1095
|
+
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
1096
|
}
|
|
1237
|
-
function
|
|
1238
|
-
if (
|
|
1239
|
-
return
|
|
1097
|
+
function toDefaultItems(defaultValue) {
|
|
1098
|
+
if (!defaultValue) {
|
|
1099
|
+
return [];
|
|
1240
1100
|
}
|
|
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
|
-
}
|
|
1101
|
+
if (Array.isArray(defaultValue)) {
|
|
1102
|
+
return defaultValue;
|
|
1247
1103
|
}
|
|
1248
|
-
return
|
|
1104
|
+
return [defaultValue];
|
|
1249
1105
|
}
|
|
1250
1106
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1107
|
+
const useStyles$h = core$1.createStyles(() => {
|
|
1108
|
+
return {
|
|
1109
|
+
searchInput: {
|
|
1110
|
+
input: {
|
|
1111
|
+
width: 220,
|
|
1112
|
+
transition: 'width 0.2s',
|
|
1113
|
+
},
|
|
1114
|
+
'input:focus': {
|
|
1115
|
+
width: 400,
|
|
1116
|
+
},
|
|
1117
|
+
'@media (max-width: 800px)': {
|
|
1118
|
+
input: {
|
|
1119
|
+
width: 150,
|
|
1120
|
+
},
|
|
1121
|
+
'input:focus': {
|
|
1122
|
+
width: 150,
|
|
1123
|
+
},
|
|
1124
|
+
},
|
|
1125
|
+
},
|
|
1126
|
+
};
|
|
1127
|
+
});
|
|
1128
|
+
function toKey$1(resource) {
|
|
1129
|
+
return resource.id;
|
|
1130
|
+
}
|
|
1131
|
+
function toOption$1(resource) {
|
|
1132
|
+
return {
|
|
1133
|
+
value: resource.id,
|
|
1134
|
+
label: core.getDisplayString(resource),
|
|
1135
|
+
resource,
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function HeaderSearchInput() {
|
|
1139
|
+
const { classes } = useStyles$h();
|
|
1140
|
+
const navigate = useMedplumNavigate();
|
|
1141
|
+
const medplum = useMedplum();
|
|
1142
|
+
const location = reactRouterDom.useLocation();
|
|
1143
|
+
const loadData = React.useCallback(async (input, signal) => {
|
|
1144
|
+
const query = buildGraphQLQuery(input);
|
|
1145
|
+
const options = { signal };
|
|
1146
|
+
const response = (await medplum.graphql(query, undefined, undefined, options));
|
|
1147
|
+
return getResourcesFromResponse(response, input);
|
|
1148
|
+
}, [medplum]);
|
|
1149
|
+
const handleSelect = React.useCallback((item) => {
|
|
1150
|
+
if (item.length > 0) {
|
|
1151
|
+
navigate(`/${core.getReferenceString(item[0])}`);
|
|
1152
|
+
}
|
|
1153
|
+
}, [navigate]);
|
|
1154
|
+
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 }));
|
|
1155
|
+
}
|
|
1156
|
+
const ItemComponent$1 = React.forwardRef(({ resource, ...others }, ref) => {
|
|
1157
|
+
let helpText = undefined;
|
|
1158
|
+
if (resource.resourceType === 'Patient') {
|
|
1159
|
+
helpText = resource.birthDate;
|
|
1255
1160
|
}
|
|
1256
|
-
|
|
1161
|
+
else if (resource.resourceType === 'ServiceRequest') {
|
|
1162
|
+
helpText = resource.subject?.display;
|
|
1163
|
+
}
|
|
1164
|
+
return (React.createElement("div", { ref: ref, ...others },
|
|
1165
|
+
React.createElement(core$1.Group, { noWrap: true },
|
|
1166
|
+
React.createElement(ResourceAvatar, { value: resource }),
|
|
1167
|
+
React.createElement("div", null,
|
|
1168
|
+
React.createElement(core$1.Text, null, core.getDisplayString(resource)),
|
|
1169
|
+
React.createElement(core$1.Text, { size: "xs", color: "dimmed" }, helpText)))));
|
|
1170
|
+
});
|
|
1171
|
+
function buildGraphQLQuery(input) {
|
|
1172
|
+
const escaped = JSON.stringify(input);
|
|
1173
|
+
if (core.isUUID(input)) {
|
|
1174
|
+
return `{
|
|
1175
|
+
Patients1: PatientList(_id: ${escaped}, _count: 1) {
|
|
1176
|
+
resourceType
|
|
1177
|
+
id
|
|
1178
|
+
identifier {
|
|
1179
|
+
system
|
|
1180
|
+
value
|
|
1181
|
+
}
|
|
1182
|
+
name {
|
|
1183
|
+
given
|
|
1184
|
+
family
|
|
1185
|
+
}
|
|
1186
|
+
birthDate
|
|
1187
|
+
}
|
|
1188
|
+
ServiceRequestList(_id: ${escaped}, _count: 1) {
|
|
1189
|
+
resourceType
|
|
1190
|
+
id
|
|
1191
|
+
identifier {
|
|
1192
|
+
system
|
|
1193
|
+
value
|
|
1194
|
+
}
|
|
1195
|
+
subject {
|
|
1196
|
+
display
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}`.replace(/\s+/g, ' ');
|
|
1200
|
+
}
|
|
1201
|
+
return `{
|
|
1202
|
+
Patients1: PatientList(name: ${escaped}, _count: 5) {
|
|
1203
|
+
resourceType
|
|
1204
|
+
id
|
|
1205
|
+
identifier {
|
|
1206
|
+
system
|
|
1207
|
+
value
|
|
1208
|
+
}
|
|
1209
|
+
name {
|
|
1210
|
+
given
|
|
1211
|
+
family
|
|
1212
|
+
}
|
|
1213
|
+
birthDate
|
|
1214
|
+
}
|
|
1215
|
+
Patients2: PatientList(identifier: ${escaped}, _count: 5) {
|
|
1216
|
+
resourceType
|
|
1217
|
+
id
|
|
1218
|
+
identifier {
|
|
1219
|
+
system
|
|
1220
|
+
value
|
|
1221
|
+
}
|
|
1222
|
+
name {
|
|
1223
|
+
given
|
|
1224
|
+
family
|
|
1225
|
+
}
|
|
1226
|
+
birthDate
|
|
1227
|
+
}
|
|
1228
|
+
ServiceRequestList(identifier: ${escaped}, _count: 5) {
|
|
1229
|
+
resourceType
|
|
1230
|
+
id
|
|
1231
|
+
identifier {
|
|
1232
|
+
system
|
|
1233
|
+
value
|
|
1234
|
+
}
|
|
1235
|
+
subject {
|
|
1236
|
+
display
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}`.replace(/\s+/g, ' ');
|
|
1257
1240
|
}
|
|
1258
|
-
|
|
1259
1241
|
/**
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1242
|
+
* Returns a de-duped and sorted list of resources from the search response.
|
|
1243
|
+
* The search request is actually 3+ separate searches, which can include duplicates.
|
|
1244
|
+
* This function combines the results, de-dupes, and sorts by relevance.
|
|
1245
|
+
* @param response The response from a search query.
|
|
1246
|
+
* @param query The user entered search query.
|
|
1247
|
+
* @returns The resources to display in the autocomplete.
|
|
1263
1248
|
*/
|
|
1264
|
-
function
|
|
1265
|
-
|
|
1266
|
-
|
|
1249
|
+
function getResourcesFromResponse(response, query) {
|
|
1250
|
+
const resources = [];
|
|
1251
|
+
if (response.data.Patients1) {
|
|
1252
|
+
resources.push(...response.data.Patients1);
|
|
1253
|
+
}
|
|
1254
|
+
if (response.data.Patients2) {
|
|
1255
|
+
resources.push(...response.data.Patients2);
|
|
1256
|
+
}
|
|
1257
|
+
if (response.data.ServiceRequestList) {
|
|
1258
|
+
resources.push(...response.data.ServiceRequestList);
|
|
1267
1259
|
}
|
|
1260
|
+
return sortByRelevance(dedupeResources(resources), query).slice(0, 5);
|
|
1268
1261
|
}
|
|
1269
1262
|
/**
|
|
1270
|
-
*
|
|
1271
|
-
* @param
|
|
1272
|
-
* @returns
|
|
1263
|
+
* Removes duplicate resources from an array by ID.
|
|
1264
|
+
* @param resources The array of resources with possible duplicates.
|
|
1265
|
+
* @returns The array of resources with no duplicates.
|
|
1273
1266
|
*/
|
|
1274
|
-
function
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1267
|
+
function dedupeResources(resources) {
|
|
1268
|
+
const ids = new Set();
|
|
1269
|
+
const result = [];
|
|
1270
|
+
for (const resource of resources) {
|
|
1271
|
+
if (!ids.has(resource.id)) {
|
|
1272
|
+
ids.add(resource.id);
|
|
1273
|
+
result.push(resource);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return result;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Sorts an array of resources by relevance.
|
|
1280
|
+
* @param resources The candidate resources.
|
|
1281
|
+
* @param query The user entered search string.
|
|
1282
|
+
* @returns The sorted array of resources.
|
|
1283
|
+
*/
|
|
1284
|
+
function sortByRelevance(resources, query) {
|
|
1285
|
+
return resources.sort((a, b) => {
|
|
1286
|
+
return getResourceScore(b, query) - getResourceScore(a, query);
|
|
1284
1287
|
});
|
|
1285
1288
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1289
|
+
/**
|
|
1290
|
+
* Calculates a relevance score of a candidate resource.
|
|
1291
|
+
* Higher scores are better.
|
|
1292
|
+
* @param resource The candidate resource.
|
|
1293
|
+
* @param query The user entered search string.
|
|
1294
|
+
* @returns The relevance score of the candidate resource.
|
|
1295
|
+
*/
|
|
1296
|
+
function getResourceScore(resource, query) {
|
|
1297
|
+
let bestScore = 0;
|
|
1298
|
+
if (resource.identifier) {
|
|
1299
|
+
for (const identifier of resource.identifier) {
|
|
1300
|
+
bestScore = Math.max(bestScore, getStringScore(identifier.value, query));
|
|
1296
1301
|
}
|
|
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"))));
|
|
1302
|
+
}
|
|
1303
|
+
if (resource.resourceType === 'Patient' && resource.name) {
|
|
1304
|
+
for (const name of resource.name) {
|
|
1305
|
+
bestScore = Math.max(bestScore, getStringScore(core.formatHumanName(name), query));
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return bestScore;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Calculates a relevance score of a candidate display string.
|
|
1312
|
+
* Higher scores are better.
|
|
1313
|
+
* @param str The candidate display string.
|
|
1314
|
+
* @param query The user entered search string.
|
|
1315
|
+
* @returns The relevance score of the candidate string.
|
|
1316
|
+
*/
|
|
1317
|
+
function getStringScore(str, query) {
|
|
1318
|
+
if (!str) {
|
|
1319
|
+
return 0;
|
|
1320
|
+
}
|
|
1321
|
+
const index = str.toLowerCase().indexOf(query.toLowerCase());
|
|
1322
|
+
if (index < 0) {
|
|
1323
|
+
return 0;
|
|
1324
|
+
}
|
|
1325
|
+
return 100 - index;
|
|
1358
1326
|
}
|
|
1359
1327
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1328
|
+
const useStyles$g = core$1.createStyles((theme) => ({
|
|
1329
|
+
logoButton: {
|
|
1330
|
+
padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
|
|
1331
|
+
borderRadius: theme.radius.sm,
|
|
1332
|
+
transition: 'background-color 100ms ease',
|
|
1333
|
+
'&:hover': {
|
|
1334
|
+
backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
user: {
|
|
1338
|
+
padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
|
|
1339
|
+
borderRadius: theme.radius.sm,
|
|
1340
|
+
transition: 'background-color 100ms ease',
|
|
1341
|
+
'&:hover': {
|
|
1342
|
+
backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
|
|
1343
|
+
},
|
|
1344
|
+
},
|
|
1345
|
+
userName: {
|
|
1346
|
+
fontWeight: 500,
|
|
1347
|
+
lineHeight: 1,
|
|
1348
|
+
marginRight: 3,
|
|
1349
|
+
[theme.fn.smallerThan('xs')]: {
|
|
1350
|
+
display: 'none',
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
userActive: {
|
|
1354
|
+
backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
|
|
1355
|
+
},
|
|
1356
|
+
}));
|
|
1357
|
+
function Header(props) {
|
|
1358
|
+
const context = useMedplumContext();
|
|
1359
|
+
const { medplum, profile, navigate } = context;
|
|
1360
|
+
const logins = medplum.getLogins();
|
|
1361
|
+
const { classes, cx } = useStyles$g();
|
|
1362
|
+
const [userMenuOpened, setUserMenuOpened] = React.useState(false);
|
|
1363
|
+
return (React.createElement(core$1.Header, { height: 60, p: 8, style: { zIndex: 101 } },
|
|
1364
|
+
React.createElement(core$1.Group, { position: "apart" },
|
|
1365
|
+
React.createElement(core$1.Group, { spacing: "xs" },
|
|
1366
|
+
React.createElement(core$1.UnstyledButton, { className: classes.logoButton, onClick: props.navbarToggle }, props.logo),
|
|
1367
|
+
React.createElement(HeaderSearchInput, null)),
|
|
1368
|
+
React.createElement(core$1.Menu, { width: 260, shadow: "xl", position: "bottom-end", transitionProps: { transition: 'pop-top-right' }, opened: userMenuOpened, onClose: () => setUserMenuOpened(false) },
|
|
1369
|
+
React.createElement(core$1.Menu.Target, null,
|
|
1370
|
+
React.createElement(core$1.UnstyledButton, { className: cx(classes.user, { [classes.userActive]: userMenuOpened }), onClick: () => setUserMenuOpened((o) => !o) },
|
|
1371
|
+
React.createElement(core$1.Group, { spacing: 7 },
|
|
1372
|
+
React.createElement(ResourceAvatar, { value: profile, radius: "xl", size: 24 }),
|
|
1373
|
+
React.createElement(core$1.Text, { size: "sm", className: classes.userName }, core.formatHumanName(profile?.name?.[0])),
|
|
1374
|
+
React.createElement(IconChevronDown, { size: 12, stroke: 1.5 })))),
|
|
1375
|
+
React.createElement(core$1.Menu.Dropdown, null,
|
|
1376
|
+
React.createElement(core$1.Stack, { align: "center", p: "xl" },
|
|
1377
|
+
React.createElement(ResourceAvatar, { size: "xl", radius: 100, value: context.profile }),
|
|
1378
|
+
React.createElement(HumanNameDisplay, { value: context.profile?.name?.[0] }),
|
|
1379
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, medplum.getActiveLogin()?.project?.display)),
|
|
1380
|
+
logins.length > 1 && React.createElement(core$1.Menu.Divider, null),
|
|
1381
|
+
logins.map((login) => login.profile?.reference !== core.getReferenceString(context.profile) && (React.createElement(core$1.Menu.Item, { key: login.profile?.reference, onClick: () => {
|
|
1382
|
+
medplum
|
|
1383
|
+
.setActiveLogin(login)
|
|
1384
|
+
.then(() => window.location.reload())
|
|
1385
|
+
.catch(console.log);
|
|
1386
|
+
} },
|
|
1387
|
+
React.createElement(core$1.Group, null,
|
|
1388
|
+
React.createElement(core$1.Avatar, { radius: "xl" }),
|
|
1389
|
+
React.createElement("div", { style: { flex: 1 } },
|
|
1390
|
+
React.createElement(core$1.Text, { size: "sm", weight: 500 }, login.profile?.display),
|
|
1391
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, login.project?.display)))))),
|
|
1392
|
+
React.createElement(core$1.Menu.Divider, null),
|
|
1393
|
+
React.createElement(core$1.Menu.Item, { icon: React.createElement(IconSwitchHorizontal, { size: 14, stroke: 1.5 }), onClick: () => navigate('/signin') }, "Add another account"),
|
|
1394
|
+
React.createElement(core$1.Menu.Item, { icon: React.createElement(IconSettings, { size: 14, stroke: 1.5 }), onClick: () => navigate(`/${core.getReferenceString(profile)}`) }, "Account settings"),
|
|
1395
|
+
React.createElement(core$1.Menu.Item, { icon: React.createElement(IconLogout, { size: 14, stroke: 1.5 }), onClick: async () => {
|
|
1396
|
+
await medplum.signOut();
|
|
1397
|
+
navigate('/signin');
|
|
1398
|
+
} }, "Sign out"),
|
|
1399
|
+
React.createElement(core$1.Text, { size: "xs", color: "dimmed", align: "center" }, props.version))))));
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* Parses an HTML form and returns the result as a JavaScript object.
|
|
1404
|
+
* @param form The HTML form element.
|
|
1405
|
+
*/
|
|
1406
|
+
function parseForm(form) {
|
|
1407
|
+
const result = {};
|
|
1408
|
+
for (const element of Array.from(form.elements)) {
|
|
1409
|
+
if (element instanceof HTMLInputElement) {
|
|
1410
|
+
parseInputElement(result, element);
|
|
1372
1411
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if (response.code) {
|
|
1376
|
-
medplum
|
|
1377
|
-
.processCode(response.code)
|
|
1378
|
-
.then(() => onSuccess())
|
|
1379
|
-
.catch(console.log);
|
|
1412
|
+
else if (element instanceof HTMLTextAreaElement) {
|
|
1413
|
+
result[element.name] = element.value;
|
|
1380
1414
|
}
|
|
1381
|
-
else if (
|
|
1382
|
-
|
|
1415
|
+
else if (element instanceof HTMLSelectElement) {
|
|
1416
|
+
parseSelectElement(result, element);
|
|
1383
1417
|
}
|
|
1384
1418
|
}
|
|
1385
|
-
return
|
|
1386
|
-
outcome && React.createElement("pre", null, JSON.stringify(outcome, null, 2)),
|
|
1387
|
-
!login && (React.createElement(NewUserForm, { projectId: projectId, googleClientId: googleClientId, recaptchaSiteKey: recaptchaSiteKey, handleAuthResponse: handleAuthResponse }, props.children)),
|
|
1388
|
-
login && type === 'project' && React.createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse })));
|
|
1419
|
+
return result;
|
|
1389
1420
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1421
|
+
/**
|
|
1422
|
+
* Parses an HTML input element.
|
|
1423
|
+
* Sets the name/value pair in the result,
|
|
1424
|
+
* but only if the element is enabled and checked.
|
|
1425
|
+
* @param el The input element.
|
|
1426
|
+
* @param result The result builder.
|
|
1427
|
+
*/
|
|
1428
|
+
function parseInputElement(result, el) {
|
|
1429
|
+
if (el.disabled) {
|
|
1430
|
+
// Ignore disabled elements
|
|
1431
|
+
return;
|
|
1395
1432
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1433
|
+
if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked) {
|
|
1434
|
+
// Ignore unchecked radio or checkbox elements
|
|
1435
|
+
return;
|
|
1398
1436
|
}
|
|
1437
|
+
result[el.name] = el.value;
|
|
1399
1438
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
const state = JSON.stringify({
|
|
1409
|
-
...(await medplum.ensureCodeChallenge(baseLoginRequest)),
|
|
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"))));
|
|
1439
|
+
/**
|
|
1440
|
+
* Parses an HTML select element.
|
|
1441
|
+
* Sets the name/value pair if one is selected.
|
|
1442
|
+
* @param result The result builder.
|
|
1443
|
+
* @param el The select element.
|
|
1444
|
+
*/
|
|
1445
|
+
function parseSelectElement(result, el) {
|
|
1446
|
+
result[el.name] = el.value;
|
|
1442
1447
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1448
|
+
|
|
1449
|
+
function Form(props) {
|
|
1450
|
+
return (React.createElement("form", { style: props.style, "data-testid": props.testid, onSubmit: (e) => {
|
|
1451
|
+
e.preventDefault();
|
|
1452
|
+
const formData = parseForm(e.target);
|
|
1453
|
+
if (props.onSubmit) {
|
|
1454
|
+
props.onSubmit(formData);
|
|
1455
|
+
}
|
|
1456
|
+
} }, props.children));
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function BookmarkDialog(props) {
|
|
1445
1460
|
const medplum = useMedplum();
|
|
1446
|
-
const
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1461
|
+
const config = medplum.getUserConfiguration();
|
|
1462
|
+
const location = reactRouterDom.useLocation();
|
|
1463
|
+
function submitHandler(formData) {
|
|
1464
|
+
const { menuname, bookmarkname: name } = formData;
|
|
1465
|
+
const target = location.pathname + location.search;
|
|
1466
|
+
const newConfig = core.deepClone(config);
|
|
1467
|
+
const menu = newConfig?.menu?.find(({ title }) => title === menuname);
|
|
1468
|
+
menu?.link?.push({ name, target });
|
|
1449
1469
|
medplum
|
|
1450
|
-
.
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1470
|
+
.updateResource(newConfig)
|
|
1471
|
+
.then((res) => {
|
|
1472
|
+
// refresh current config menu
|
|
1473
|
+
config.menu = res.menu;
|
|
1474
|
+
medplum.dispatchEvent({ type: 'change' });
|
|
1475
|
+
notifications.showNotification({ color: 'green', message: 'Success' });
|
|
1476
|
+
props.onOk();
|
|
1454
1477
|
})
|
|
1455
|
-
.
|
|
1456
|
-
.
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
React.createElement(
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
React.createElement(core$1.Button, { type: "submit" }, "Sign in"))));
|
|
1478
|
+
.catch((err) => {
|
|
1479
|
+
notifications.showNotification({ color: 'red', message: core.normalizeErrorString(err) });
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
return (React.createElement(core$1.Modal, { title: "Add Bookmark", closeButtonProps: { 'aria-label': 'Close' }, opened: props.visible, onClose: props.onCancel },
|
|
1483
|
+
React.createElement(Form, { onSubmit: submitHandler },
|
|
1484
|
+
React.createElement(core$1.Stack, null,
|
|
1485
|
+
React.createElement(SelectMenu, { config: config }),
|
|
1486
|
+
React.createElement(core$1.TextInput, { label: "Bookmark Name", type: "text", name: "bookmarkname", placeholder: "bookmark name", withAsterisk: true }),
|
|
1487
|
+
React.createElement(core$1.Group, { position: "right" },
|
|
1488
|
+
React.createElement(core$1.Button, { mt: "sm", type: "submit" }, "OK"))))));
|
|
1467
1489
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
React.createElement(Logo, { size: 32 }),
|
|
1475
|
-
React.createElement(core$1.Title, { order: 3 }, "Choose profile")),
|
|
1476
|
-
React.createElement(OperationOutcomeAlert, { outcome: outcome }),
|
|
1477
|
-
props.memberships.map((membership) => (React.createElement(core$1.UnstyledButton, { key: membership.id, onClick: () => {
|
|
1478
|
-
medplum
|
|
1479
|
-
.post('auth/profile', {
|
|
1480
|
-
login: props.login,
|
|
1481
|
-
profile: membership.id,
|
|
1482
|
-
})
|
|
1483
|
-
.then(props.handleAuthResponse)
|
|
1484
|
-
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
1485
|
-
} },
|
|
1486
|
-
React.createElement(core$1.Group, null,
|
|
1487
|
-
React.createElement(core$1.Avatar, { radius: "xl" }),
|
|
1488
|
-
React.createElement("div", { style: { flex: 1 } },
|
|
1489
|
-
React.createElement(core$1.Text, { size: "sm", weight: 500 }, membership.profile?.display),
|
|
1490
|
-
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, membership.project?.display))))))));
|
|
1490
|
+
function SelectMenu(props) {
|
|
1491
|
+
function userConfigToMenu(config) {
|
|
1492
|
+
return config?.menu?.map((menu) => menu.title);
|
|
1493
|
+
}
|
|
1494
|
+
const menus = userConfigToMenu(props.config);
|
|
1495
|
+
return (React.createElement(core$1.NativeSelect, { name: "menuname", defaultValue: menus?.[0], label: "Select Menu Option", placeholder: "Menu", data: menus, withAsterisk: true }));
|
|
1491
1496
|
}
|
|
1492
1497
|
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1495
|
-
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
|
|
1496
|
-
medplum
|
|
1497
|
-
.post('auth/scope', {
|
|
1498
|
-
login: props.login,
|
|
1499
|
-
scope: Object.keys(formData).join(' '),
|
|
1500
|
-
})
|
|
1501
|
-
.then(props.handleAuthResponse)
|
|
1502
|
-
.catch(console.log);
|
|
1503
|
-
} },
|
|
1504
|
-
React.createElement(core$1.Stack, null,
|
|
1505
|
-
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
1506
|
-
React.createElement(Logo, { size: 32 }),
|
|
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")))));
|
|
1498
|
+
function toKey(element) {
|
|
1499
|
+
return element.code;
|
|
1511
1500
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
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")))));
|
|
1501
|
+
function toOption(element) {
|
|
1502
|
+
return {
|
|
1503
|
+
value: element.code,
|
|
1504
|
+
label: getDisplay(element),
|
|
1505
|
+
resource: element,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
function createValue(input) {
|
|
1509
|
+
return {
|
|
1510
|
+
code: input,
|
|
1511
|
+
display: input,
|
|
1512
|
+
};
|
|
1535
1513
|
}
|
|
1536
|
-
|
|
1537
1514
|
/**
|
|
1538
|
-
*
|
|
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
|
|
1515
|
+
* A low-level component to autocomplete based on a FHIR Valueset.
|
|
1546
1516
|
*/
|
|
1547
|
-
function
|
|
1548
|
-
const { chooseScopes, onSuccess, onForgotPassword, onRegister, onCode, ...baseLoginRequest } = props;
|
|
1517
|
+
function ValueSetAutocomplete(props) {
|
|
1549
1518
|
const medplum = useMedplum();
|
|
1550
|
-
const
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
.processCode(code)
|
|
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);
|
|
1519
|
+
const { elementDefinition, creatable, clearable, ...rest } = props;
|
|
1520
|
+
const loadValues = React.useCallback(async (input, signal) => {
|
|
1521
|
+
const system = elementDefinition.binding?.valueSet;
|
|
1522
|
+
const valueSet = await medplum.searchValueSet(system, input, { signal });
|
|
1523
|
+
const valueSetElements = valueSet.expansion?.contains;
|
|
1524
|
+
const newData = [];
|
|
1525
|
+
for (const valueSetElement of valueSetElements) {
|
|
1526
|
+
if (valueSetElement.code && !newData.some((item) => item.code === valueSetElement.code)) {
|
|
1527
|
+
newData.push(valueSetElement);
|
|
1582
1528
|
}
|
|
1583
1529
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
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 });
|
|
1605
|
-
}
|
|
1606
|
-
else if (props.projectId === 'new') {
|
|
1607
|
-
return React.createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
1608
|
-
}
|
|
1609
|
-
else if (props.chooseScopes) {
|
|
1610
|
-
return React.createElement(ChooseScopeForm, { login: login, scope: props.scope, handleAuthResponse: handleScopeResponse });
|
|
1611
|
-
}
|
|
1612
|
-
else {
|
|
1613
|
-
return React.createElement("div", null, "Success");
|
|
1614
|
-
}
|
|
1615
|
-
})()));
|
|
1530
|
+
return newData;
|
|
1531
|
+
}, [medplum, elementDefinition]);
|
|
1532
|
+
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}` }));
|
|
1533
|
+
}
|
|
1534
|
+
function getDisplay(item) {
|
|
1535
|
+
return item.display || item.code || '';
|
|
1616
1536
|
}
|
|
1617
1537
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1538
|
+
function CodeInput(props) {
|
|
1539
|
+
const [value, setValue] = React.useState(props.defaultValue);
|
|
1540
|
+
function handleChange(newValues) {
|
|
1541
|
+
const newValue = newValues[0];
|
|
1542
|
+
const newCode = valueSetElementToCode(newValue);
|
|
1543
|
+
setValue(newCode);
|
|
1544
|
+
if (props.onChange) {
|
|
1545
|
+
props.onChange(newCode);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
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 }));
|
|
1549
|
+
}
|
|
1550
|
+
function codeToValueSetElement(code) {
|
|
1551
|
+
return code ? { code } : undefined;
|
|
1552
|
+
}
|
|
1553
|
+
function valueSetElementToCode(element) {
|
|
1554
|
+
return element?.code;
|
|
1555
|
+
}
|
|
1627
1556
|
|
|
1628
|
-
const useStyles$
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
margin: 0,
|
|
1557
|
+
const useStyles$f = core$1.createStyles((theme) => {
|
|
1558
|
+
return {
|
|
1559
|
+
menuTitle: {
|
|
1560
|
+
margin: '20px 0 4px 6px',
|
|
1561
|
+
fontSize: '9px',
|
|
1562
|
+
fontWeight: 'normal',
|
|
1563
|
+
textTransform: 'uppercase',
|
|
1564
|
+
letterSpacing: '2px',
|
|
1637
1565
|
},
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1566
|
+
link: {
|
|
1567
|
+
...theme.fn.focusStyles(),
|
|
1568
|
+
display: 'flex',
|
|
1569
|
+
alignItems: 'center',
|
|
1570
|
+
textDecoration: 'none',
|
|
1571
|
+
fontSize: theme.fontSizes.sm,
|
|
1572
|
+
color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],
|
|
1573
|
+
padding: `8px 12px`,
|
|
1574
|
+
borderRadius: theme.radius.sm,
|
|
1575
|
+
fontWeight: 500,
|
|
1576
|
+
'&:hover': {
|
|
1577
|
+
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
|
1578
|
+
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
|
|
1579
|
+
textDecoration: 'none',
|
|
1580
|
+
[`& svg`]: {
|
|
1581
|
+
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
'& svg': {
|
|
1585
|
+
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],
|
|
1586
|
+
marginRight: theme.spacing.sm,
|
|
1587
|
+
strokeWidth: 1.5,
|
|
1588
|
+
width: 18,
|
|
1589
|
+
height: 18,
|
|
1590
|
+
},
|
|
1644
1591
|
},
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1592
|
+
linkActive: {
|
|
1593
|
+
'&, &:hover': {
|
|
1594
|
+
backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,
|
|
1595
|
+
color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,
|
|
1596
|
+
[`& svg`]: {
|
|
1597
|
+
color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,
|
|
1598
|
+
},
|
|
1599
|
+
},
|
|
1600
|
+
},
|
|
1601
|
+
};
|
|
1602
|
+
});
|
|
1603
|
+
function Navbar(props) {
|
|
1604
|
+
const { classes } = useStyles$f();
|
|
1605
|
+
const navigate = useMedplumNavigate();
|
|
1606
|
+
const [bookmarkDialogVisible, setBookmarkDialogVisible] = React.useState(false);
|
|
1607
|
+
function onLinkClick(e, to) {
|
|
1608
|
+
e.stopPropagation();
|
|
1609
|
+
e.preventDefault();
|
|
1610
|
+
navigate(to);
|
|
1611
|
+
if (window.innerWidth < 768) {
|
|
1612
|
+
props.closeNavbar();
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
function navigateResourceType(resourceType) {
|
|
1616
|
+
if (resourceType) {
|
|
1617
|
+
navigate(`/${resourceType}`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1653
1620
|
return (React.createElement(React.Fragment, null,
|
|
1654
|
-
React.createElement(
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1621
|
+
React.createElement(core$1.Navbar, { width: { sm: 250 }, p: "xs" },
|
|
1622
|
+
React.createElement(core$1.Navbar.Section, { mb: "sm" },
|
|
1623
|
+
React.createElement(CodeInput, { key: window.location.pathname, name: "resourceType", placeholder: "Resource Type", property: {
|
|
1624
|
+
binding: {
|
|
1625
|
+
valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',
|
|
1626
|
+
},
|
|
1627
|
+
}, onChange: (newValue) => navigateResourceType(newValue), creatable: false, maxSelectedValues: 0, clearSearchOnChange: true, clearable: false })),
|
|
1628
|
+
React.createElement(core$1.Navbar.Section, { grow: true },
|
|
1629
|
+
props.menus?.map((menu) => (React.createElement(React.Fragment, { key: `menu-${menu.title}` },
|
|
1630
|
+
React.createElement(core$1.Text, { className: classes.menuTitle }, menu.title),
|
|
1631
|
+
menu.links?.map((link) => (React.createElement(NavbarLink, { key: link.href, to: link.href, onClick: (e) => onLinkClick(e, link.href) },
|
|
1632
|
+
React.createElement(NavLinkIcon, { to: link.href, icon: link.icon }),
|
|
1633
|
+
React.createElement("span", null, link.label))))))),
|
|
1634
|
+
props.displayAddBookmark && (React.createElement(core$1.Button, { variant: "subtle", size: "xs", mt: "xl", leftIcon: React.createElement(IconPlus, { size: "0.75rem" }), onClick: () => setBookmarkDialogVisible(true) }, "Add Bookmark")))),
|
|
1635
|
+
React.createElement(BookmarkDialog, { visible: bookmarkDialogVisible, onOk: () => setBookmarkDialogVisible(false), onCancel: () => setBookmarkDialogVisible(false) })));
|
|
1636
|
+
}
|
|
1637
|
+
function NavbarLink(props) {
|
|
1638
|
+
const { classes, cx } = useStyles$f();
|
|
1639
|
+
const location = reactRouterDom.useLocation();
|
|
1640
|
+
const [searchParams] = reactRouterDom.useSearchParams();
|
|
1641
|
+
const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);
|
|
1642
|
+
const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);
|
|
1643
|
+
return (React.createElement(MedplumLink, { onClick: props.onClick, to: props.to, className: cx(classes.link, { [classes.linkActive]: isActive }) }, props.children));
|
|
1660
1644
|
}
|
|
1661
|
-
|
|
1645
|
+
/**
|
|
1646
|
+
* Returns true if the search params match.
|
|
1647
|
+
* @param searchParams The current search params.
|
|
1648
|
+
* @param toUrl The destination URL of the link.
|
|
1649
|
+
* @returns True if the search params match.
|
|
1650
|
+
*/
|
|
1651
|
+
function matchesParams(searchParams, toUrl) {
|
|
1652
|
+
for (const [key, value] of toUrl.searchParams.entries()) {
|
|
1653
|
+
if (searchParams.get(key) !== value) {
|
|
1654
|
+
return false;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return true;
|
|
1658
|
+
}
|
|
1659
|
+
function NavLinkIcon(props) {
|
|
1660
|
+
if (props.icon) {
|
|
1661
|
+
return props.icon;
|
|
1662
|
+
}
|
|
1663
|
+
return React.createElement(core$1.Space, { w: 30 });
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function AppShell(props) {
|
|
1667
|
+
const theme = core$1.useMantineTheme();
|
|
1668
|
+
const [navbarOpen, setNavbarOpen] = React.useState(localStorage['navbarOpen'] === 'true');
|
|
1669
|
+
const medplum = useMedplum();
|
|
1670
|
+
const profile = useMedplumProfile();
|
|
1671
|
+
function setNavbarOpenWrapper(open) {
|
|
1672
|
+
localStorage['navbarOpen'] = open.toString();
|
|
1673
|
+
setNavbarOpen(open);
|
|
1674
|
+
}
|
|
1675
|
+
function closeNavbar() {
|
|
1676
|
+
setNavbarOpenWrapper(false);
|
|
1677
|
+
}
|
|
1678
|
+
function toggleNavbar() {
|
|
1679
|
+
setNavbarOpenWrapper(!navbarOpen);
|
|
1680
|
+
}
|
|
1681
|
+
if (medplum.isLoading()) {
|
|
1682
|
+
return React.createElement(Loading, null);
|
|
1683
|
+
}
|
|
1684
|
+
return (React.createElement(core$1.AppShell, { styles: {
|
|
1685
|
+
main: {
|
|
1686
|
+
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],
|
|
1687
|
+
},
|
|
1688
|
+
}, 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, displayAddBookmark: props.displayAddBookmark })) : undefined },
|
|
1689
|
+
React.createElement(ErrorBoundary, null,
|
|
1690
|
+
React.createElement(React.Suspense, { fallback: React.createElement(Loading, null) }, props.children))));
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function AttachmentDisplay(props) {
|
|
1694
|
+
const value = props.value;
|
|
1695
|
+
const { contentType, url, title } = value ?? {};
|
|
1696
|
+
if (!url) {
|
|
1697
|
+
return null;
|
|
1698
|
+
}
|
|
1699
|
+
return (React.createElement("div", { "data-testid": "attachment-display" },
|
|
1700
|
+
contentType?.startsWith('image/') && (React.createElement("img", { "data-testid": "attachment-image", style: { maxWidth: props.maxWidth }, src: url, alt: value?.title })),
|
|
1701
|
+
contentType?.startsWith('video/') && (React.createElement("video", { "data-testid": "attachment-video", style: { maxWidth: props.maxWidth }, controls: true },
|
|
1702
|
+
React.createElement("source", { type: contentType, src: url }))),
|
|
1703
|
+
contentType === 'application/pdf' && !title?.endsWith('.pdf') && (React.createElement("div", { "data-testid": "attachment-pdf", style: { maxWidth: props.maxWidth, minHeight: 400 } },
|
|
1704
|
+
React.createElement("iframe", { width: "100%", height: "400", src: url + '#navpanes=0', allowFullScreen: true, frameBorder: 0, seamless: true }))),
|
|
1705
|
+
React.createElement("div", { "data-testid": "download-link", style: { padding: '2px 16px 16px 16px' } },
|
|
1706
|
+
React.createElement(core$1.Anchor, { href: value?.url, "data-testid": "attachment-details", target: "_blank", rel: "noopener noreferrer" }, value?.title || 'Download'))));
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function AttachmentArrayDisplay(props) {
|
|
1710
|
+
return (React.createElement("div", null, props.values &&
|
|
1711
|
+
props.values.map((v, index) => (React.createElement("div", { key: 'attatchment-' + index },
|
|
1712
|
+
React.createElement(AttachmentDisplay, { value: v, maxWidth: props.maxWidth }))))));
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
function AttachmentButton(props) {
|
|
1716
|
+
const medplum = useMedplum();
|
|
1717
|
+
const fileInputRef = React.useRef(null);
|
|
1718
|
+
function onClick(e) {
|
|
1719
|
+
killEvent(e);
|
|
1720
|
+
fileInputRef.current?.click();
|
|
1721
|
+
}
|
|
1722
|
+
function onFileChange(e) {
|
|
1723
|
+
killEvent(e);
|
|
1724
|
+
const files = e.target.files;
|
|
1725
|
+
if (files) {
|
|
1726
|
+
Array.from(files).forEach(processFile);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Processes a single file.
|
|
1731
|
+
*
|
|
1732
|
+
* @param {File} file The file descriptor.
|
|
1733
|
+
*/
|
|
1734
|
+
function processFile(file) {
|
|
1735
|
+
if (!file) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
const fileName = file.name;
|
|
1739
|
+
if (!fileName) {
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (props.onUploadStart) {
|
|
1743
|
+
props.onUploadStart();
|
|
1744
|
+
}
|
|
1745
|
+
const filename = file.name;
|
|
1746
|
+
const contentType = file.type || 'application/octet-stream';
|
|
1747
|
+
medplum
|
|
1748
|
+
.createBinary(file, filename, contentType, props.onUploadProgress)
|
|
1749
|
+
.then((binary) => {
|
|
1750
|
+
props.onUpload({
|
|
1751
|
+
contentType: binary.contentType,
|
|
1752
|
+
url: binary.url,
|
|
1753
|
+
title: filename,
|
|
1754
|
+
});
|
|
1755
|
+
})
|
|
1756
|
+
.catch((outcome) => {
|
|
1757
|
+
alert(outcome?.issue?.[0]?.details?.text);
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
return (React.createElement(React.Fragment, null,
|
|
1761
|
+
React.createElement("input", { type: "file", "data-testid": "upload-file-input", style: { display: 'none' }, ref: fileInputRef, onChange: (e) => onFileChange(e) }),
|
|
1762
|
+
props.children({ onClick })));
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
function AttachmentArrayInput(props) {
|
|
1766
|
+
const [values, setValues] = React.useState(props.defaultValue ?? []);
|
|
1767
|
+
const valuesRef = React.useRef();
|
|
1768
|
+
valuesRef.current = values;
|
|
1769
|
+
function setValuesWrapper(newValues) {
|
|
1770
|
+
setValues(newValues);
|
|
1771
|
+
if (props.onChange) {
|
|
1772
|
+
props.onChange(newValues);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return (React.createElement("table", { style: { width: '100%' } },
|
|
1776
|
+
React.createElement("colgroup", null,
|
|
1777
|
+
React.createElement("col", { width: "97%" }),
|
|
1778
|
+
React.createElement("col", { width: "3%" })),
|
|
1779
|
+
React.createElement("tbody", null,
|
|
1780
|
+
values.map((v, index) => (React.createElement("tr", { key: `${index}-${values.length}` },
|
|
1781
|
+
React.createElement("td", null,
|
|
1782
|
+
React.createElement(AttachmentDisplay, { value: v, maxWidth: 200 })),
|
|
1783
|
+
React.createElement("td", null,
|
|
1784
|
+
React.createElement(core$1.ActionIcon, { title: "Remove", size: "sm", onClick: (e) => {
|
|
1785
|
+
killEvent(e);
|
|
1786
|
+
const copy = values.slice();
|
|
1787
|
+
copy.splice(index, 1);
|
|
1788
|
+
setValuesWrapper(copy);
|
|
1789
|
+
} },
|
|
1790
|
+
React.createElement(IconCircleMinus, null)))))),
|
|
1791
|
+
React.createElement("tr", null,
|
|
1792
|
+
React.createElement("td", null),
|
|
1793
|
+
React.createElement("td", null,
|
|
1794
|
+
React.createElement(AttachmentButton, { onUpload: (attachment) => {
|
|
1795
|
+
setValuesWrapper([...valuesRef.current, attachment]);
|
|
1796
|
+
} }, (props) => (React.createElement(core$1.ActionIcon, { ...props, title: "Add", size: "sm", color: "green" },
|
|
1797
|
+
React.createElement(IconCloudUpload, { size: 16 })))))))));
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function AttachmentInput(props) {
|
|
1801
|
+
const [value, setValue] = React.useState(props.defaultValue);
|
|
1802
|
+
function setValueWrapper(newValue) {
|
|
1803
|
+
setValue(newValue);
|
|
1804
|
+
if (props.onChange) {
|
|
1805
|
+
props.onChange(newValue);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (value) {
|
|
1809
|
+
return (React.createElement(React.Fragment, null,
|
|
1810
|
+
React.createElement(AttachmentDisplay, { value: value, maxWidth: 200 }),
|
|
1811
|
+
React.createElement(core$1.Button, { onClick: (e) => {
|
|
1812
|
+
killEvent(e);
|
|
1813
|
+
setValueWrapper(undefined);
|
|
1814
|
+
} }, "Remove")));
|
|
1815
|
+
}
|
|
1816
|
+
return (React.createElement(AttachmentButton, { onUpload: setValueWrapper }, (props) => React.createElement(core$1.Button, { ...props }, "Upload...")));
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const DEFAULT_IGNORED_PROPERTIES = [
|
|
1820
|
+
'meta',
|
|
1821
|
+
'implicitRules',
|
|
1822
|
+
'language',
|
|
1823
|
+
'text',
|
|
1824
|
+
'contained',
|
|
1825
|
+
'extension',
|
|
1826
|
+
'modifierExtension',
|
|
1827
|
+
];
|
|
1828
|
+
|
|
1829
|
+
const useStyles$e = core$1.createStyles((theme) => ({
|
|
1830
|
+
root: {
|
|
1831
|
+
display: 'grid',
|
|
1832
|
+
gridTemplateColumns: '30% 70%',
|
|
1833
|
+
margin: 0,
|
|
1834
|
+
'& > dt, & > dd': {
|
|
1835
|
+
padding: `${theme.spacing.sm} ${theme.spacing.sm}`,
|
|
1836
|
+
borderTop: `0.1px solid ${theme.colors.gray[3]}`,
|
|
1837
|
+
margin: 0,
|
|
1838
|
+
},
|
|
1839
|
+
},
|
|
1840
|
+
compact: {
|
|
1841
|
+
gridTemplateColumns: '20% 80%',
|
|
1842
|
+
'& > dt, & > dd': {
|
|
1843
|
+
padding: `0 ${theme.spacing.xs} ${theme.spacing.xs} 0`,
|
|
1844
|
+
border: 0,
|
|
1845
|
+
},
|
|
1846
|
+
},
|
|
1847
|
+
}));
|
|
1848
|
+
function DescriptionList(props) {
|
|
1849
|
+
const { children, compact } = props;
|
|
1850
|
+
const { classes, cx } = useStyles$e();
|
|
1851
|
+
return React.createElement("dl", { className: cx(classes.root, { [classes.compact]: compact }) }, children);
|
|
1852
|
+
}
|
|
1853
|
+
function DescriptionListEntry(props) {
|
|
1854
|
+
return (React.createElement(React.Fragment, null,
|
|
1855
|
+
React.createElement("dt", null, props.term),
|
|
1856
|
+
React.createElement("dd", null, props.children)));
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function CodeableConceptDisplay(props) {
|
|
1860
|
+
return React.createElement(React.Fragment, null, core.formatCodeableConcept(props.value));
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1662
1863
|
function CodingDisplay(props) {
|
|
1663
1864
|
return React.createElement(React.Fragment, null, core.formatCoding(props.value));
|
|
1664
1865
|
}
|
|
@@ -1699,14 +1900,6 @@
|
|
|
1699
1900
|
contactDetail.telecom?.map((telecom, index) => (React.createElement(ContactPointDisplay, { key: 'telecom-' + index, value: telecom })))));
|
|
1700
1901
|
}
|
|
1701
1902
|
|
|
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
1903
|
function IdentifierDisplay(props) {
|
|
1711
1904
|
return (React.createElement("div", null,
|
|
1712
1905
|
props.value?.system,
|
|
@@ -1737,50 +1930,6 @@
|
|
|
1737
1930
|
React.createElement(QuantityDisplay, { value: value.denominator })));
|
|
1738
1931
|
}
|
|
1739
1932
|
|
|
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
1933
|
function ReferenceDisplay(props) {
|
|
1785
1934
|
if (!props.value) {
|
|
1786
1935
|
return null;
|
|
@@ -1947,69 +2096,36 @@
|
|
|
1947
2096
|
React.createElement(core$1.Input.Wrapper, { id: props.htmlFor, label: props.title, description: props.description, withAsterisk: props.withAsterisk }, (() => null)()))));
|
|
1948
2097
|
}
|
|
1949
2098
|
|
|
1950
|
-
function
|
|
1951
|
-
return
|
|
2099
|
+
function getErrorsForInput(outcome, expression) {
|
|
2100
|
+
return outcome?.issue
|
|
2101
|
+
?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression))
|
|
2102
|
+
?.map((issue) => issue.details?.text)
|
|
2103
|
+
?.join('\n');
|
|
1952
2104
|
}
|
|
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;
|
|
2105
|
+
function getIssuesForExpression(outcome, expression) {
|
|
2106
|
+
return outcome?.issue?.filter((issue) => isExpressionMatch(issue.expression?.[0], expression));
|
|
1993
2107
|
}
|
|
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
|
-
}
|
|
2108
|
+
function isExpressionMatch(expr1, expr2) {
|
|
2109
|
+
// Expression can be either "fieldName" or "resourceType.fieldName"
|
|
2110
|
+
if (expr1 === expr2) {
|
|
2111
|
+
return true;
|
|
2011
2112
|
}
|
|
2012
|
-
|
|
2113
|
+
if (!expr1 || !expr2) {
|
|
2114
|
+
return false;
|
|
2115
|
+
}
|
|
2116
|
+
const dot1 = expr1.indexOf('.');
|
|
2117
|
+
if (dot1 >= 0 && expr1.substring(dot1 + 1) === expr2) {
|
|
2118
|
+
return true;
|
|
2119
|
+
}
|
|
2120
|
+
const dot2 = expr2.indexOf('.');
|
|
2121
|
+
if (dot2 >= 0 && expr2.substring(dot2 + 1) === expr1) {
|
|
2122
|
+
return true;
|
|
2123
|
+
}
|
|
2124
|
+
return false;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
function FormSection(props) {
|
|
2128
|
+
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
2129
|
}
|
|
2014
2130
|
|
|
2015
2131
|
function ResourceForm(props) {
|
|
@@ -2058,46 +2174,6 @@
|
|
|
2058
2174
|
return obj;
|
|
2059
2175
|
}
|
|
2060
2176
|
|
|
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
2177
|
function CodeableConceptInput(props) {
|
|
2102
2178
|
const [value, setValue] = React.useState(props.defaultValue);
|
|
2103
2179
|
function handleChange(newValues) {
|
|
@@ -2129,25 +2205,6 @@
|
|
|
2129
2205
|
};
|
|
2130
2206
|
}
|
|
2131
2207
|
|
|
2132
|
-
function CodeInput(props) {
|
|
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
2208
|
function CodingInput(props) {
|
|
2152
2209
|
const [value, setValue] = React.useState(props.defaultValue);
|
|
2153
2210
|
function handleChange(newValues) {
|
|
@@ -2518,21 +2575,6 @@
|
|
|
2518
2575
|
}) })));
|
|
2519
2576
|
}
|
|
2520
2577
|
|
|
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
2578
|
/**
|
|
2537
2579
|
* Defines which search parameters will be used by the type ahead to search for each resourceType
|
|
2538
2580
|
*/
|
|
@@ -2924,7 +2966,7 @@
|
|
|
2924
2966
|
})));
|
|
2925
2967
|
}
|
|
2926
2968
|
|
|
2927
|
-
const useStyles$
|
|
2969
|
+
const useStyles$d = core$1.createStyles((theme) => ({
|
|
2928
2970
|
table: {
|
|
2929
2971
|
width: 350,
|
|
2930
2972
|
'& th': {
|
|
@@ -2969,7 +3011,7 @@
|
|
|
2969
3011
|
return date.toLocaleString('default', { month: 'long' }) + ' ' + date.getFullYear();
|
|
2970
3012
|
}
|
|
2971
3013
|
function CalendarInput(props) {
|
|
2972
|
-
const { classes } = useStyles$
|
|
3014
|
+
const { classes } = useStyles$d();
|
|
2973
3015
|
const { onChangeMonth, onClick } = props;
|
|
2974
3016
|
const [month, setMonth] = React.useState(getStartMonth);
|
|
2975
3017
|
function moveMonth(delta) {
|
|
@@ -3052,13 +3094,27 @@
|
|
|
3052
3094
|
return false;
|
|
3053
3095
|
}
|
|
3054
3096
|
|
|
3055
|
-
const useStyles$
|
|
3097
|
+
const useStyles$c = core$1.createStyles(() => ({
|
|
3098
|
+
root: {
|
|
3099
|
+
'@media (max-width: 800px)': {
|
|
3100
|
+
paddingLeft: 4,
|
|
3101
|
+
paddingRight: 4,
|
|
3102
|
+
},
|
|
3103
|
+
},
|
|
3104
|
+
}));
|
|
3105
|
+
function Container(props) {
|
|
3106
|
+
const { children, ...others } = props;
|
|
3107
|
+
const { classes } = useStyles$c();
|
|
3108
|
+
return (React.createElement(core$1.Container, { className: classes.root, ...others }, children));
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
const useStyles$b = core$1.createStyles((theme) => ({
|
|
3056
3112
|
noteBody: { fontSize: theme.fontSizes.sm },
|
|
3057
3113
|
noteCite: { fontSize: theme.fontSizes.xs, marginBlockStart: 3 },
|
|
3058
3114
|
noteRoot: { padding: 5 },
|
|
3059
3115
|
}));
|
|
3060
3116
|
function NoteDisplay({ value }) {
|
|
3061
|
-
const { classes } = useStyles$
|
|
3117
|
+
const { classes } = useStyles$b();
|
|
3062
3118
|
if (!value) {
|
|
3063
3119
|
return null;
|
|
3064
3120
|
}
|
|
@@ -3151,7 +3207,7 @@
|
|
|
3151
3207
|
return React.createElement(core$1.Badge, { color: statusToColor[props.status] }, props.status);
|
|
3152
3208
|
}
|
|
3153
3209
|
|
|
3154
|
-
const useStyles$
|
|
3210
|
+
const useStyles$a = core$1.createStyles((theme) => ({
|
|
3155
3211
|
table: {
|
|
3156
3212
|
border: `0.1px solid ${theme.colors.gray[5]}`,
|
|
3157
3213
|
borderCollapse: 'collapse',
|
|
@@ -3246,7 +3302,7 @@
|
|
|
3246
3302
|
core.formatDateTime(specimen.receivedTime)))))))));
|
|
3247
3303
|
}
|
|
3248
3304
|
function ObservationTable(props) {
|
|
3249
|
-
const { classes } = useStyles$
|
|
3305
|
+
const { classes } = useStyles$a();
|
|
3250
3306
|
return (React.createElement("table", { className: classes.table },
|
|
3251
3307
|
React.createElement("thead", null,
|
|
3252
3308
|
React.createElement("tr", null,
|
|
@@ -3260,7 +3316,7 @@
|
|
|
3260
3316
|
React.createElement("tbody", null, props.value?.map((observation, index) => (React.createElement(ObservationRow, { key: `obs-${observation.id}-${index}`, hideObservationNotes: props.hideObservationNotes, value: observation }))))));
|
|
3261
3317
|
}
|
|
3262
3318
|
function ObservationRow(props) {
|
|
3263
|
-
const { classes, cx } = useStyles$
|
|
3319
|
+
const { classes, cx } = useStyles$a();
|
|
3264
3320
|
const observation = useResource(props.value);
|
|
3265
3321
|
if (!observation) {
|
|
3266
3322
|
return null;
|
|
@@ -3310,6 +3366,35 @@
|
|
|
3310
3366
|
return code === 'AA' || code === 'LL' || code === 'HH' || code === 'A';
|
|
3311
3367
|
}
|
|
3312
3368
|
|
|
3369
|
+
const useStyles$9 = core$1.createStyles((theme, { width, fill }) => ({
|
|
3370
|
+
paper: {
|
|
3371
|
+
maxWidth: width,
|
|
3372
|
+
margin: `${theme.spacing.xl} auto`,
|
|
3373
|
+
padding: fill ? 0 : theme.spacing.md,
|
|
3374
|
+
'@media (max-width: 800px)': {
|
|
3375
|
+
padding: fill ? 0 : 8,
|
|
3376
|
+
},
|
|
3377
|
+
'& img': {
|
|
3378
|
+
width: '100%',
|
|
3379
|
+
maxWidth: '100%',
|
|
3380
|
+
},
|
|
3381
|
+
'& video': {
|
|
3382
|
+
width: '100%',
|
|
3383
|
+
maxWidth: '100%',
|
|
3384
|
+
},
|
|
3385
|
+
},
|
|
3386
|
+
}));
|
|
3387
|
+
const defaultProps$1 = {
|
|
3388
|
+
shadow: 'xs',
|
|
3389
|
+
radius: 'md',
|
|
3390
|
+
withBorder: true,
|
|
3391
|
+
};
|
|
3392
|
+
function Panel(props) {
|
|
3393
|
+
const { className, children, width, fill, unstyled, ...others } = core$1.useComponentDefaultProps('Panel', defaultProps$1, props);
|
|
3394
|
+
const { classes, cx } = useStyles$9({ width, fill }, { name: 'Panel', unstyled });
|
|
3395
|
+
return (React.createElement(core$1.Paper, { className: cx(classes.paper, className), ...others }, children));
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3313
3398
|
const useStyles$8 = core$1.createStyles((theme) => ({
|
|
3314
3399
|
root: {
|
|
3315
3400
|
borderCollapse: 'collapse',
|
|
@@ -3400,29 +3485,6 @@
|
|
|
3400
3485
|
}, ignoreMissingValues: props.ignoreMissingValues }));
|
|
3401
3486
|
}
|
|
3402
3487
|
|
|
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
3488
|
function Timeline(props) {
|
|
3427
3489
|
return React.createElement(Container, null, props.children);
|
|
3428
3490
|
}
|
|
@@ -3783,10 +3845,21 @@
|
|
|
3783
3845
|
|
|
3784
3846
|
function DefaultResourceTimeline(props) {
|
|
3785
3847
|
return (React.createElement(ResourceTimeline, { value: props.resource, loadTimelineResources: async (medplum, resourceType, id) => {
|
|
3786
|
-
|
|
3848
|
+
const ref = `${resourceType}/${id}`;
|
|
3849
|
+
const _count = 100;
|
|
3850
|
+
return Promise.allSettled([
|
|
3851
|
+
medplum.readHistory(resourceType, id),
|
|
3852
|
+
medplum.search('Task', { _filter: `based-on eq ${ref} or focus eq ${ref} or subject eq ${ref}`, _count }),
|
|
3853
|
+
]);
|
|
3787
3854
|
} }));
|
|
3788
3855
|
}
|
|
3789
3856
|
|
|
3857
|
+
function Document(props) {
|
|
3858
|
+
const { children, ...others } = props;
|
|
3859
|
+
return (React.createElement(Container, null,
|
|
3860
|
+
React.createElement(Panel, { ...others }, children)));
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3790
3863
|
function EncounterTimeline(props) {
|
|
3791
3864
|
return (React.createElement(ResourceTimeline, { value: props.encounter, loadTimelineResources: async (medplum, _resourceType, id) => {
|
|
3792
3865
|
return Promise.allSettled([
|
|
@@ -3895,6 +3968,7 @@
|
|
|
3895
3968
|
'of-type': 'of type',
|
|
3896
3969
|
missing: 'missing',
|
|
3897
3970
|
identifier: 'identifier',
|
|
3971
|
+
iterate: 'iterate',
|
|
3898
3972
|
};
|
|
3899
3973
|
/**
|
|
3900
3974
|
* Sets the array of filters.
|
|
@@ -4565,7 +4639,7 @@
|
|
|
4565
4639
|
return null;
|
|
4566
4640
|
}
|
|
4567
4641
|
const resourceType = props.search.resourceType;
|
|
4568
|
-
const searchParams = core.globalSchema.types[resourceType].searchParams;
|
|
4642
|
+
const searchParams = core.globalSchema.types[resourceType].searchParams ?? {};
|
|
4569
4643
|
const filters = search.filters || [];
|
|
4570
4644
|
return (React.createElement(core$1.Modal, { title: "Filters", closeButtonProps: { 'aria-label': 'Close' }, size: 900, opened: props.visible, onClose: props.onCancel },
|
|
4571
4645
|
React.createElement("div", null,
|
|
@@ -5056,8 +5130,8 @@
|
|
|
5056
5130
|
return (React.createElement("div", { className: classes.root, "data-testid": "search-control" },
|
|
5057
5131
|
!props.hideToolbar && (React.createElement(core$1.Group, { position: "apart", mb: "xl" },
|
|
5058
5132
|
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(
|
|
5133
|
+
React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconColumns, { size: iconSize }), onClick: () => setState({ ...stateRef.current, fieldEditorVisible: true }) }, "Fields"),
|
|
5134
|
+
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
5135
|
props.onNew && (React.createElement(core$1.Button, { compact: true, variant: buttonVariant, color: buttonColor, leftIcon: React.createElement(IconFilePlus, { size: iconSize }), onClick: props.onNew }, "New...")),
|
|
5062
5136
|
!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
5137
|
!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,16 +5367,37 @@
|
|
|
5293
5367
|
}
|
|
5294
5368
|
const MemoizedFhirPathTable = React.memo(FhirPathTable);
|
|
5295
5369
|
|
|
5370
|
+
function Logo(props) {
|
|
5371
|
+
return (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 491 491", style: { width: props.size, height: props.size } },
|
|
5372
|
+
React.createElement("title", null, "Medplum Logo"),
|
|
5373
|
+
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" }),
|
|
5374
|
+
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" }),
|
|
5375
|
+
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" }),
|
|
5376
|
+
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" }),
|
|
5377
|
+
React.createElement("path", { fill: props.fill || '#33961e', d: "M252 48A421 421 0 0057 270l-27-7A176 176 0 01251 43l1 5z" })));
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
function OperationOutcomeAlert(props) {
|
|
5381
|
+
const issues = props.outcome?.issue || props.issues;
|
|
5382
|
+
if (!issues) {
|
|
5383
|
+
return null;
|
|
5384
|
+
}
|
|
5385
|
+
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)))));
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5296
5388
|
function PatientTimeline(props) {
|
|
5297
|
-
const loadTimelineResources = React.useCallback((medplum,
|
|
5389
|
+
const loadTimelineResources = React.useCallback((medplum, resourceType, id) => {
|
|
5390
|
+
const ref = `${resourceType}/${id}`;
|
|
5391
|
+
const _count = 100;
|
|
5298
5392
|
return Promise.allSettled([
|
|
5299
5393
|
medplum.readHistory('Patient', id),
|
|
5300
|
-
medplum.search('Communication',
|
|
5301
|
-
medplum.search('Device',
|
|
5302
|
-
medplum.search('DeviceRequest',
|
|
5303
|
-
medplum.search('DiagnosticReport',
|
|
5304
|
-
medplum.search('Media',
|
|
5305
|
-
medplum.search('ServiceRequest',
|
|
5394
|
+
medplum.search('Communication', { subject: ref, _count }),
|
|
5395
|
+
medplum.search('Device', { patient: ref, _count }),
|
|
5396
|
+
medplum.search('DeviceRequest', { patient: ref, _count }),
|
|
5397
|
+
medplum.search('DiagnosticReport', { subject: ref, _count }),
|
|
5398
|
+
medplum.search('Media', { subject: ref, _count }),
|
|
5399
|
+
medplum.search('ServiceRequest', { subject: ref, _count }),
|
|
5400
|
+
medplum.search('Task', { subject: ref, _count }),
|
|
5306
5401
|
]);
|
|
5307
5402
|
}, []);
|
|
5308
5403
|
return (React.createElement(ResourceTimeline, { value: props.patient, loadTimelineResources: loadTimelineResources, createCommunication: (resource, sender, text) => ({
|
|
@@ -6892,12 +6987,15 @@
|
|
|
6892
6987
|
}
|
|
6893
6988
|
|
|
6894
6989
|
function ServiceRequestTimeline(props) {
|
|
6895
|
-
return (React.createElement(ResourceTimeline, { value: props.serviceRequest, loadTimelineResources: async (medplum,
|
|
6990
|
+
return (React.createElement(ResourceTimeline, { value: props.serviceRequest, loadTimelineResources: async (medplum, resourceType, id) => {
|
|
6991
|
+
const ref = `${resourceType}/${id}`;
|
|
6992
|
+
const _count = 100;
|
|
6896
6993
|
return Promise.allSettled([
|
|
6897
6994
|
medplum.readHistory('ServiceRequest', id),
|
|
6898
|
-
medplum.search('Communication', 'based-on
|
|
6899
|
-
medplum.search('
|
|
6900
|
-
medplum.search('
|
|
6995
|
+
medplum.search('Communication', { 'based-on': ref, _count }),
|
|
6996
|
+
medplum.search('DiagnosticReport', { 'based-on': ref, _count }),
|
|
6997
|
+
medplum.search('Media', { 'based-on': ref, _count }),
|
|
6998
|
+
medplum.search('Task', { _filter: `based-on eq ${ref} or focus eq ${ref} or subject eq ${ref}`, _count }),
|
|
6901
6999
|
]);
|
|
6902
7000
|
}, createCommunication: (resource, sender, text) => ({
|
|
6903
7001
|
resourceType: 'Communication',
|
|
@@ -6918,9 +7016,455 @@
|
|
|
6918
7016
|
}) }));
|
|
6919
7017
|
}
|
|
6920
7018
|
|
|
7019
|
+
function NewProjectForm(props) {
|
|
7020
|
+
const medplum = useMedplum();
|
|
7021
|
+
const [outcome, setOutcome] = React.useState();
|
|
7022
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: async (formData) => {
|
|
7023
|
+
try {
|
|
7024
|
+
props.handleAuthResponse(await medplum.startNewProject({
|
|
7025
|
+
login: props.login,
|
|
7026
|
+
projectName: formData.projectName,
|
|
7027
|
+
}));
|
|
7028
|
+
}
|
|
7029
|
+
catch (err) {
|
|
7030
|
+
setOutcome(err);
|
|
7031
|
+
}
|
|
7032
|
+
} },
|
|
7033
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7034
|
+
React.createElement(Logo, { size: 32 }),
|
|
7035
|
+
React.createElement(core$1.Title, null, "Create project")),
|
|
7036
|
+
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
7037
|
+
React.createElement(core$1.TextInput, { name: "projectName", label: "Project Name", placeholder: "My Project", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
7038
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
7039
|
+
"By clicking submit you agree to the Medplum",
|
|
7040
|
+
' ',
|
|
7041
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
7042
|
+
' and ',
|
|
7043
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
7044
|
+
".")),
|
|
7045
|
+
React.createElement(core$1.Group, { position: "right", mt: "xl", noWrap: true },
|
|
7046
|
+
React.createElement(core$1.Button, { type: "submit" }, "Create project"))));
|
|
7047
|
+
}
|
|
7048
|
+
|
|
7049
|
+
/**
|
|
7050
|
+
* Dynamically creates a script tag for the specified JavaScript file.
|
|
7051
|
+
* @param src The JavaScript file URL.
|
|
7052
|
+
*/
|
|
7053
|
+
function createScriptTag(src, onload) {
|
|
7054
|
+
const head = document.getElementsByTagName('head')[0];
|
|
7055
|
+
const script = document.createElement('script');
|
|
7056
|
+
script.async = true;
|
|
7057
|
+
script.src = src;
|
|
7058
|
+
script.onload = onload || null;
|
|
7059
|
+
head.appendChild(script);
|
|
7060
|
+
}
|
|
7061
|
+
|
|
7062
|
+
function GoogleButton(props) {
|
|
7063
|
+
const medplum = useMedplum();
|
|
7064
|
+
const { googleClientId, handleGoogleCredential } = props;
|
|
7065
|
+
const parentRef = React.useRef(null);
|
|
7066
|
+
const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
|
|
7067
|
+
const [initialized, setInitialized] = React.useState(false);
|
|
7068
|
+
const [buttonRendered, setButtonRendered] = React.useState(false);
|
|
7069
|
+
React.useEffect(() => {
|
|
7070
|
+
if (typeof google === 'undefined') {
|
|
7071
|
+
createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
|
|
7072
|
+
return;
|
|
7073
|
+
}
|
|
7074
|
+
if (!initialized) {
|
|
7075
|
+
google.accounts.id.initialize({
|
|
7076
|
+
client_id: googleClientId,
|
|
7077
|
+
callback: handleGoogleCredential,
|
|
7078
|
+
});
|
|
7079
|
+
setInitialized(true);
|
|
7080
|
+
}
|
|
7081
|
+
if (parentRef.current && !buttonRendered) {
|
|
7082
|
+
google.accounts.id.renderButton(parentRef.current, {});
|
|
7083
|
+
setButtonRendered(true);
|
|
7084
|
+
}
|
|
7085
|
+
}, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
|
|
7086
|
+
if (!googleClientId) {
|
|
7087
|
+
return null;
|
|
7088
|
+
}
|
|
7089
|
+
return React.createElement("div", { ref: parentRef });
|
|
7090
|
+
}
|
|
7091
|
+
function getGoogleClientId(clientId) {
|
|
7092
|
+
if (clientId) {
|
|
7093
|
+
return clientId;
|
|
7094
|
+
}
|
|
7095
|
+
if (typeof window !== 'undefined') {
|
|
7096
|
+
const origin = window.location.protocol + '//' + window.location.host;
|
|
7097
|
+
const authorizedOrigins = "undefined"?.split(',') ?? [];
|
|
7098
|
+
if (authorizedOrigins.includes(origin)) {
|
|
7099
|
+
return "undefined";
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
return undefined;
|
|
7103
|
+
}
|
|
7104
|
+
|
|
7105
|
+
/**
|
|
7106
|
+
* Dynamically loads the recaptcha script.
|
|
7107
|
+
* We do not want to load the script on page load unless the user needs it.
|
|
7108
|
+
* @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
|
|
7109
|
+
*/
|
|
7110
|
+
function initRecaptcha(siteKey) {
|
|
7111
|
+
if (typeof grecaptcha === 'undefined') {
|
|
7112
|
+
createScriptTag('https://www.google.com/recaptcha/api.js?render=' + siteKey);
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
/**
|
|
7116
|
+
* Starts a request to generate a recapcha token.
|
|
7117
|
+
* @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
|
|
7118
|
+
* @returns Promise to a recaptcha token for the current user.
|
|
7119
|
+
*/
|
|
7120
|
+
function getRecaptcha(siteKey) {
|
|
7121
|
+
return new Promise((resolve, reject) => {
|
|
7122
|
+
grecaptcha.ready(async () => {
|
|
7123
|
+
try {
|
|
7124
|
+
resolve(await grecaptcha.execute(siteKey, { action: 'submit' }));
|
|
7125
|
+
}
|
|
7126
|
+
catch (err) {
|
|
7127
|
+
reject(err);
|
|
7128
|
+
}
|
|
7129
|
+
});
|
|
7130
|
+
});
|
|
7131
|
+
}
|
|
7132
|
+
|
|
7133
|
+
function NewUserForm(props) {
|
|
7134
|
+
const googleClientId = getGoogleClientId(props.googleClientId);
|
|
7135
|
+
const recaptchaSiteKey = props.recaptchaSiteKey;
|
|
7136
|
+
const medplum = useMedplum();
|
|
7137
|
+
const [outcome, setOutcome] = React.useState();
|
|
7138
|
+
const issues = getIssuesForExpression(outcome, undefined);
|
|
7139
|
+
React.useEffect(() => {
|
|
7140
|
+
if (recaptchaSiteKey) {
|
|
7141
|
+
initRecaptcha(recaptchaSiteKey);
|
|
7142
|
+
}
|
|
7143
|
+
}, [recaptchaSiteKey]);
|
|
7144
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: async (formData) => {
|
|
7145
|
+
try {
|
|
7146
|
+
let recaptchaToken = '';
|
|
7147
|
+
if (recaptchaSiteKey) {
|
|
7148
|
+
recaptchaToken = await getRecaptcha(recaptchaSiteKey);
|
|
7149
|
+
}
|
|
7150
|
+
props.handleAuthResponse(await medplum.startNewUser({
|
|
7151
|
+
projectId: props.projectId,
|
|
7152
|
+
firstName: formData.firstName,
|
|
7153
|
+
lastName: formData.lastName,
|
|
7154
|
+
email: formData.email,
|
|
7155
|
+
password: formData.password,
|
|
7156
|
+
remember: formData.remember === 'true',
|
|
7157
|
+
recaptchaSiteKey,
|
|
7158
|
+
recaptchaToken,
|
|
7159
|
+
}));
|
|
7160
|
+
}
|
|
7161
|
+
catch (err) {
|
|
7162
|
+
setOutcome(err);
|
|
7163
|
+
}
|
|
7164
|
+
} },
|
|
7165
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, props.children),
|
|
7166
|
+
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
7167
|
+
googleClientId && (React.createElement(React.Fragment, null,
|
|
7168
|
+
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
7169
|
+
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: async (response) => {
|
|
7170
|
+
try {
|
|
7171
|
+
props.handleAuthResponse(await medplum.startGoogleLogin({
|
|
7172
|
+
googleClientId: response.clientId,
|
|
7173
|
+
googleCredential: response.credential,
|
|
7174
|
+
createUser: true,
|
|
7175
|
+
}));
|
|
7176
|
+
}
|
|
7177
|
+
catch (err) {
|
|
7178
|
+
setOutcome(err);
|
|
7179
|
+
}
|
|
7180
|
+
} })),
|
|
7181
|
+
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
7182
|
+
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
7183
|
+
React.createElement(core$1.TextInput, { name: "firstName", type: "text", label: "First name", placeholder: "First name", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'firstName') }),
|
|
7184
|
+
React.createElement(core$1.TextInput, { name: "lastName", type: "text", label: "Last name", placeholder: "Last name", required: true, error: getErrorsForInput(outcome, 'lastName') }),
|
|
7185
|
+
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, error: getErrorsForInput(outcome, 'email') }),
|
|
7186
|
+
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') }),
|
|
7187
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
7188
|
+
"By clicking submit you agree to the Medplum",
|
|
7189
|
+
' ',
|
|
7190
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
|
|
7191
|
+
' and ',
|
|
7192
|
+
React.createElement(core$1.Anchor, { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
7193
|
+
"."),
|
|
7194
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" },
|
|
7195
|
+
"This site is protected by reCAPTCHA and the Google",
|
|
7196
|
+
' ',
|
|
7197
|
+
React.createElement(core$1.Anchor, { href: "https://policies.google.com/privacy" }, "Privacy\u00A0Policy"),
|
|
7198
|
+
' and ',
|
|
7199
|
+
React.createElement(core$1.Anchor, { href: "https://policies.google.com/terms" }, "Terms\u00A0of\u00A0Service"),
|
|
7200
|
+
" apply.")),
|
|
7201
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", noWrap: true },
|
|
7202
|
+
React.createElement(core$1.Checkbox, { name: "remember", label: "Remember me", size: "xs" }),
|
|
7203
|
+
React.createElement(core$1.Button, { type: "submit" }, "Create account"))));
|
|
7204
|
+
}
|
|
7205
|
+
|
|
7206
|
+
function RegisterForm(props) {
|
|
7207
|
+
const { type, projectId, googleClientId, recaptchaSiteKey, onSuccess } = props;
|
|
7208
|
+
const medplum = useMedplum();
|
|
7209
|
+
const [login, setLogin] = React.useState(undefined);
|
|
7210
|
+
const [outcome, setOutcome] = React.useState();
|
|
7211
|
+
React.useEffect(() => {
|
|
7212
|
+
if (type === 'patient' && login) {
|
|
7213
|
+
medplum
|
|
7214
|
+
.startNewPatient({ login, projectId: projectId })
|
|
7215
|
+
.then((response) => medplum.processCode(response.code))
|
|
7216
|
+
.then(() => onSuccess())
|
|
7217
|
+
.catch((err) => setOutcome(err));
|
|
7218
|
+
}
|
|
7219
|
+
}, [medplum, type, projectId, login, onSuccess]);
|
|
7220
|
+
function handleAuthResponse(response) {
|
|
7221
|
+
if (response.code) {
|
|
7222
|
+
medplum
|
|
7223
|
+
.processCode(response.code)
|
|
7224
|
+
.then(() => onSuccess())
|
|
7225
|
+
.catch(console.log);
|
|
7226
|
+
}
|
|
7227
|
+
else if (response.login) {
|
|
7228
|
+
setLogin(response.login);
|
|
7229
|
+
}
|
|
7230
|
+
}
|
|
7231
|
+
return (React.createElement(Document, { width: 450 },
|
|
7232
|
+
outcome && React.createElement("pre", null, JSON.stringify(outcome, null, 2)),
|
|
7233
|
+
!login && (React.createElement(NewUserForm, { projectId: projectId, googleClientId: googleClientId, recaptchaSiteKey: recaptchaSiteKey, handleAuthResponse: handleAuthResponse }, props.children)),
|
|
7234
|
+
login && type === 'project' && React.createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse })));
|
|
7235
|
+
}
|
|
7236
|
+
|
|
7237
|
+
function AuthenticationForm(props) {
|
|
7238
|
+
const [email, setEmail] = React.useState();
|
|
7239
|
+
if (!email) {
|
|
7240
|
+
return React.createElement(EmailForm, { setEmail: setEmail, ...props });
|
|
7241
|
+
}
|
|
7242
|
+
else {
|
|
7243
|
+
return React.createElement(PasswordForm, { email: email, ...props });
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
function EmailForm(props) {
|
|
7247
|
+
const { setEmail, onRegister, handleAuthResponse, children, ...baseLoginRequest } = props;
|
|
7248
|
+
const medplum = useMedplum();
|
|
7249
|
+
const googleClientId = !props.disableGoogleAuth && getGoogleClientId(props.googleClientId);
|
|
7250
|
+
const isExternalAuth = React.useCallback(async (authMethod) => {
|
|
7251
|
+
if (!authMethod.authorizeUrl) {
|
|
7252
|
+
return false;
|
|
7253
|
+
}
|
|
7254
|
+
const state = JSON.stringify({
|
|
7255
|
+
...(await medplum.ensureCodeChallenge(baseLoginRequest)),
|
|
7256
|
+
domain: authMethod.domain,
|
|
7257
|
+
});
|
|
7258
|
+
const url = new URL(authMethod.authorizeUrl);
|
|
7259
|
+
url.searchParams.set('state', state);
|
|
7260
|
+
window.location.assign(url.toString());
|
|
7261
|
+
return true;
|
|
7262
|
+
}, [medplum, baseLoginRequest]);
|
|
7263
|
+
const handleSubmit = React.useCallback(async (formData) => {
|
|
7264
|
+
const authMethod = await medplum.post('auth/method', { email: formData.email });
|
|
7265
|
+
if (!(await isExternalAuth(authMethod))) {
|
|
7266
|
+
setEmail(formData.email);
|
|
7267
|
+
}
|
|
7268
|
+
}, [medplum, isExternalAuth, setEmail]);
|
|
7269
|
+
const handleGoogleCredential = React.useCallback(async (response) => {
|
|
7270
|
+
const authResponse = await medplum.startGoogleLogin({
|
|
7271
|
+
...baseLoginRequest,
|
|
7272
|
+
googleCredential: response.credential,
|
|
7273
|
+
});
|
|
7274
|
+
if (!(await isExternalAuth(authResponse))) {
|
|
7275
|
+
handleAuthResponse(authResponse);
|
|
7276
|
+
}
|
|
7277
|
+
}, [medplum, baseLoginRequest, isExternalAuth, handleAuthResponse]);
|
|
7278
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
7279
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
7280
|
+
googleClientId && (React.createElement(React.Fragment, null,
|
|
7281
|
+
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
7282
|
+
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: handleGoogleCredential })),
|
|
7283
|
+
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
7284
|
+
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true }),
|
|
7285
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
7286
|
+
React.createElement("div", null, onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register"))),
|
|
7287
|
+
React.createElement(core$1.Button, { type: "submit" }, "Next"))));
|
|
7288
|
+
}
|
|
7289
|
+
function PasswordForm(props) {
|
|
7290
|
+
const { onForgotPassword, handleAuthResponse, children, ...baseLoginRequest } = props;
|
|
7291
|
+
const medplum = useMedplum();
|
|
7292
|
+
const [outcome, setOutcome] = React.useState();
|
|
7293
|
+
const issues = getIssuesForExpression(outcome, undefined);
|
|
7294
|
+
const handleSubmit = React.useCallback((formData) => {
|
|
7295
|
+
medplum
|
|
7296
|
+
.startLogin({
|
|
7297
|
+
...baseLoginRequest,
|
|
7298
|
+
password: formData.password,
|
|
7299
|
+
remember: formData.remember === 'on',
|
|
7300
|
+
})
|
|
7301
|
+
.then(handleAuthResponse)
|
|
7302
|
+
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
7303
|
+
}, [medplum, baseLoginRequest, handleAuthResponse]);
|
|
7304
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
7305
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
7306
|
+
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
7307
|
+
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
7308
|
+
React.createElement(core$1.PasswordInput, { name: "password", label: "Password", autoComplete: "off", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'password') })),
|
|
7309
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
7310
|
+
onForgotPassword && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onForgotPassword, size: "xs" }, "Forgot password")),
|
|
7311
|
+
React.createElement(core$1.Checkbox, { id: "remember", name: "remember", label: "Remember me", size: "xs", sx: { lineHeight: 1 } }),
|
|
7312
|
+
React.createElement(core$1.Button, { type: "submit" }, "Sign in"))));
|
|
7313
|
+
}
|
|
7314
|
+
|
|
7315
|
+
function ChooseProfileForm(props) {
|
|
7316
|
+
const medplum = useMedplum();
|
|
7317
|
+
const [outcome, setOutcome] = React.useState();
|
|
7318
|
+
return (React.createElement(core$1.Stack, null,
|
|
7319
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7320
|
+
React.createElement(Logo, { size: 32 }),
|
|
7321
|
+
React.createElement(core$1.Title, { order: 3 }, "Choose profile")),
|
|
7322
|
+
React.createElement(OperationOutcomeAlert, { outcome: outcome }),
|
|
7323
|
+
props.memberships.map((membership) => (React.createElement(core$1.UnstyledButton, { key: membership.id, onClick: () => {
|
|
7324
|
+
medplum
|
|
7325
|
+
.post('auth/profile', {
|
|
7326
|
+
login: props.login,
|
|
7327
|
+
profile: membership.id,
|
|
7328
|
+
})
|
|
7329
|
+
.then(props.handleAuthResponse)
|
|
7330
|
+
.catch((err) => setOutcome(core.normalizeOperationOutcome(err)));
|
|
7331
|
+
} },
|
|
7332
|
+
React.createElement(core$1.Group, null,
|
|
7333
|
+
React.createElement(core$1.Avatar, { radius: "xl" }),
|
|
7334
|
+
React.createElement("div", { style: { flex: 1 } },
|
|
7335
|
+
React.createElement(core$1.Text, { size: "sm", weight: 500 }, membership.profile?.display),
|
|
7336
|
+
React.createElement(core$1.Text, { color: "dimmed", size: "xs" }, membership.project?.display))))))));
|
|
7337
|
+
}
|
|
7338
|
+
|
|
7339
|
+
function ChooseScopeForm(props) {
|
|
7340
|
+
const medplum = useMedplum();
|
|
7341
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
|
|
7342
|
+
medplum
|
|
7343
|
+
.post('auth/scope', {
|
|
7344
|
+
login: props.login,
|
|
7345
|
+
scope: Object.keys(formData).join(' '),
|
|
7346
|
+
})
|
|
7347
|
+
.then(props.handleAuthResponse)
|
|
7348
|
+
.catch(console.log);
|
|
7349
|
+
} },
|
|
7350
|
+
React.createElement(core$1.Stack, null,
|
|
7351
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7352
|
+
React.createElement(Logo, { size: 32 }),
|
|
7353
|
+
React.createElement(core$1.Title, null, "Choose scope")),
|
|
7354
|
+
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 })))),
|
|
7355
|
+
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
7356
|
+
React.createElement(core$1.Button, { type: "submit" }, "Set scope")))));
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7359
|
+
function MfaForm(props) {
|
|
7360
|
+
const medplum = useMedplum();
|
|
7361
|
+
const [errorMessage, setErrorMessage] = React.useState(undefined);
|
|
7362
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
|
|
7363
|
+
setErrorMessage(undefined);
|
|
7364
|
+
medplum
|
|
7365
|
+
.post('auth/mfa/verify', {
|
|
7366
|
+
login: props.login,
|
|
7367
|
+
token: formData.token,
|
|
7368
|
+
})
|
|
7369
|
+
.then(props.handleAuthResponse)
|
|
7370
|
+
.catch((err) => setErrorMessage(core.normalizeErrorString(err)));
|
|
7371
|
+
} },
|
|
7372
|
+
React.createElement(core$1.Stack, null,
|
|
7373
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } },
|
|
7374
|
+
React.createElement(Logo, { size: 32 }),
|
|
7375
|
+
React.createElement(core$1.Title, null, "Enter MFA code")),
|
|
7376
|
+
errorMessage && (React.createElement(core$1.Alert, { icon: React.createElement(IconAlertCircle, { size: 16 }), title: "Error", color: "red" }, errorMessage)),
|
|
7377
|
+
React.createElement(core$1.Stack, null,
|
|
7378
|
+
React.createElement(core$1.TextInput, { name: "token", label: "MFA code", required: true })),
|
|
7379
|
+
React.createElement(core$1.Group, { position: "right", mt: "xl" },
|
|
7380
|
+
React.createElement(core$1.Button, { type: "submit" }, "Submit code")))));
|
|
7381
|
+
}
|
|
7382
|
+
|
|
7383
|
+
/**
|
|
7384
|
+
* The SignInForm component allows users to sign in to Medplum.
|
|
7385
|
+
*
|
|
7386
|
+
* "Signing in" is a multi-step process:
|
|
7387
|
+
* 1) Authentication - identify the user
|
|
7388
|
+
* 2) MFA - If MFA is enabled, prompt for MFA code
|
|
7389
|
+
* 3) Choose profile - If the user has multiple profiles, prompt to choose one
|
|
7390
|
+
* 4) Choose scope - If the user has multiple scopes, prompt to choose one
|
|
7391
|
+
* 5) Success - Return to the caller with either a code or a redirect
|
|
7392
|
+
*/
|
|
7393
|
+
function SignInForm(props) {
|
|
7394
|
+
const { chooseScopes, onSuccess, onForgotPassword, onRegister, onCode, ...baseLoginRequest } = props;
|
|
7395
|
+
const medplum = useMedplum();
|
|
7396
|
+
const [login, setLogin] = React.useState(undefined);
|
|
7397
|
+
const [mfaRequired, setAuthenticatorRequired] = React.useState(false);
|
|
7398
|
+
const [memberships, setMemberships] = React.useState(undefined);
|
|
7399
|
+
const handleCode = React.useCallback((code) => {
|
|
7400
|
+
if (onCode) {
|
|
7401
|
+
onCode(code);
|
|
7402
|
+
}
|
|
7403
|
+
else {
|
|
7404
|
+
medplum
|
|
7405
|
+
.processCode(code)
|
|
7406
|
+
.then(() => {
|
|
7407
|
+
if (onSuccess) {
|
|
7408
|
+
onSuccess();
|
|
7409
|
+
}
|
|
7410
|
+
})
|
|
7411
|
+
.catch(console.log);
|
|
7412
|
+
}
|
|
7413
|
+
}, [medplum, onCode, onSuccess]);
|
|
7414
|
+
const handleAuthResponse = React.useCallback((response) => {
|
|
7415
|
+
setAuthenticatorRequired(!!response.mfaRequired);
|
|
7416
|
+
if (response.login) {
|
|
7417
|
+
setLogin(response.login);
|
|
7418
|
+
}
|
|
7419
|
+
if (response.memberships) {
|
|
7420
|
+
setMemberships(response.memberships);
|
|
7421
|
+
}
|
|
7422
|
+
if (response.code) {
|
|
7423
|
+
if (chooseScopes) {
|
|
7424
|
+
setMemberships(undefined);
|
|
7425
|
+
}
|
|
7426
|
+
else {
|
|
7427
|
+
handleCode(response.code);
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
}, [chooseScopes, handleCode]);
|
|
7431
|
+
const handleScopeResponse = React.useCallback((response) => {
|
|
7432
|
+
handleCode(response.code);
|
|
7433
|
+
}, [handleCode]);
|
|
7434
|
+
React.useEffect(() => {
|
|
7435
|
+
if (props.login) {
|
|
7436
|
+
medplum
|
|
7437
|
+
.get('auth/login/' + props.login)
|
|
7438
|
+
.then(handleAuthResponse)
|
|
7439
|
+
.catch(console.error);
|
|
7440
|
+
}
|
|
7441
|
+
}, [medplum, props, handleAuthResponse]);
|
|
7442
|
+
return (React.createElement(Document, { width: 450 }, (() => {
|
|
7443
|
+
if (!login) {
|
|
7444
|
+
return (React.createElement(AuthenticationForm, { onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, disableGoogleAuth: props.disableGoogleAuth, ...baseLoginRequest }, props.children));
|
|
7445
|
+
}
|
|
7446
|
+
else if (mfaRequired) {
|
|
7447
|
+
return React.createElement(MfaForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
7448
|
+
}
|
|
7449
|
+
else if (memberships) {
|
|
7450
|
+
return React.createElement(ChooseProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
|
|
7451
|
+
}
|
|
7452
|
+
else if (props.projectId === 'new') {
|
|
7453
|
+
return React.createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse });
|
|
7454
|
+
}
|
|
7455
|
+
else if (props.chooseScopes) {
|
|
7456
|
+
return React.createElement(ChooseScopeForm, { login: login, scope: props.scope, handleAuthResponse: handleScopeResponse });
|
|
7457
|
+
}
|
|
7458
|
+
else {
|
|
7459
|
+
return React.createElement("div", null, "Success");
|
|
7460
|
+
}
|
|
7461
|
+
})()));
|
|
7462
|
+
}
|
|
7463
|
+
|
|
6921
7464
|
exports.AddressDisplay = AddressDisplay;
|
|
6922
7465
|
exports.AddressInput = AddressInput;
|
|
6923
7466
|
exports.AnnotationInput = AnnotationInput;
|
|
7467
|
+
exports.AppShell = AppShell;
|
|
6924
7468
|
exports.AsyncAutocomplete = AsyncAutocomplete;
|
|
6925
7469
|
exports.AttachmentArrayDisplay = AttachmentArrayDisplay;
|
|
6926
7470
|
exports.AttachmentArrayInput = AttachmentArrayInput;
|
|
@@ -6954,10 +7498,12 @@
|
|
|
6954
7498
|
exports.FhirPathTable = FhirPathTable;
|
|
6955
7499
|
exports.Form = Form;
|
|
6956
7500
|
exports.FormSection = FormSection;
|
|
7501
|
+
exports.Header = Header;
|
|
6957
7502
|
exports.HumanNameDisplay = HumanNameDisplay;
|
|
6958
7503
|
exports.HumanNameInput = HumanNameInput;
|
|
6959
7504
|
exports.IdentifierDisplay = IdentifierDisplay;
|
|
6960
7505
|
exports.IdentifierInput = IdentifierInput;
|
|
7506
|
+
exports.Loading = Loading;
|
|
6961
7507
|
exports.Logo = Logo;
|
|
6962
7508
|
exports.MedplumLink = MedplumLink;
|
|
6963
7509
|
exports.MedplumProvider = MedplumProvider;
|
|
@@ -6965,6 +7511,7 @@
|
|
|
6965
7511
|
exports.MemoizedSearchControl = MemoizedSearchControl;
|
|
6966
7512
|
exports.MoneyDisplay = MoneyDisplay;
|
|
6967
7513
|
exports.MoneyInput = MoneyInput;
|
|
7514
|
+
exports.Navbar = Navbar;
|
|
6968
7515
|
exports.ObservationTable = ObservationTable;
|
|
6969
7516
|
exports.OperationOutcomeAlert = OperationOutcomeAlert;
|
|
6970
7517
|
exports.Panel = Panel;
|