@medplum/react 2.0.18 → 2.0.20
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 +200 -105
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/esm/AppShell/AppShell.mjs +1 -1
- package/dist/esm/AppShell/AppShell.mjs.map +1 -1
- package/dist/esm/AppShell/HeaderSearchInput.mjs +2 -4
- package/dist/esm/AppShell/HeaderSearchInput.mjs.map +1 -1
- package/dist/esm/AppShell/Navbar.mjs +83 -34
- package/dist/esm/AppShell/Navbar.mjs.map +1 -1
- package/dist/esm/BookmarkDialog/BookmarkDialog.mjs +1 -3
- package/dist/esm/BookmarkDialog/BookmarkDialog.mjs.map +1 -1
- package/dist/esm/GoogleButton/GoogleButton.mjs +2 -2
- package/dist/esm/GoogleButton/GoogleButton.mjs.map +1 -1
- package/dist/esm/SearchControl/SearchControl.mjs +2 -2
- package/dist/esm/SearchControl/SearchControl.mjs.map +1 -1
- package/dist/esm/SearchFilterValueDisplay/SearchFilterValueDisplay.mjs +3 -2
- package/dist/esm/SearchFilterValueDisplay/SearchFilterValueDisplay.mjs.map +1 -1
- package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs +3 -0
- package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +4 -2
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs +2 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs +2 -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 +2 -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 +2 -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 +2 -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 +2 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs.map +1 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs +2 -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 +2 -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 +2 -1
- package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs.map +1 -1
- package/dist/types/AddressDisplay/AddressDisplay.d.ts +1 -0
- package/dist/types/AddressInput/AddressInput.d.ts +1 -0
- package/dist/types/AnnotationInput/AnnotationInput.d.ts +1 -0
- package/dist/types/AppShell/AppShell.d.ts +2 -0
- package/dist/types/AppShell/HeaderSearchInput.d.ts +5 -1
- package/dist/types/AppShell/Navbar.d.ts +3 -0
- package/dist/types/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
- package/dist/types/AttachmentArrayDisplay/AttachmentArrayDisplay.d.ts +1 -0
- package/dist/types/AttachmentArrayInput/AttachmentArrayInput.d.ts +1 -0
- package/dist/types/AttachmentDisplay/AttachmentDisplay.d.ts +1 -0
- package/dist/types/AttachmentInput/AttachmentInput.d.ts +1 -0
- package/dist/types/BackboneElementDisplay/BackboneElementDisplay.d.ts +1 -0
- package/dist/types/BackboneElementInput/BackboneElementInput.d.ts +1 -0
- package/dist/types/BookmarkDialog/BookmarkDialog.d.ts +3 -0
- package/dist/types/CalendarInput/CalendarInput.d.ts +1 -0
- package/dist/types/CodeInput/CodeInput.d.ts +1 -0
- package/dist/types/CodeableConceptDisplay/CodeableConceptDisplay.d.ts +1 -0
- package/dist/types/CodeableConceptInput/CodeableConceptInput.d.ts +1 -0
- package/dist/types/CodingDisplay/CodingDisplay.d.ts +1 -0
- package/dist/types/CodingInput/CodingInput.d.ts +1 -0
- package/dist/types/ContactDetailDisplay/ContactDetailDisplay.d.ts +1 -0
- package/dist/types/ContactDetailInput/ContactDetailInput.d.ts +1 -0
- package/dist/types/ContactPointDisplay/ContactPointDisplay.d.ts +1 -0
- package/dist/types/ContactPointInput/ContactPointInput.d.ts +1 -0
- package/dist/types/Container/Container.d.ts +1 -0
- package/dist/types/DateTimeInput/DateTimeInput.d.ts +1 -0
- package/dist/types/DefaultResourceTimeline/DefaultResourceTimeline.d.ts +1 -0
- package/dist/types/DiagnosticReportDisplay/DiagnosticReportDisplay.d.ts +1 -0
- package/dist/types/Document/Document.d.ts +1 -0
- package/dist/types/EncounterTimeline/EncounterTimeline.d.ts +1 -0
- package/dist/types/ExtensionInput/ExtensionInput.d.ts +1 -0
- package/dist/types/FhirPathDisplay/FhirPathDisplay.d.ts +1 -0
- package/dist/types/GoogleButton/GoogleButton.d.ts +1 -0
- package/dist/types/HumanNameDisplay/HumanNameDisplay.d.ts +1 -0
- package/dist/types/HumanNameInput/HumanNameInput.d.ts +1 -0
- package/dist/types/IdentifierDisplay/IdentifierDisplay.d.ts +1 -0
- package/dist/types/IdentifierInput/IdentifierInput.d.ts +1 -0
- package/dist/types/Loading/Loading.d.ts +1 -0
- package/dist/types/Logo/Logo.d.ts +1 -0
- package/dist/types/MoneyDisplay/MoneyDisplay.d.ts +1 -0
- package/dist/types/MoneyInput/MoneyInput.d.ts +1 -0
- package/dist/types/NoteDisplay/NoteDisplay.d.ts +1 -0
- package/dist/types/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +1 -0
- package/dist/types/Panel/Panel.d.ts +1 -0
- package/dist/types/PatientTimeline/PatientTimeline.d.ts +1 -0
- package/dist/types/PeriodInput/PeriodInput.d.ts +1 -0
- package/dist/types/PlanDefinitionBuilder/PlanDefinitionBuilder.d.ts +1 -0
- package/dist/types/QuantityDisplay/QuantityDisplay.d.ts +1 -0
- package/dist/types/QuantityInput/QuantityInput.d.ts +1 -0
- package/dist/types/QuestionnaireBuilder/QuestionnaireBuilder.d.ts +1 -0
- package/dist/types/QuestionnaireForm/QuestionnaireForm.d.ts +1 -0
- package/dist/types/RangeDisplay/RangeDisplay.d.ts +1 -0
- package/dist/types/RangeInput/RangeInput.d.ts +1 -0
- package/dist/types/RatioDisplay/RatioDisplay.d.ts +1 -0
- package/dist/types/RatioInput/RatioInput.d.ts +1 -0
- package/dist/types/ReferenceDisplay/ReferenceDisplay.d.ts +1 -0
- package/dist/types/ReferenceInput/ReferenceInput.d.ts +1 -0
- package/dist/types/ReferenceRangeEditor/ReferenceRangeEditor.d.ts +1 -0
- package/dist/types/RequestGroupDisplay/RequestGroupDisplay.d.ts +1 -0
- package/dist/types/ResourceArrayDisplay/ResourceArrayDisplay.d.ts +1 -0
- package/dist/types/ResourceArrayInput/ResourceArrayInput.d.ts +1 -0
- package/dist/types/ResourceAvatar/ResourceAvatar.d.ts +1 -0
- package/dist/types/ResourceBadge/ResourceBadge.d.ts +1 -0
- package/dist/types/ResourceBlame/ResourceBlame.d.ts +1 -0
- package/dist/types/ResourceDiff/ResourceDiff.d.ts +1 -0
- package/dist/types/ResourceDiffTable/ResourceDiffTable.d.ts +1 -0
- package/dist/types/ResourceForm/ResourceForm.d.ts +1 -0
- package/dist/types/ResourceHistoryTable/ResourceHistoryTable.d.ts +1 -0
- package/dist/types/ResourceInput/ResourceInput.d.ts +1 -0
- package/dist/types/ResourceName/ResourceName.d.ts +1 -0
- package/dist/types/ResourcePropertyDisplay/ResourcePropertyDisplay.d.ts +1 -0
- package/dist/types/ResourcePropertyInput/ResourcePropertyInput.d.ts +1 -0
- package/dist/types/ResourceTable/ResourceTable.d.ts +1 -0
- package/dist/types/ResourceTimeline/ResourceTimeline.d.ts +1 -0
- package/dist/types/Scheduler/Scheduler.d.ts +1 -0
- package/dist/types/SearchControl/SearchUtils.d.ts +1 -0
- package/dist/types/SearchExportDialog/SearchExportDialog.d.ts +1 -0
- package/dist/types/SearchFieldEditor/SearchFieldEditor.d.ts +1 -0
- package/dist/types/SearchFilterEditor/SearchFilterEditor.d.ts +1 -0
- package/dist/types/SearchFilterValueDialog/SearchFilterValueDialog.d.ts +1 -0
- package/dist/types/SearchFilterValueDisplay/SearchFilterValueDisplay.d.ts +1 -0
- package/dist/types/SearchFilterValueInput/SearchFilterValueInput.d.ts +1 -0
- package/dist/types/SearchPopupMenu/SearchPopupMenu.d.ts +1 -0
- package/dist/types/ServiceRequestTimeline/ServiceRequestTimeline.d.ts +1 -0
- package/dist/types/StatusBadge/StatusBadge.d.ts +1 -0
- package/dist/types/TimingInput/TimingInput.d.ts +1 -0
- package/dist/types/ValueSetAutocomplete/ValueSetAutocomplete.d.ts +1 -0
- package/dist/types/auth/ChooseProfileForm.d.ts +1 -0
- package/dist/types/auth/ChooseScopeForm.d.ts +1 -0
- package/dist/types/auth/MfaForm.d.ts +1 -0
- package/dist/types/auth/NewProjectForm.d.ts +1 -0
- package/package.json +15 -16
|
@@ -28,7 +28,7 @@ function AppShell(props) {
|
|
|
28
28
|
main: {
|
|
29
29
|
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],
|
|
30
30
|
},
|
|
31
|
-
}, 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 },
|
|
31
|
+
}, padding: 0, fixed: true, header: profile && React.createElement(Header, { logo: props.logo, version: props.version, navbarToggle: toggleNavbar }), navbar: profile && navbarOpen ? (React.createElement(Navbar, { pathname: props.pathname, searchParams: props.searchParams, menus: props.menus, closeNavbar: closeNavbar, displayAddBookmark: props.displayAddBookmark })) : undefined },
|
|
32
32
|
React.createElement(ErrorBoundary, null,
|
|
33
33
|
React.createElement(Suspense, { fallback: React.createElement(Loading, null) }, props.children))));
|
|
34
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShell.mjs","sources":["../../../src/AppShell/AppShell.tsx"],"sourcesContent":["import { AppShell as MantineAppShell, useMantineTheme } from '@mantine/core';\nimport React, { Suspense, useState } from 'react';\nimport { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary';\nimport { Loading } from '../Loading/Loading';\nimport { useMedplum, useMedplumProfile } from '../MedplumProvider/MedplumProvider';\nimport { Header } from './Header';\nimport { Navbar, NavbarMenu } from './Navbar';\n\nexport interface AppShellProps {\n logo: React.ReactNode;\n version?: string;\n menus?: NavbarMenu[];\n children: React.ReactNode;\n displayAddBookmark?: boolean;\n}\n\nexport function AppShell(props: AppShellProps): JSX.Element {\n const theme = useMantineTheme();\n const [navbarOpen, setNavbarOpen] = useState(localStorage['navbarOpen'] === 'true');\n const medplum = useMedplum();\n const profile = useMedplumProfile();\n\n function setNavbarOpenWrapper(open: boolean): void {\n localStorage['navbarOpen'] = open.toString();\n setNavbarOpen(open);\n }\n\n function closeNavbar(): void {\n setNavbarOpenWrapper(false);\n }\n\n function toggleNavbar(): void {\n setNavbarOpenWrapper(!navbarOpen);\n }\n\n if (medplum.isLoading()) {\n return <Loading />;\n }\n\n return (\n <MantineAppShell\n styles={{\n main: {\n background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],\n },\n }}\n padding={0}\n fixed={true}\n header={profile && <Header logo={props.logo} version={props.version} navbarToggle={toggleNavbar} />}\n navbar={\n profile && navbarOpen ? (\n <Navbar
|
|
1
|
+
{"version":3,"file":"AppShell.mjs","sources":["../../../src/AppShell/AppShell.tsx"],"sourcesContent":["import { AppShell as MantineAppShell, useMantineTheme } from '@mantine/core';\nimport React, { Suspense, useState } from 'react';\nimport { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary';\nimport { Loading } from '../Loading/Loading';\nimport { useMedplum, useMedplumProfile } from '../MedplumProvider/MedplumProvider';\nimport { Header } from './Header';\nimport { Navbar, NavbarMenu } from './Navbar';\n\nexport interface AppShellProps {\n logo: React.ReactNode;\n pathname?: string;\n searchParams?: URLSearchParams;\n version?: string;\n menus?: NavbarMenu[];\n children: React.ReactNode;\n displayAddBookmark?: boolean;\n}\n\nexport function AppShell(props: AppShellProps): JSX.Element {\n const theme = useMantineTheme();\n const [navbarOpen, setNavbarOpen] = useState(localStorage['navbarOpen'] === 'true');\n const medplum = useMedplum();\n const profile = useMedplumProfile();\n\n function setNavbarOpenWrapper(open: boolean): void {\n localStorage['navbarOpen'] = open.toString();\n setNavbarOpen(open);\n }\n\n function closeNavbar(): void {\n setNavbarOpenWrapper(false);\n }\n\n function toggleNavbar(): void {\n setNavbarOpenWrapper(!navbarOpen);\n }\n\n if (medplum.isLoading()) {\n return <Loading />;\n }\n\n return (\n <MantineAppShell\n styles={{\n main: {\n background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],\n },\n }}\n padding={0}\n fixed={true}\n header={profile && <Header logo={props.logo} version={props.version} navbarToggle={toggleNavbar} />}\n navbar={\n profile && navbarOpen ? (\n <Navbar\n pathname={props.pathname}\n searchParams={props.searchParams}\n menus={props.menus}\n closeNavbar={closeNavbar}\n displayAddBookmark={props.displayAddBookmark}\n />\n ) : undefined\n }\n >\n <ErrorBoundary>\n <Suspense fallback={<Loading />}>{props.children}</Suspense>\n </ErrorBoundary>\n </MantineAppShell>\n );\n}\n"],"names":["MantineAppShell"],"mappings":";;;;;;;;AAkBM,SAAU,QAAQ,CAAC,KAAoB,EAAA;AAC3C,IAAA,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;AAChC,IAAA,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC,CAAC;AACpF,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,IAAA,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IAEpC,SAAS,oBAAoB,CAAC,IAAa,EAAA;QACzC,YAAY,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7C,aAAa,CAAC,IAAI,CAAC,CAAC;KACrB;AAED,IAAA,SAAS,WAAW,GAAA;QAClB,oBAAoB,CAAC,KAAK,CAAC,CAAC;KAC7B;AAED,IAAA,SAAS,YAAY,GAAA;AACnB,QAAA,oBAAoB,CAAC,CAAC,UAAU,CAAC,CAAC;KACnC;AAED,IAAA,IAAI,OAAO,CAAC,SAAS,EAAE,EAAE;QACvB,OAAO,KAAA,CAAA,aAAA,CAAC,OAAO,EAAA,IAAA,CAAG,CAAC;AACpB,KAAA;AAED,IAAA,QACE,KAAA,CAAA,aAAA,CAACA,UAAe,EAAA,EACd,MAAM,EAAE;AACN,YAAA,IAAI,EAAE;gBACJ,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACvF,aAAA;AACF,SAAA,EACD,OAAO,EAAE,CAAC,EACV,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,OAAO,IAAI,KAAA,CAAA,aAAA,CAAC,MAAM,EAAA,EAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,GAAI,EACnG,MAAM,EACJ,OAAO,IAAI,UAAU,IACnB,KAAC,CAAA,aAAA,CAAA,MAAM,EACL,EAAA,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,YAAY,EAAE,KAAK,CAAC,YAAY,EAChC,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,WAAW,EAAE,WAAW,EACxB,kBAAkB,EAAE,KAAK,CAAC,kBAAkB,EAC5C,CAAA,IACA,SAAS,EAAA;AAGf,QAAA,KAAA,CAAA,aAAA,CAAC,aAAa,EAAA,IAAA;AACZ,YAAA,KAAA,CAAA,aAAA,CAAC,QAAQ,EAAA,EAAC,QAAQ,EAAE,oBAAC,OAAO,EAAA,IAAA,CAAG,EAAG,EAAA,KAAK,CAAC,QAAQ,CAAY,CAC9C,CACA,EAClB;AACJ;;;;"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createStyles, Group, Text } from '@mantine/core';
|
|
2
2
|
import { getDisplayString, getReferenceString, isUUID, formatHumanName } from '@medplum/core';
|
|
3
3
|
import React, { forwardRef, useCallback } from 'react';
|
|
4
|
-
import { useLocation } from 'react-router-dom';
|
|
5
4
|
import { AsyncAutocomplete } from '../AsyncAutocomplete/AsyncAutocomplete.mjs';
|
|
6
5
|
import { useMedplumNavigate, useMedplum } from '../MedplumProvider/MedplumProvider.mjs';
|
|
7
6
|
import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar.mjs';
|
|
@@ -38,11 +37,10 @@ function toOption(resource) {
|
|
|
38
37
|
resource,
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
|
-
function HeaderSearchInput() {
|
|
40
|
+
function HeaderSearchInput(props) {
|
|
42
41
|
const { classes } = useStyles();
|
|
43
42
|
const navigate = useMedplumNavigate();
|
|
44
43
|
const medplum = useMedplum();
|
|
45
|
-
const location = useLocation();
|
|
46
44
|
const loadData = useCallback(async (input, signal) => {
|
|
47
45
|
const query = buildGraphQLQuery(input);
|
|
48
46
|
const options = { signal };
|
|
@@ -54,7 +52,7 @@ function HeaderSearchInput() {
|
|
|
54
52
|
navigate(`/${getReferenceString(item[0])}`);
|
|
55
53
|
}
|
|
56
54
|
}, [navigate]);
|
|
57
|
-
return (React.createElement(AsyncAutocomplete, { key:
|
|
55
|
+
return (React.createElement(AsyncAutocomplete, { key: props.pathname, size: "sm", radius: "md", className: classes.searchInput, icon: React.createElement(IconSearch, { size: 16 }), placeholder: "Search", itemComponent: ItemComponent, toKey: toKey, toOption: toOption, onChange: handleSelect, loadOptions: loadData }));
|
|
58
56
|
}
|
|
59
57
|
const ItemComponent = forwardRef(({ resource, ...others }, ref) => {
|
|
60
58
|
let helpText = undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderSearchInput.mjs","sources":["../../../src/AppShell/HeaderSearchInput.tsx"],"sourcesContent":["import { createStyles, Group, Text } from '@mantine/core';\nimport { formatHumanName, getDisplayString, getReferenceString, isUUID } from '@medplum/core';\nimport { Patient, ServiceRequest } from '@medplum/fhirtypes';\nimport { IconSearch } from '@tabler/icons-react';\nimport React, { forwardRef, useCallback } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { AsyncAutocomplete, AsyncAutocompleteOption } from '../AsyncAutocomplete/AsyncAutocomplete';\nimport { useMedplum, useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\nimport { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar';\n\nexport type HeaderSearchTypes = Patient | ServiceRequest;\n\nconst useStyles = createStyles(() => {\n return {\n searchInput: {\n input: {\n width: 220,\n transition: 'width 0.2s',\n },\n 'input:focus': {\n width: 400,\n },\n '@media (max-width: 800px)': {\n input: {\n width: 150,\n },\n 'input:focus': {\n width: 150,\n },\n },\n },\n };\n});\n\ninterface SearchGraphQLResponse {\n readonly data: {\n readonly Patients1: Patient[] | undefined;\n readonly Patients2: Patient[] | undefined;\n readonly ServiceRequestList: ServiceRequest[] | undefined;\n };\n}\n\nfunction toKey(resource: HeaderSearchTypes): string {\n return resource.id as string;\n}\n\nfunction toOption(resource: HeaderSearchTypes): AsyncAutocompleteOption<HeaderSearchTypes> {\n return {\n value: resource.id as string,\n label: getDisplayString(resource),\n resource,\n };\n}\n\nexport function HeaderSearchInput(): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const medplum = useMedplum();\n const location = useLocation();\n\n const loadData = useCallback(\n async (input: string, signal: AbortSignal): Promise<HeaderSearchTypes[]> => {\n const query = buildGraphQLQuery(input);\n const options = { signal };\n const response = (await medplum.graphql(query, undefined, undefined, options)) as SearchGraphQLResponse;\n return getResourcesFromResponse(response, input);\n },\n [medplum]\n );\n\n const handleSelect = useCallback(\n (item: HeaderSearchTypes[]): void => {\n if (item.length > 0) {\n navigate(`/${getReferenceString(item[0])}`);\n }\n },\n [navigate]\n );\n\n return (\n <AsyncAutocomplete\n key={location.pathname}\n size=\"sm\"\n radius=\"md\"\n className={classes.searchInput}\n icon={<IconSearch size={16} />}\n placeholder=\"Search\"\n itemComponent={ItemComponent}\n toKey={toKey}\n toOption={toOption}\n onChange={handleSelect}\n loadOptions={loadData}\n />\n );\n}\n\nconst ItemComponent = forwardRef<HTMLDivElement, any>(\n ({ resource, ...others }: AsyncAutocompleteOption<HeaderSearchTypes>, ref) => {\n let helpText: string | undefined = undefined;\n\n if (resource.resourceType === 'Patient') {\n helpText = resource.birthDate;\n } else if (resource.resourceType === 'ServiceRequest') {\n helpText = resource.subject?.display;\n }\n\n return (\n <div ref={ref} {...others}>\n <Group noWrap>\n <ResourceAvatar value={resource} />\n <div>\n <Text>{getDisplayString(resource)}</Text>\n <Text size=\"xs\" color=\"dimmed\">\n {helpText}\n </Text>\n </div>\n </Group>\n </div>\n );\n }\n);\n\nfunction buildGraphQLQuery(input: string): string {\n const escaped = JSON.stringify(input);\n if (isUUID(input)) {\n return `{\n Patients1: PatientList(_id: ${escaped}, _count: 1) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n ServiceRequestList(_id: ${escaped}, _count: 1) {\n resourceType\n id\n identifier {\n system\n value\n }\n subject {\n display\n }\n }\n }`.replace(/\\s+/g, ' ');\n }\n return `{\n Patients1: PatientList(name: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n Patients2: PatientList(identifier: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n ServiceRequestList(identifier: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n subject {\n display\n }\n }\n }`.replace(/\\s+/g, ' ');\n}\n\n/**\n * Returns a de-duped and sorted list of resources from the search response.\n * The search request is actually 3+ separate searches, which can include duplicates.\n * This function combines the results, de-dupes, and sorts by relevance.\n * @param response The response from a search query.\n * @param query The user entered search query.\n * @returns The resources to display in the autocomplete.\n */\nfunction getResourcesFromResponse(response: SearchGraphQLResponse, query: string): HeaderSearchTypes[] {\n const resources = [];\n if (response.data.Patients1) {\n resources.push(...response.data.Patients1);\n }\n if (response.data.Patients2) {\n resources.push(...response.data.Patients2);\n }\n if (response.data.ServiceRequestList) {\n resources.push(...response.data.ServiceRequestList);\n }\n return sortByRelevance(dedupeResources(resources), query).slice(0, 5);\n}\n\n/**\n * Removes duplicate resources from an array by ID.\n * @param resources The array of resources with possible duplicates.\n * @returns The array of resources with no duplicates.\n */\nfunction dedupeResources(resources: HeaderSearchTypes[]): HeaderSearchTypes[] {\n const ids = new Set<string>();\n const result = [];\n\n for (const resource of resources) {\n if (!ids.has(resource.id as string)) {\n ids.add(resource.id as string);\n result.push(resource);\n }\n }\n\n return result;\n}\n\n/**\n * Sorts an array of resources by relevance.\n * @param resources The candidate resources.\n * @param query The user entered search string.\n * @returns The sorted array of resources.\n */\nfunction sortByRelevance(resources: HeaderSearchTypes[], query: string): HeaderSearchTypes[] {\n return resources.sort((a: HeaderSearchTypes, b: HeaderSearchTypes) => {\n return getResourceScore(b, query) - getResourceScore(a, query);\n });\n}\n\n/**\n * Calculates a relevance score of a candidate resource.\n * Higher scores are better.\n * @param resource The candidate resource.\n * @param query The user entered search string.\n * @returns The relevance score of the candidate resource.\n */\nfunction getResourceScore(resource: HeaderSearchTypes, query: string): number {\n let bestScore = 0;\n\n if (resource.identifier) {\n for (const identifier of resource.identifier) {\n bestScore = Math.max(bestScore, getStringScore(identifier.value, query));\n }\n }\n\n if (resource.resourceType === 'Patient' && resource.name) {\n for (const name of resource.name) {\n bestScore = Math.max(bestScore, getStringScore(formatHumanName(name), query));\n }\n }\n\n return bestScore;\n}\n\n/**\n * Calculates a relevance score of a candidate display string.\n * Higher scores are better.\n * @param str The candidate display string.\n * @param query The user entered search string.\n * @returns The relevance score of the candidate string.\n */\nfunction getStringScore(str: string | undefined, query: string): number {\n if (!str) {\n return 0;\n }\n const index = str.toLowerCase().indexOf(query.toLowerCase());\n if (index < 0) {\n return 0;\n }\n return 100 - index;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HeaderSearchInput.mjs","sources":["../../../src/AppShell/HeaderSearchInput.tsx"],"sourcesContent":["import { createStyles, Group, Text } from '@mantine/core';\nimport { formatHumanName, getDisplayString, getReferenceString, isUUID } from '@medplum/core';\nimport { Patient, ServiceRequest } from '@medplum/fhirtypes';\nimport { IconSearch } from '@tabler/icons-react';\nimport React, { forwardRef, useCallback } from 'react';\nimport { AsyncAutocomplete, AsyncAutocompleteOption } from '../AsyncAutocomplete/AsyncAutocomplete';\nimport { useMedplum, useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\nimport { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar';\n\nexport type HeaderSearchTypes = Patient | ServiceRequest;\n\nconst useStyles = createStyles(() => {\n return {\n searchInput: {\n input: {\n width: 220,\n transition: 'width 0.2s',\n },\n 'input:focus': {\n width: 400,\n },\n '@media (max-width: 800px)': {\n input: {\n width: 150,\n },\n 'input:focus': {\n width: 150,\n },\n },\n },\n };\n});\n\ninterface SearchGraphQLResponse {\n readonly data: {\n readonly Patients1: Patient[] | undefined;\n readonly Patients2: Patient[] | undefined;\n readonly ServiceRequestList: ServiceRequest[] | undefined;\n };\n}\n\nfunction toKey(resource: HeaderSearchTypes): string {\n return resource.id as string;\n}\n\nfunction toOption(resource: HeaderSearchTypes): AsyncAutocompleteOption<HeaderSearchTypes> {\n return {\n value: resource.id as string,\n label: getDisplayString(resource),\n resource,\n };\n}\n\nexport interface HeaderSearchInputProps {\n pathname?: string;\n}\n\nexport function HeaderSearchInput(props: HeaderSearchInputProps): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const medplum = useMedplum();\n\n const loadData = useCallback(\n async (input: string, signal: AbortSignal): Promise<HeaderSearchTypes[]> => {\n const query = buildGraphQLQuery(input);\n const options = { signal };\n const response = (await medplum.graphql(query, undefined, undefined, options)) as SearchGraphQLResponse;\n return getResourcesFromResponse(response, input);\n },\n [medplum]\n );\n\n const handleSelect = useCallback(\n (item: HeaderSearchTypes[]): void => {\n if (item.length > 0) {\n navigate(`/${getReferenceString(item[0])}`);\n }\n },\n [navigate]\n );\n\n return (\n <AsyncAutocomplete\n key={props.pathname}\n size=\"sm\"\n radius=\"md\"\n className={classes.searchInput}\n icon={<IconSearch size={16} />}\n placeholder=\"Search\"\n itemComponent={ItemComponent}\n toKey={toKey}\n toOption={toOption}\n onChange={handleSelect}\n loadOptions={loadData}\n />\n );\n}\n\nconst ItemComponent = forwardRef<HTMLDivElement, any>(\n ({ resource, ...others }: AsyncAutocompleteOption<HeaderSearchTypes>, ref) => {\n let helpText: string | undefined = undefined;\n\n if (resource.resourceType === 'Patient') {\n helpText = resource.birthDate;\n } else if (resource.resourceType === 'ServiceRequest') {\n helpText = resource.subject?.display;\n }\n\n return (\n <div ref={ref} {...others}>\n <Group noWrap>\n <ResourceAvatar value={resource} />\n <div>\n <Text>{getDisplayString(resource)}</Text>\n <Text size=\"xs\" color=\"dimmed\">\n {helpText}\n </Text>\n </div>\n </Group>\n </div>\n );\n }\n);\n\nfunction buildGraphQLQuery(input: string): string {\n const escaped = JSON.stringify(input);\n if (isUUID(input)) {\n return `{\n Patients1: PatientList(_id: ${escaped}, _count: 1) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n ServiceRequestList(_id: ${escaped}, _count: 1) {\n resourceType\n id\n identifier {\n system\n value\n }\n subject {\n display\n }\n }\n }`.replace(/\\s+/g, ' ');\n }\n return `{\n Patients1: PatientList(name: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n Patients2: PatientList(identifier: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n ServiceRequestList(identifier: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n subject {\n display\n }\n }\n }`.replace(/\\s+/g, ' ');\n}\n\n/**\n * Returns a de-duped and sorted list of resources from the search response.\n * The search request is actually 3+ separate searches, which can include duplicates.\n * This function combines the results, de-dupes, and sorts by relevance.\n * @param response The response from a search query.\n * @param query The user entered search query.\n * @returns The resources to display in the autocomplete.\n */\nfunction getResourcesFromResponse(response: SearchGraphQLResponse, query: string): HeaderSearchTypes[] {\n const resources = [];\n if (response.data.Patients1) {\n resources.push(...response.data.Patients1);\n }\n if (response.data.Patients2) {\n resources.push(...response.data.Patients2);\n }\n if (response.data.ServiceRequestList) {\n resources.push(...response.data.ServiceRequestList);\n }\n return sortByRelevance(dedupeResources(resources), query).slice(0, 5);\n}\n\n/**\n * Removes duplicate resources from an array by ID.\n * @param resources The array of resources with possible duplicates.\n * @returns The array of resources with no duplicates.\n */\nfunction dedupeResources(resources: HeaderSearchTypes[]): HeaderSearchTypes[] {\n const ids = new Set<string>();\n const result = [];\n\n for (const resource of resources) {\n if (!ids.has(resource.id as string)) {\n ids.add(resource.id as string);\n result.push(resource);\n }\n }\n\n return result;\n}\n\n/**\n * Sorts an array of resources by relevance.\n * @param resources The candidate resources.\n * @param query The user entered search string.\n * @returns The sorted array of resources.\n */\nfunction sortByRelevance(resources: HeaderSearchTypes[], query: string): HeaderSearchTypes[] {\n return resources.sort((a: HeaderSearchTypes, b: HeaderSearchTypes) => {\n return getResourceScore(b, query) - getResourceScore(a, query);\n });\n}\n\n/**\n * Calculates a relevance score of a candidate resource.\n * Higher scores are better.\n * @param resource The candidate resource.\n * @param query The user entered search string.\n * @returns The relevance score of the candidate resource.\n */\nfunction getResourceScore(resource: HeaderSearchTypes, query: string): number {\n let bestScore = 0;\n\n if (resource.identifier) {\n for (const identifier of resource.identifier) {\n bestScore = Math.max(bestScore, getStringScore(identifier.value, query));\n }\n }\n\n if (resource.resourceType === 'Patient' && resource.name) {\n for (const name of resource.name) {\n bestScore = Math.max(bestScore, getStringScore(formatHumanName(name), query));\n }\n }\n\n return bestScore;\n}\n\n/**\n * Calculates a relevance score of a candidate display string.\n * Higher scores are better.\n * @param str The candidate display string.\n * @param query The user entered search string.\n * @returns The relevance score of the candidate string.\n */\nfunction getStringScore(str: string | undefined, query: string): number {\n if (!str) {\n return 0;\n }\n const index = str.toLowerCase().indexOf(query.toLowerCase());\n if (index < 0) {\n return 0;\n }\n return 100 - index;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { createStyles, Navbar as Navbar$1, Text, Button, Space } from '@mantine/core';
|
|
1
|
+
import { createStyles, Navbar as Navbar$1, ScrollArea, Text, Button, Space } from '@mantine/core';
|
|
2
2
|
import React, { useState } from 'react';
|
|
3
|
-
import { useLocation, useSearchParams } from 'react-router-dom';
|
|
4
3
|
import { BookmarkDialog } from '../BookmarkDialog/BookmarkDialog.mjs';
|
|
5
4
|
import { CodeInput } from '../CodeInput/CodeInput.mjs';
|
|
6
5
|
import { MedplumLink } from '../MedplumLink/MedplumLink.mjs';
|
|
@@ -56,6 +55,7 @@ const useStyles = createStyles((theme) => {
|
|
|
56
55
|
function Navbar(props) {
|
|
57
56
|
const { classes } = useStyles();
|
|
58
57
|
const navigate = useMedplumNavigate();
|
|
58
|
+
const activeLink = getActiveLink(props.pathname, props.searchParams, props.menus);
|
|
59
59
|
const [bookmarkDialogVisible, setBookmarkDialogVisible] = useState(false);
|
|
60
60
|
function onLinkClick(e, to) {
|
|
61
61
|
e.stopPropagation();
|
|
@@ -72,48 +72,97 @@ function Navbar(props) {
|
|
|
72
72
|
}
|
|
73
73
|
return (React.createElement(React.Fragment, null,
|
|
74
74
|
React.createElement(Navbar$1, { width: { sm: 250 }, p: "xs" },
|
|
75
|
-
React.createElement(
|
|
76
|
-
React.createElement(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
React.createElement(
|
|
84
|
-
|
|
85
|
-
React.createElement(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
React.createElement(ScrollArea, null,
|
|
76
|
+
React.createElement(Navbar$1.Section, { mb: "sm" },
|
|
77
|
+
React.createElement(CodeInput, { key: window.location.pathname, name: "resourceType", placeholder: "Resource Type", property: {
|
|
78
|
+
binding: {
|
|
79
|
+
valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',
|
|
80
|
+
},
|
|
81
|
+
}, onChange: (newValue) => navigateResourceType(newValue), creatable: false, maxSelectedValues: 0, clearSearchOnChange: true, clearable: false })),
|
|
82
|
+
React.createElement(Navbar$1.Section, { grow: true },
|
|
83
|
+
props.menus?.map((menu) => (React.createElement(React.Fragment, { key: `menu-${menu.title}` },
|
|
84
|
+
React.createElement(Text, { className: classes.menuTitle }, menu.title),
|
|
85
|
+
menu.links?.map((link) => (React.createElement(NavbarLink, { key: link.href, to: link.href, active: link.href === activeLink?.href, onClick: (e) => onLinkClick(e, link.href) },
|
|
86
|
+
React.createElement(NavLinkIcon, { to: link.href, icon: link.icon }),
|
|
87
|
+
React.createElement("span", null, link.label))))))),
|
|
88
|
+
props.displayAddBookmark && (React.createElement(Button, { variant: "subtle", size: "xs", mt: "xl", leftIcon: React.createElement(IconPlus, { size: "0.75rem" }), onClick: () => setBookmarkDialogVisible(true) }, "Add Bookmark"))))),
|
|
89
|
+
props.pathname && props.searchParams && (React.createElement(BookmarkDialog, { pathname: props.pathname, searchParams: props.searchParams, visible: bookmarkDialogVisible, onOk: () => setBookmarkDialogVisible(false), onCancel: () => setBookmarkDialogVisible(false) }))));
|
|
89
90
|
}
|
|
90
91
|
function NavbarLink(props) {
|
|
91
92
|
const { classes, cx } = useStyles();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
return (React.createElement(MedplumLink, { onClick: props.onClick, to: props.to, className: cx(classes.link, { [classes.linkActive]: props.active }) }, props.children));
|
|
94
|
+
}
|
|
95
|
+
function NavLinkIcon(props) {
|
|
96
|
+
if (props.icon) {
|
|
97
|
+
return props.icon;
|
|
98
|
+
}
|
|
99
|
+
return React.createElement(Space, { w: 30 });
|
|
97
100
|
}
|
|
98
101
|
/**
|
|
99
|
-
* Returns
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
102
|
+
* Returns the best "active" link for the menu.
|
|
103
|
+
* In most cases, the navbar links are simple, and an exact match can determine which link is active.
|
|
104
|
+
* However, we ignore some search parameters to support pagination.
|
|
105
|
+
* But we cannot ignore all search parameters, to support separate links based on search filters.
|
|
106
|
+
* So in the end, we use a simple scoring system based on the number of matching query search params.
|
|
107
|
+
* @param currentPathname The web browser current pathname.
|
|
108
|
+
* @param currentSearchParams The web browser current search parameters.
|
|
109
|
+
* @param menus Collection of navbar menus and links.
|
|
110
|
+
* @returns The active link if one is found.
|
|
103
111
|
*/
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
function getActiveLink(currentPathname, currentSearchParams, menus) {
|
|
113
|
+
if (!currentPathname || !currentSearchParams || !menus) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
let bestLink = undefined;
|
|
117
|
+
let bestScore = 0;
|
|
118
|
+
for (const menu of menus) {
|
|
119
|
+
if (menu.links) {
|
|
120
|
+
for (const link of menu.links) {
|
|
121
|
+
const score = getLinkScore(currentPathname, currentSearchParams, link.href);
|
|
122
|
+
if (score > bestScore) {
|
|
123
|
+
bestScore = score;
|
|
124
|
+
bestLink = link;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
108
127
|
}
|
|
109
128
|
}
|
|
110
|
-
return
|
|
129
|
+
return bestLink;
|
|
111
130
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Calculates a score for a link.
|
|
133
|
+
* Zero means "does not match at all".
|
|
134
|
+
* One means "matches the pathname only".
|
|
135
|
+
* Additional increases for each matching search parameter.
|
|
136
|
+
* Ignores pagination parameters "_count" and "_offset".
|
|
137
|
+
* @param currentPathname The web browser current pathname.
|
|
138
|
+
* @param currentSearchParams The web browser current search parameters.
|
|
139
|
+
* @param linkHref A candidate link href.
|
|
140
|
+
* @returns The link score.
|
|
141
|
+
*/
|
|
142
|
+
function getLinkScore(currentPathname, currentSearchParams, linkHref) {
|
|
143
|
+
const linkUrl = new URL(linkHref, 'https://example.com');
|
|
144
|
+
if (currentPathname !== linkUrl.pathname) {
|
|
145
|
+
return 0;
|
|
115
146
|
}
|
|
116
|
-
|
|
147
|
+
const ignoredParams = ['_count', '_offset'];
|
|
148
|
+
for (const [key, value] of linkUrl.searchParams.entries()) {
|
|
149
|
+
if (ignoredParams.includes(key)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (currentSearchParams.get(key) !== value) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
let count = 1;
|
|
157
|
+
for (const [key, value] of currentSearchParams.entries()) {
|
|
158
|
+
if (ignoredParams.includes(key)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (linkUrl.searchParams.get(key) === value) {
|
|
162
|
+
count++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return count;
|
|
117
166
|
}
|
|
118
167
|
|
|
119
168
|
export { Navbar };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navbar.mjs","sources":["../../../src/AppShell/Navbar.tsx"],"sourcesContent":["import { Button, createStyles, Navbar as MantineNavbar, Space, Text } from '@mantine/core';\nimport { IconPlus } from '@tabler/icons-react';\nimport React, { useState } from 'react';\nimport { useLocation, useSearchParams } from 'react-router-dom';\nimport { BookmarkDialog } from '../BookmarkDialog/BookmarkDialog';\nimport { CodeInput } from '../CodeInput/CodeInput';\nimport { MedplumLink } from '../MedplumLink/MedplumLink';\nimport { useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\n\nconst useStyles = createStyles((theme) => {\n return {\n menuTitle: {\n margin: '20px 0 4px 6px',\n fontSize: '9px',\n fontWeight: 'normal',\n textTransform: 'uppercase',\n letterSpacing: '2px',\n },\n\n link: {\n ...theme.fn.focusStyles(),\n display: 'flex',\n alignItems: 'center',\n textDecoration: 'none',\n fontSize: theme.fontSizes.sm,\n color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],\n padding: `8px 12px`,\n borderRadius: theme.radius.sm,\n fontWeight: 500,\n\n '&:hover': {\n backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n textDecoration: 'none',\n\n [`& svg`]: {\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n },\n },\n\n '& svg': {\n color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],\n marginRight: theme.spacing.sm,\n strokeWidth: 1.5,\n width: 18,\n height: 18,\n },\n },\n\n linkActive: {\n '&, &:hover': {\n backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n [`& svg`]: {\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n },\n },\n },\n };\n});\n\nexport interface NavbarLink {\n icon?: JSX.Element;\n label?: string;\n href: string;\n}\n\nexport interface NavbarMenu {\n title?: string;\n links?: NavbarLink[];\n}\n\nexport interface NavbarProps {\n menus?: NavbarMenu[];\n closeNavbar: () => void;\n displayAddBookmark?: boolean;\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const [bookmarkDialogVisible, setBookmarkDialogVisible] = useState(false);\n\n function onLinkClick(e: React.SyntheticEvent, to: string): void {\n e.stopPropagation();\n e.preventDefault();\n navigate(to);\n if (window.innerWidth < 768) {\n props.closeNavbar();\n }\n }\n\n function navigateResourceType(resourceType: string | undefined): void {\n if (resourceType) {\n navigate(`/${resourceType}`);\n }\n }\n\n return (\n <>\n <MantineNavbar width={{ sm: 250 }} p=\"xs\">\n <MantineNavbar.Section mb=\"sm\">\n <CodeInput\n key={window.location.pathname}\n name=\"resourceType\"\n placeholder=\"Resource Type\"\n property={{\n binding: {\n valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',\n },\n }}\n onChange={(newValue) => navigateResourceType(newValue)}\n creatable={false}\n maxSelectedValues={0}\n clearSearchOnChange={true}\n clearable={false}\n />\n </MantineNavbar.Section>\n <MantineNavbar.Section grow>\n {props.menus?.map((menu) => (\n <React.Fragment key={`menu-${menu.title}`}>\n <Text className={classes.menuTitle}>{menu.title}</Text>\n {menu.links?.map((link) => (\n <NavbarLink key={link.href} to={link.href} onClick={(e) => onLinkClick(e, link.href)}>\n <NavLinkIcon to={link.href} icon={link.icon} />\n <span>{link.label}</span>\n </NavbarLink>\n ))}\n </React.Fragment>\n ))}\n {props.displayAddBookmark && (\n <Button\n variant=\"subtle\"\n size=\"xs\"\n mt=\"xl\"\n leftIcon={<IconPlus size=\"0.75rem\" />}\n onClick={() => setBookmarkDialogVisible(true)}\n >\n Add Bookmark\n </Button>\n )}\n </MantineNavbar.Section>\n </MantineNavbar>\n <BookmarkDialog\n visible={bookmarkDialogVisible}\n onOk={() => setBookmarkDialogVisible(false)}\n onCancel={() => setBookmarkDialogVisible(false)}\n />\n </>\n );\n}\ninterface NavbarLinkProps {\n to: string;\n onClick: React.MouseEventHandler;\n children: React.ReactNode;\n}\n\nfunction NavbarLink(props: NavbarLinkProps): JSX.Element {\n const { classes, cx } = useStyles();\n const location = useLocation();\n const [searchParams] = useSearchParams();\n const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);\n const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);\n\n return (\n <MedplumLink onClick={props.onClick} to={props.to} className={cx(classes.link, { [classes.linkActive]: isActive })}>\n {props.children}\n </MedplumLink>\n );\n}\n\n/**\n * Returns true if the search params match.\n * @param searchParams The current search params.\n * @param toUrl The destination URL of the link.\n * @returns True if the search params match.\n */\nfunction matchesParams(searchParams: URLSearchParams, toUrl: URL): boolean {\n for (const [key, value] of toUrl.searchParams.entries()) {\n if (searchParams.get(key) !== value) {\n return false;\n }\n }\n return true;\n}\n\ninterface NavLinkIconProps {\n to: string;\n icon?: JSX.Element;\n}\n\nfunction NavLinkIcon(props: NavLinkIconProps): JSX.Element {\n if (props.icon) {\n return props.icon;\n }\n return <Space w={30} />;\n}\n"],"names":["MantineNavbar"],"mappings":";;;;;;;;;AASA,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,KAAK,KAAI;IACvC,OAAO;AACL,QAAA,SAAS,EAAE;AACT,YAAA,MAAM,EAAE,gBAAgB;AACxB,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,UAAU,EAAE,QAAQ;AACpB,YAAA,aAAa,EAAE,WAAW;AAC1B,YAAA,aAAa,EAAE,KAAK;AACrB,SAAA;AAED,QAAA,IAAI,EAAE;AACJ,YAAA,GAAG,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE;AACzB,YAAA,OAAO,EAAE,MAAM;AACf,YAAA,UAAU,EAAE,QAAQ;AACpB,YAAA,cAAc,EAAE,MAAM;AACtB,YAAA,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;YAC5B,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACjF,YAAA,OAAO,EAAE,CAAU,QAAA,CAAA;AACnB,YAAA,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;AAC7B,YAAA,UAAU,EAAE,GAAG;AAEf,YAAA,SAAS,EAAE;gBACT,eAAe,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3F,gBAAA,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;AAC/D,gBAAA,cAAc,EAAE,MAAM;gBAEtB,CAAC,CAAA,KAAA,CAAO,GAAG;AACT,oBAAA,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;AAChE,iBAAA;AACF,aAAA;AAED,YAAA,OAAO,EAAE;gBACP,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACjF,gBAAA,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;AAC7B,gBAAA,WAAW,EAAE,GAAG;AAChB,gBAAA,KAAK,EAAE,EAAE;AACT,gBAAA,MAAM,EAAE,EAAE;AACX,aAAA;AACF,SAAA;AAED,QAAA,UAAU,EAAE;AACV,YAAA,YAAY,EAAE;gBACZ,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU;gBAC7F,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,KAAK;gBAC9E,CAAC,CAAA,KAAA,CAAO,GAAG;oBACT,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,KAAK;AAC/E,iBAAA;AACF,aAAA;AACF,SAAA;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAmBG,SAAU,MAAM,CAAC,KAAkB,EAAA;AACvC,IAAA,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,CAAC;AAChC,IAAA,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;AAE1E,IAAA,SAAS,WAAW,CAAC,CAAuB,EAAE,EAAU,EAAA;QACtD,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,CAAC,CAAC;AACb,QAAA,IAAI,MAAM,CAAC,UAAU,GAAG,GAAG,EAAE;YAC3B,KAAK,CAAC,WAAW,EAAE,CAAC;AACrB,SAAA;KACF;IAED,SAAS,oBAAoB,CAAC,YAAgC,EAAA;AAC5D,QAAA,IAAI,YAAY,EAAE;AAChB,YAAA,QAAQ,CAAC,CAAA,CAAA,EAAI,YAAY,CAAA,CAAE,CAAC,CAAC;AAC9B,SAAA;KACF;AAED,IAAA,QACE,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA;AACE,QAAA,KAAA,CAAA,aAAA,CAACA,QAAa,EAAA,EAAC,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAC,IAAI,EAAA;AACvC,YAAA,KAAA,CAAA,aAAA,CAACA,QAAa,CAAC,OAAO,EAAC,EAAA,EAAE,EAAC,IAAI,EAAA;AAC5B,gBAAA,KAAA,CAAA,aAAA,CAAC,SAAS,EACR,EAAA,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAC7B,IAAI,EAAC,cAAc,EACnB,WAAW,EAAC,eAAe,EAC3B,QAAQ,EAAE;AACR,wBAAA,OAAO,EAAE;AACP,4BAAA,QAAQ,EAAE,6CAA6C;AACxD,yBAAA;AACF,qBAAA,EACD,QAAQ,EAAE,CAAC,QAAQ,KAAK,oBAAoB,CAAC,QAAQ,CAAC,EACtD,SAAS,EAAE,KAAK,EAChB,iBAAiB,EAAE,CAAC,EACpB,mBAAmB,EAAE,IAAI,EACzB,SAAS,EAAE,KAAK,EAAA,CAChB,CACoB;AACxB,YAAA,KAAA,CAAA,aAAA,CAACA,QAAa,CAAC,OAAO,EAAA,EAAC,IAAI,EAAA,IAAA,EAAA;gBACxB,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,MACrB,oBAAC,KAAK,CAAC,QAAQ,EAAC,EAAA,GAAG,EAAE,CAAQ,KAAA,EAAA,IAAI,CAAC,KAAK,CAAE,CAAA,EAAA;oBACvC,KAAC,CAAA,aAAA,CAAA,IAAI,EAAC,EAAA,SAAS,EAAE,OAAO,CAAC,SAAS,EAAG,EAAA,IAAI,CAAC,KAAK,CAAQ;AACtD,oBAAA,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,MACpB,oBAAC,UAAU,EAAA,EAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAA;AAClF,wBAAA,KAAA,CAAA,aAAA,CAAC,WAAW,EAAA,EAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAI,CAAA;wBAC/C,KAAO,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,EAAA,IAAI,CAAC,KAAK,CAAQ,CACd,CACd,CAAC,CACa,CAClB,CAAC;AACD,gBAAA,KAAK,CAAC,kBAAkB,KACvB,KAAC,CAAA,aAAA,CAAA,MAAM,IACL,OAAO,EAAC,QAAQ,EAChB,IAAI,EAAC,IAAI,EACT,EAAE,EAAC,IAAI,EACP,QAAQ,EAAE,KAAA,CAAA,aAAA,CAAC,QAAQ,EAAC,EAAA,IAAI,EAAC,SAAS,EAAA,CAAG,EACrC,OAAO,EAAE,MAAM,wBAAwB,CAAC,IAAI,CAAC,EAGtC,EAAA,cAAA,CAAA,CACV,CACqB,CACV;AAChB,QAAA,KAAA,CAAA,aAAA,CAAC,cAAc,EAAA,EACb,OAAO,EAAE,qBAAqB,EAC9B,IAAI,EAAE,MAAM,wBAAwB,CAAC,KAAK,CAAC,EAC3C,QAAQ,EAAE,MAAM,wBAAwB,CAAC,KAAK,CAAC,EAAA,CAC/C,CACD,EACH;AACJ,CAAC;AAOD,SAAS,UAAU,CAAC,KAAsB,EAAA;IACxC,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,SAAS,EAAE,CAAC;AACpC,IAAA,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;AAC/B,IAAA,MAAM,CAAC,YAAY,CAAC,GAAG,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxF,IAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,IAAI,aAAa,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;AAE5F,IAAA,QACE,KAAC,CAAA,aAAA,CAAA,WAAW,IAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC,EAC/G,EAAA,KAAK,CAAC,QAAQ,CACH,EACd;AACJ,CAAC;AAED;;;;;AAKG;AACH,SAAS,aAAa,CAAC,YAA6B,EAAE,KAAU,EAAA;AAC9D,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE;QACvD,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE;AACnC,YAAA,OAAO,KAAK,CAAC;AACd,SAAA;AACF,KAAA;AACD,IAAA,OAAO,IAAI,CAAC;AACd,CAAC;AAOD,SAAS,WAAW,CAAC,KAAuB,EAAA;IAC1C,IAAI,KAAK,CAAC,IAAI,EAAE;QACd,OAAO,KAAK,CAAC,IAAI,CAAC;AACnB,KAAA;AACD,IAAA,OAAO,oBAAC,KAAK,EAAA,EAAC,CAAC,EAAE,EAAE,GAAI,CAAC;AAC1B;;;;"}
|
|
1
|
+
{"version":3,"file":"Navbar.mjs","sources":["../../../src/AppShell/Navbar.tsx"],"sourcesContent":["import { Button, createStyles, Navbar as MantineNavbar, ScrollArea, Space, Text } from '@mantine/core';\nimport { IconPlus } from '@tabler/icons-react';\nimport React, { useState } from 'react';\nimport { BookmarkDialog } from '../BookmarkDialog/BookmarkDialog';\nimport { CodeInput } from '../CodeInput/CodeInput';\nimport { MedplumLink } from '../MedplumLink/MedplumLink';\nimport { useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\n\nconst useStyles = createStyles((theme) => {\n return {\n menuTitle: {\n margin: '20px 0 4px 6px',\n fontSize: '9px',\n fontWeight: 'normal',\n textTransform: 'uppercase',\n letterSpacing: '2px',\n },\n\n link: {\n ...theme.fn.focusStyles(),\n display: 'flex',\n alignItems: 'center',\n textDecoration: 'none',\n fontSize: theme.fontSizes.sm,\n color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],\n padding: `8px 12px`,\n borderRadius: theme.radius.sm,\n fontWeight: 500,\n\n '&:hover': {\n backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n textDecoration: 'none',\n\n [`& svg`]: {\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n },\n },\n\n '& svg': {\n color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],\n marginRight: theme.spacing.sm,\n strokeWidth: 1.5,\n width: 18,\n height: 18,\n },\n },\n\n linkActive: {\n '&, &:hover': {\n backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n [`& svg`]: {\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n },\n },\n },\n };\n});\n\nexport interface NavbarLink {\n icon?: JSX.Element;\n label?: string;\n href: string;\n}\n\nexport interface NavbarMenu {\n title?: string;\n links?: NavbarLink[];\n}\n\nexport interface NavbarProps {\n pathname?: string;\n searchParams?: URLSearchParams;\n menus?: NavbarMenu[];\n closeNavbar: () => void;\n displayAddBookmark?: boolean;\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const activeLink = getActiveLink(props.pathname, props.searchParams, props.menus);\n const [bookmarkDialogVisible, setBookmarkDialogVisible] = useState(false);\n\n function onLinkClick(e: React.SyntheticEvent, to: string): void {\n e.stopPropagation();\n e.preventDefault();\n navigate(to);\n if (window.innerWidth < 768) {\n props.closeNavbar();\n }\n }\n\n function navigateResourceType(resourceType: string | undefined): void {\n if (resourceType) {\n navigate(`/${resourceType}`);\n }\n }\n\n return (\n <>\n <MantineNavbar width={{ sm: 250 }} p=\"xs\">\n <ScrollArea>\n <MantineNavbar.Section mb=\"sm\">\n <CodeInput\n key={window.location.pathname}\n name=\"resourceType\"\n placeholder=\"Resource Type\"\n property={{\n binding: {\n valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',\n },\n }}\n onChange={(newValue) => navigateResourceType(newValue)}\n creatable={false}\n maxSelectedValues={0}\n clearSearchOnChange={true}\n clearable={false}\n />\n </MantineNavbar.Section>\n <MantineNavbar.Section grow>\n {props.menus?.map((menu) => (\n <React.Fragment key={`menu-${menu.title}`}>\n <Text className={classes.menuTitle}>{menu.title}</Text>\n {menu.links?.map((link) => (\n <NavbarLink\n key={link.href}\n to={link.href}\n active={link.href === activeLink?.href}\n onClick={(e) => onLinkClick(e, link.href)}\n >\n <NavLinkIcon to={link.href} icon={link.icon} />\n <span>{link.label}</span>\n </NavbarLink>\n ))}\n </React.Fragment>\n ))}\n {props.displayAddBookmark && (\n <Button\n variant=\"subtle\"\n size=\"xs\"\n mt=\"xl\"\n leftIcon={<IconPlus size=\"0.75rem\" />}\n onClick={() => setBookmarkDialogVisible(true)}\n >\n Add Bookmark\n </Button>\n )}\n </MantineNavbar.Section>\n </ScrollArea>\n </MantineNavbar>\n {props.pathname && props.searchParams && (\n <BookmarkDialog\n pathname={props.pathname}\n searchParams={props.searchParams}\n visible={bookmarkDialogVisible}\n onOk={() => setBookmarkDialogVisible(false)}\n onCancel={() => setBookmarkDialogVisible(false)}\n />\n )}\n </>\n );\n}\n\ninterface NavbarLinkProps {\n to: string;\n active: boolean;\n onClick: React.MouseEventHandler;\n children: React.ReactNode;\n}\n\nfunction NavbarLink(props: NavbarLinkProps): JSX.Element {\n const { classes, cx } = useStyles();\n return (\n <MedplumLink\n onClick={props.onClick}\n to={props.to}\n className={cx(classes.link, { [classes.linkActive]: props.active })}\n >\n {props.children}\n </MedplumLink>\n );\n}\n\ninterface NavLinkIconProps {\n to: string;\n icon?: JSX.Element;\n}\n\nfunction NavLinkIcon(props: NavLinkIconProps): JSX.Element {\n if (props.icon) {\n return props.icon;\n }\n return <Space w={30} />;\n}\n\n/**\n * Returns the best \"active\" link for the menu.\n * In most cases, the navbar links are simple, and an exact match can determine which link is active.\n * However, we ignore some search parameters to support pagination.\n * But we cannot ignore all search parameters, to support separate links based on search filters.\n * So in the end, we use a simple scoring system based on the number of matching query search params.\n * @param currentPathname The web browser current pathname.\n * @param currentSearchParams The web browser current search parameters.\n * @param menus Collection of navbar menus and links.\n * @returns The active link if one is found.\n */\nfunction getActiveLink(\n currentPathname: string | undefined,\n currentSearchParams: URLSearchParams | undefined,\n menus: NavbarMenu[] | undefined\n): NavbarLink | undefined {\n if (!currentPathname || !currentSearchParams || !menus) {\n return undefined;\n }\n\n let bestLink = undefined;\n let bestScore = 0;\n\n for (const menu of menus) {\n if (menu.links) {\n for (const link of menu.links) {\n const score = getLinkScore(currentPathname, currentSearchParams, link.href);\n if (score > bestScore) {\n bestScore = score;\n bestLink = link;\n }\n }\n }\n }\n\n return bestLink;\n}\n\n/**\n * Calculates a score for a link.\n * Zero means \"does not match at all\".\n * One means \"matches the pathname only\".\n * Additional increases for each matching search parameter.\n * Ignores pagination parameters \"_count\" and \"_offset\".\n * @param currentPathname The web browser current pathname.\n * @param currentSearchParams The web browser current search parameters.\n * @param linkHref A candidate link href.\n * @returns The link score.\n */\nfunction getLinkScore(currentPathname: string, currentSearchParams: URLSearchParams, linkHref: string): number {\n const linkUrl = new URL(linkHref, 'https://example.com');\n if (currentPathname !== linkUrl.pathname) {\n return 0;\n }\n const ignoredParams = ['_count', '_offset'];\n for (const [key, value] of linkUrl.searchParams.entries()) {\n if (ignoredParams.includes(key)) {\n continue;\n }\n if (currentSearchParams.get(key) !== value) {\n return 0;\n }\n }\n let count = 1;\n for (const [key, value] of currentSearchParams.entries()) {\n if (ignoredParams.includes(key)) {\n continue;\n }\n if (linkUrl.searchParams.get(key) === value) {\n count++;\n }\n }\n return count;\n}\n"],"names":["MantineNavbar"],"mappings":";;;;;;;;AAQA,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,KAAK,KAAI;IACvC,OAAO;AACL,QAAA,SAAS,EAAE;AACT,YAAA,MAAM,EAAE,gBAAgB;AACxB,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,UAAU,EAAE,QAAQ;AACpB,YAAA,aAAa,EAAE,WAAW;AAC1B,YAAA,aAAa,EAAE,KAAK;AACrB,SAAA;AAED,QAAA,IAAI,EAAE;AACJ,YAAA,GAAG,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE;AACzB,YAAA,OAAO,EAAE,MAAM;AACf,YAAA,UAAU,EAAE,QAAQ;AACpB,YAAA,cAAc,EAAE,MAAM;AACtB,YAAA,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;YAC5B,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACjF,YAAA,OAAO,EAAE,CAAU,QAAA,CAAA;AACnB,YAAA,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;AAC7B,YAAA,UAAU,EAAE,GAAG;AAEf,YAAA,SAAS,EAAE;gBACT,eAAe,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3F,gBAAA,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;AAC/D,gBAAA,cAAc,EAAE,MAAM;gBAEtB,CAAC,CAAA,KAAA,CAAO,GAAG;AACT,oBAAA,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;AAChE,iBAAA;AACF,aAAA;AAED,YAAA,OAAO,EAAE;gBACP,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACjF,gBAAA,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;AAC7B,gBAAA,WAAW,EAAE,GAAG;AAChB,gBAAA,KAAK,EAAE,EAAE;AACT,gBAAA,MAAM,EAAE,EAAE;AACX,aAAA;AACF,SAAA;AAED,QAAA,UAAU,EAAE;AACV,YAAA,YAAY,EAAE;gBACZ,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU;gBAC7F,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,KAAK;gBAC9E,CAAC,CAAA,KAAA,CAAO,GAAG;oBACT,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,KAAK;AAC/E,iBAAA;AACF,aAAA;AACF,SAAA;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAqBG,SAAU,MAAM,CAAC,KAAkB,EAAA;AACvC,IAAA,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,CAAC;AAChC,IAAA,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;AACtC,IAAA,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAClF,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;AAE1E,IAAA,SAAS,WAAW,CAAC,CAAuB,EAAE,EAAU,EAAA;QACtD,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,CAAC,CAAC;AACb,QAAA,IAAI,MAAM,CAAC,UAAU,GAAG,GAAG,EAAE;YAC3B,KAAK,CAAC,WAAW,EAAE,CAAC;AACrB,SAAA;KACF;IAED,SAAS,oBAAoB,CAAC,YAAgC,EAAA;AAC5D,QAAA,IAAI,YAAY,EAAE;AAChB,YAAA,QAAQ,CAAC,CAAA,CAAA,EAAI,YAAY,CAAA,CAAE,CAAC,CAAC;AAC9B,SAAA;KACF;AAED,IAAA,QACE,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA;AACE,QAAA,KAAA,CAAA,aAAA,CAACA,QAAa,EAAA,EAAC,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAC,IAAI,EAAA;AACvC,YAAA,KAAA,CAAA,aAAA,CAAC,UAAU,EAAA,IAAA;AACT,gBAAA,KAAA,CAAA,aAAA,CAACA,QAAa,CAAC,OAAO,EAAC,EAAA,EAAE,EAAC,IAAI,EAAA;AAC5B,oBAAA,KAAA,CAAA,aAAA,CAAC,SAAS,EACR,EAAA,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAC7B,IAAI,EAAC,cAAc,EACnB,WAAW,EAAC,eAAe,EAC3B,QAAQ,EAAE;AACR,4BAAA,OAAO,EAAE;AACP,gCAAA,QAAQ,EAAE,6CAA6C;AACxD,6BAAA;AACF,yBAAA,EACD,QAAQ,EAAE,CAAC,QAAQ,KAAK,oBAAoB,CAAC,QAAQ,CAAC,EACtD,SAAS,EAAE,KAAK,EAChB,iBAAiB,EAAE,CAAC,EACpB,mBAAmB,EAAE,IAAI,EACzB,SAAS,EAAE,KAAK,EAAA,CAChB,CACoB;AACxB,gBAAA,KAAA,CAAA,aAAA,CAACA,QAAa,CAAC,OAAO,EAAA,EAAC,IAAI,EAAA,IAAA,EAAA;oBACxB,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,MACrB,oBAAC,KAAK,CAAC,QAAQ,EAAC,EAAA,GAAG,EAAE,CAAQ,KAAA,EAAA,IAAI,CAAC,KAAK,CAAE,CAAA,EAAA;wBACvC,KAAC,CAAA,aAAA,CAAA,IAAI,EAAC,EAAA,SAAS,EAAE,OAAO,CAAC,SAAS,EAAG,EAAA,IAAI,CAAC,KAAK,CAAQ;wBACtD,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,MACpB,oBAAC,UAAU,EAAA,EACT,GAAG,EAAE,IAAI,CAAC,IAAI,EACd,EAAE,EAAE,IAAI,CAAC,IAAI,EACb,MAAM,EAAE,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,IAAI,EACtC,OAAO,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAA;AAEzC,4BAAA,KAAA,CAAA,aAAA,CAAC,WAAW,EAAA,EAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAI,CAAA;4BAC/C,KAAO,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,EAAA,IAAI,CAAC,KAAK,CAAQ,CACd,CACd,CAAC,CACa,CAClB,CAAC;AACD,oBAAA,KAAK,CAAC,kBAAkB,KACvB,KAAC,CAAA,aAAA,CAAA,MAAM,IACL,OAAO,EAAC,QAAQ,EAChB,IAAI,EAAC,IAAI,EACT,EAAE,EAAC,IAAI,EACP,QAAQ,EAAE,KAAC,CAAA,aAAA,CAAA,QAAQ,IAAC,IAAI,EAAC,SAAS,EAAG,CAAA,EACrC,OAAO,EAAE,MAAM,wBAAwB,CAAC,IAAI,CAAC,mBAGtC,CACV,CACqB,CACb,CACC;QACf,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,YAAY,KACnC,KAAA,CAAA,aAAA,CAAC,cAAc,EAAA,EACb,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,YAAY,EAAE,KAAK,CAAC,YAAY,EAChC,OAAO,EAAE,qBAAqB,EAC9B,IAAI,EAAE,MAAM,wBAAwB,CAAC,KAAK,CAAC,EAC3C,QAAQ,EAAE,MAAM,wBAAwB,CAAC,KAAK,CAAC,EAC/C,CAAA,CACH,CACA,EACH;AACJ,CAAC;AASD,SAAS,UAAU,CAAC,KAAsB,EAAA;IACxC,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,SAAS,EAAE,CAAC;AACpC,IAAA,QACE,KAAC,CAAA,aAAA,CAAA,WAAW,IACV,OAAO,EAAE,KAAK,CAAC,OAAO,EACtB,EAAE,EAAE,KAAK,CAAC,EAAE,EACZ,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAElE,EAAA,KAAK,CAAC,QAAQ,CACH,EACd;AACJ,CAAC;AAOD,SAAS,WAAW,CAAC,KAAuB,EAAA;IAC1C,IAAI,KAAK,CAAC,IAAI,EAAE;QACd,OAAO,KAAK,CAAC,IAAI,CAAC;AACnB,KAAA;AACD,IAAA,OAAO,oBAAC,KAAK,EAAA,EAAC,CAAC,EAAE,EAAE,GAAI,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;AAUG;AACH,SAAS,aAAa,CACpB,eAAmC,EACnC,mBAAgD,EAChD,KAA+B,EAAA;IAE/B,IAAI,CAAC,eAAe,IAAI,CAAC,mBAAmB,IAAI,CAAC,KAAK,EAAE;AACtD,QAAA,OAAO,SAAS,CAAC;AAClB,KAAA;IAED,IAAI,QAAQ,GAAG,SAAS,CAAC;IACzB,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,IAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;QACxB,IAAI,IAAI,CAAC,KAAK,EAAE;AACd,YAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;AAC7B,gBAAA,MAAM,KAAK,GAAG,YAAY,CAAC,eAAe,EAAE,mBAAmB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5E,IAAI,KAAK,GAAG,SAAS,EAAE;oBACrB,SAAS,GAAG,KAAK,CAAC;oBAClB,QAAQ,GAAG,IAAI,CAAC;AACjB,iBAAA;AACF,aAAA;AACF,SAAA;AACF,KAAA;AAED,IAAA,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;AAUG;AACH,SAAS,YAAY,CAAC,eAAuB,EAAE,mBAAoC,EAAE,QAAgB,EAAA;IACnG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;AACzD,IAAA,IAAI,eAAe,KAAK,OAAO,CAAC,QAAQ,EAAE;AACxC,QAAA,OAAO,CAAC,CAAC;AACV,KAAA;AACD,IAAA,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC5C,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE;AACzD,QAAA,IAAI,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;YAC/B,SAAS;AACV,SAAA;QACD,IAAI,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE;AAC1C,YAAA,OAAO,CAAC,CAAC;AACV,SAAA;AACF,KAAA;IACD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,EAAE;AACxD,QAAA,IAAI,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;YAC/B,SAAS;AACV,SAAA;QACD,IAAI,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE;AAC3C,YAAA,KAAK,EAAE,CAAC;AACT,SAAA;AACF,KAAA;AACD,IAAA,OAAO,KAAK,CAAC;AACf;;;;"}
|
|
@@ -2,17 +2,15 @@ import { Modal, Stack, TextInput, Group, Button, NativeSelect } from '@mantine/c
|
|
|
2
2
|
import { showNotification } from '@mantine/notifications';
|
|
3
3
|
import { deepClone, normalizeErrorString } from '@medplum/core';
|
|
4
4
|
import React from 'react';
|
|
5
|
-
import { useLocation } from 'react-router-dom';
|
|
6
5
|
import { Form } from '../Form/Form.mjs';
|
|
7
6
|
import { useMedplum } from '../MedplumProvider/MedplumProvider.mjs';
|
|
8
7
|
|
|
9
8
|
function BookmarkDialog(props) {
|
|
10
9
|
const medplum = useMedplum();
|
|
11
10
|
const config = medplum.getUserConfiguration();
|
|
12
|
-
const location = useLocation();
|
|
13
11
|
function submitHandler(formData) {
|
|
14
12
|
const { menuname, bookmarkname: name } = formData;
|
|
15
|
-
const target =
|
|
13
|
+
const target = `${props.pathname}?${props.searchParams.toString()}`;
|
|
16
14
|
const newConfig = deepClone(config);
|
|
17
15
|
const menu = newConfig?.menu?.find(({ title }) => title === menuname);
|
|
18
16
|
menu?.link?.push({ name, target });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BookmarkDialog.mjs","sources":["../../../src/BookmarkDialog/BookmarkDialog.tsx"],"sourcesContent":["import { Button, Group, Modal, NativeSelect, Stack, TextInput } from '@mantine/core';\nimport { showNotification } from '@mantine/notifications';\nimport { deepClone, normalizeErrorString } from '@medplum/core';\nimport { UserConfiguration } from '@medplum/fhirtypes';\nimport React from 'react';\nimport {
|
|
1
|
+
{"version":3,"file":"BookmarkDialog.mjs","sources":["../../../src/BookmarkDialog/BookmarkDialog.tsx"],"sourcesContent":["import { Button, Group, Modal, NativeSelect, Stack, TextInput } from '@mantine/core';\nimport { showNotification } from '@mantine/notifications';\nimport { deepClone, normalizeErrorString } from '@medplum/core';\nimport { UserConfiguration } from '@medplum/fhirtypes';\nimport React from 'react';\nimport { Form } from '../Form/Form';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider';\n\ninterface BookmarkDialogProps {\n pathname: string;\n searchParams: URLSearchParams;\n visible: boolean;\n onOk: () => void;\n onCancel: () => void;\n defaultValue?: string;\n}\nexport function BookmarkDialog(props: BookmarkDialogProps): JSX.Element | null {\n const medplum = useMedplum();\n const config = medplum.getUserConfiguration() as UserConfiguration;\n\n function submitHandler(formData: Record<string, string>): void {\n const { menuname, bookmarkname: name } = formData;\n const target = `${props.pathname}?${props.searchParams.toString()}`;\n const newConfig = deepClone(config) as UserConfiguration;\n const menu = newConfig?.menu?.find(({ title }) => title === menuname);\n\n menu?.link?.push({ name, target });\n medplum\n .updateResource(newConfig)\n .then((res) => {\n // refresh current config menu\n config.menu = res.menu;\n medplum.dispatchEvent({ type: 'change' });\n showNotification({ color: 'green', message: 'Success' });\n props.onOk();\n })\n .catch((err: any) => {\n showNotification({ color: 'red', message: normalizeErrorString(err) });\n });\n }\n\n return (\n <Modal\n title=\"Add Bookmark\"\n closeButtonProps={{ 'aria-label': 'Close' }}\n opened={props.visible}\n onClose={props.onCancel}\n >\n <Form onSubmit={submitHandler}>\n <Stack>\n <SelectMenu config={config}></SelectMenu>\n <TextInput label=\"Bookmark Name\" type=\"text\" name=\"bookmarkname\" placeholder=\"bookmark name\" withAsterisk />\n <Group position=\"right\">\n <Button mt=\"sm\" type=\"submit\">\n OK\n </Button>\n </Group>\n </Stack>\n </Form>\n </Modal>\n );\n}\n\ninterface SelectMenuProps {\n config: UserConfiguration | undefined;\n}\n\nfunction SelectMenu(props: SelectMenuProps): JSX.Element {\n function userConfigToMenu(config: UserConfiguration | undefined): string[] {\n return config?.menu?.map((menu) => menu.title) as [];\n }\n const menus = userConfigToMenu(props.config);\n\n return (\n <NativeSelect\n name=\"menuname\"\n defaultValue={menus?.[0]}\n label=\"Select Menu Option\"\n placeholder=\"Menu\"\n data={menus}\n withAsterisk\n />\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -37,9 +37,9 @@ function getGoogleClientId(clientId) {
|
|
|
37
37
|
}
|
|
38
38
|
if (typeof window !== 'undefined') {
|
|
39
39
|
const origin = window.location.protocol + '//' + window.location.host;
|
|
40
|
-
const authorizedOrigins = "
|
|
40
|
+
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(',') ?? [];
|
|
41
41
|
if (authorizedOrigins.includes(origin)) {
|
|
42
|
-
return "
|
|
42
|
+
return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleButton.mjs","sources":["../../../src/GoogleButton/GoogleButton.tsx"],"sourcesContent":["import { GoogleCredentialResponse } from '@medplum/core';\nimport React, { useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider';\nimport { createScriptTag } from '../utils/script';\n\ninterface GoogleApi {\n accounts: {\n id: {\n initialize: (args: any) => void;\n renderButton: (parent: HTMLElement, args: any) => void;\n };\n };\n}\n\ndeclare const google: GoogleApi;\n\nexport interface GoogleButtonProps {\n readonly googleClientId?: string;\n readonly handleGoogleCredential: (response: GoogleCredentialResponse) => void;\n}\n\nexport function GoogleButton(props: GoogleButtonProps): JSX.Element | null {\n const medplum = useMedplum();\n const { googleClientId, handleGoogleCredential } = props;\n const parentRef = useRef<HTMLDivElement>(null);\n const [scriptLoaded, setScriptLoaded] = useState<boolean>(typeof google !== 'undefined');\n const [initialized, setInitialized] = useState<boolean>(false);\n const [buttonRendered, setButtonRendered] = useState<boolean>(false);\n\n useEffect(() => {\n if (typeof google === 'undefined') {\n createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));\n return;\n }\n\n if (!initialized) {\n google.accounts.id.initialize({\n client_id: googleClientId,\n callback: handleGoogleCredential,\n });\n setInitialized(true);\n }\n\n if (parentRef.current && !buttonRendered) {\n google.accounts.id.renderButton(parentRef.current, {});\n setButtonRendered(true);\n }\n }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);\n\n if (!googleClientId) {\n return null;\n }\n\n return <div ref={parentRef} />;\n}\n\nexport function getGoogleClientId(clientId: string | undefined): string | undefined {\n if (clientId) {\n return clientId;\n }\n\n if (typeof window !== 'undefined') {\n const origin = window.location.protocol + '//' + window.location.host;\n const authorizedOrigins = process.env.GOOGLE_AUTH_ORIGINS?.split(',') ?? [];\n if (authorizedOrigins.includes(origin)) {\n return process.env.GOOGLE_CLIENT_ID;\n }\n }\n\n return undefined;\n}\n"],"names":[],"mappings":";;;;AAqBM,SAAU,YAAY,CAAC,KAAwB,EAAA;AACnD,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,IAAA,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,GAAG,KAAK,CAAC;AACzD,IAAA,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;AAC/C,IAAA,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAU,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC;IACzF,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC/D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAErE,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;YACjC,eAAe,CAAC,wCAAwC,EAAE,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;YACvF,OAAO;AACR,SAAA;QAED,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC;AAC5B,gBAAA,SAAS,EAAE,cAAc;AACzB,gBAAA,QAAQ,EAAE,sBAAsB;AACjC,aAAA,CAAC,CAAC;YACH,cAAc,CAAC,IAAI,CAAC,CAAC;AACtB,SAAA;AAED,QAAA,IAAI,SAAS,CAAC,OAAO,IAAI,CAAC,cAAc,EAAE;AACxC,YAAA,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACvD,iBAAiB,CAAC,IAAI,CAAC,CAAC;AACzB,SAAA;AACH,KAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAE5G,IAAI,CAAC,cAAc,EAAE;AACnB,QAAA,OAAO,IAAI,CAAC;AACb,KAAA;AAED,IAAA,OAAO,KAAK,CAAA,aAAA,CAAA,KAAA,EAAA,EAAA,GAAG,EAAE,SAAS,GAAI,CAAC;AACjC,CAAC;AAEK,SAAU,iBAAiB,CAAC,QAA4B,EAAA;AAC5D,IAAA,IAAI,QAAQ,EAAE;AACZ,QAAA,OAAO,QAAQ,CAAC;AACjB,KAAA;AAED,IAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;AACjC,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;AACtE,QAAA,MAAM,iBAAiB,GAAG,
|
|
1
|
+
{"version":3,"file":"GoogleButton.mjs","sources":["../../../src/GoogleButton/GoogleButton.tsx"],"sourcesContent":["import { GoogleCredentialResponse } from '@medplum/core';\nimport React, { useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider';\nimport { createScriptTag } from '../utils/script';\n\ninterface GoogleApi {\n accounts: {\n id: {\n initialize: (args: any) => void;\n renderButton: (parent: HTMLElement, args: any) => void;\n };\n };\n}\n\ndeclare const google: GoogleApi;\n\nexport interface GoogleButtonProps {\n readonly googleClientId?: string;\n readonly handleGoogleCredential: (response: GoogleCredentialResponse) => void;\n}\n\nexport function GoogleButton(props: GoogleButtonProps): JSX.Element | null {\n const medplum = useMedplum();\n const { googleClientId, handleGoogleCredential } = props;\n const parentRef = useRef<HTMLDivElement>(null);\n const [scriptLoaded, setScriptLoaded] = useState<boolean>(typeof google !== 'undefined');\n const [initialized, setInitialized] = useState<boolean>(false);\n const [buttonRendered, setButtonRendered] = useState<boolean>(false);\n\n useEffect(() => {\n if (typeof google === 'undefined') {\n createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));\n return;\n }\n\n if (!initialized) {\n google.accounts.id.initialize({\n client_id: googleClientId,\n callback: handleGoogleCredential,\n });\n setInitialized(true);\n }\n\n if (parentRef.current && !buttonRendered) {\n google.accounts.id.renderButton(parentRef.current, {});\n setButtonRendered(true);\n }\n }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);\n\n if (!googleClientId) {\n return null;\n }\n\n return <div ref={parentRef} />;\n}\n\nexport function getGoogleClientId(clientId: string | undefined): string | undefined {\n if (clientId) {\n return clientId;\n }\n\n if (typeof window !== 'undefined') {\n const origin = window.location.protocol + '//' + window.location.host;\n const authorizedOrigins = process.env.GOOGLE_AUTH_ORIGINS?.split(',') ?? [];\n if (authorizedOrigins.includes(origin)) {\n return process.env.GOOGLE_CLIENT_ID;\n }\n }\n\n return undefined;\n}\n"],"names":[],"mappings":";;;;AAqBM,SAAU,YAAY,CAAC,KAAwB,EAAA;AACnD,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,IAAA,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,GAAG,KAAK,CAAC;AACzD,IAAA,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;AAC/C,IAAA,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAU,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC;IACzF,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC/D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAErE,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;YACjC,eAAe,CAAC,wCAAwC,EAAE,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;YACvF,OAAO;AACR,SAAA;QAED,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC;AAC5B,gBAAA,SAAS,EAAE,cAAc;AACzB,gBAAA,QAAQ,EAAE,sBAAsB;AACjC,aAAA,CAAC,CAAC;YACH,cAAc,CAAC,IAAI,CAAC,CAAC;AACtB,SAAA;AAED,QAAA,IAAI,SAAS,CAAC,OAAO,IAAI,CAAC,cAAc,EAAE;AACxC,YAAA,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACvD,iBAAiB,CAAC,IAAI,CAAC,CAAC;AACzB,SAAA;AACH,KAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAE5G,IAAI,CAAC,cAAc,EAAE;AACnB,QAAA,OAAO,IAAI,CAAC;AACb,KAAA;AAED,IAAA,OAAO,KAAK,CAAA,aAAA,CAAA,KAAA,EAAA,EAAA,GAAG,EAAE,SAAS,GAAI,CAAC;AACjC,CAAC;AAEK,SAAU,iBAAiB,CAAC,QAA4B,EAAA;AAC5D,IAAA,IAAI,QAAQ,EAAE;AACZ,QAAA,OAAO,QAAQ,CAAC;AACjB,KAAA;AAED,IAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;AACjC,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;AACtE,QAAA,MAAM,iBAAiB,GAAG,6NAA+B,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;AAC5E,QAAA,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AACtC,YAAA,OAAO,0EAA4B,CAAC;AACrC,SAAA;AACF,KAAA;AAED,IAAA,OAAO,SAAS,CAAC;AACnB;;;;"}
|
|
@@ -3,6 +3,7 @@ import { formatSearchQuery, globalSchema, DEFAULT_SEARCH_COUNT } from '@medplum/
|
|
|
3
3
|
import React, { useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { Container } from '../Container/Container.mjs';
|
|
5
5
|
import { useMedplum } from '../MedplumProvider/MedplumProvider.mjs';
|
|
6
|
+
import { SearchExportDialog } from '../SearchExportDialog/SearchExportDialog.mjs';
|
|
6
7
|
import { SearchFieldEditor } from '../SearchFieldEditor/SearchFieldEditor.mjs';
|
|
7
8
|
import { SearchFilterEditor } from '../SearchFilterEditor/SearchFilterEditor.mjs';
|
|
8
9
|
import { SearchFilterValueDialog } from '../SearchFilterValueDialog/SearchFilterValueDialog.mjs';
|
|
@@ -11,7 +12,6 @@ import { SearchPopupMenu } from '../SearchPopupMenu/SearchPopupMenu.mjs';
|
|
|
11
12
|
import { isCheckboxCell, killEvent } from '../utils/dom.mjs';
|
|
12
13
|
import { getFieldDefinitions } from './SearchControlField.mjs';
|
|
13
14
|
import { buildFieldNameString, renderValue, setPage, addFilter, getOpString } from './SearchUtils.mjs';
|
|
14
|
-
import { SearchExportDialog } from '../SearchExportDialog/SearchExportDialog.mjs';
|
|
15
15
|
import IconColumns from '../node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs';
|
|
16
16
|
import IconFilter from '../node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs';
|
|
17
17
|
import IconFilePlus from '../node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs';
|
|
@@ -93,7 +93,7 @@ function SearchControl(props) {
|
|
|
93
93
|
useEffect(() => {
|
|
94
94
|
setOutcome(undefined);
|
|
95
95
|
medplum
|
|
96
|
-
.search(search.resourceType, formatSearchQuery({ ...search, total: '
|
|
96
|
+
.search(search.resourceType, formatSearchQuery({ ...search, total: 'estimate', fields: undefined }))
|
|
97
97
|
.then((response) => {
|
|
98
98
|
setState({ ...stateRef.current, searchResponse: response });
|
|
99
99
|
if (onLoad) {
|