Skip to main content
Glama
App.tsx8.46 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { LoadingOverlay } from '@mantine/core'; import { Operator, capitalize, formatCodeableConcept, formatSearchQuery, getExtension, getReferenceString, normalizeErrorString, } from '@medplum/core'; import type { MedplumClient, SearchRequest } from '@medplum/core'; import type { Practitioner } from '@medplum/fhirtypes'; import { AppShell, Loading, Logo, useMedplum, useMedplumProfile } from '@medplum/react'; import type { NavbarLink } from '@medplum/react'; import { IconCategory, IconChecklist, IconDatabaseImport, IconGridDots, IconMail, IconNurse, IconReportMedical, IconRibbonHealth, IconRobot, IconUser, } from '@tabler/icons-react'; import { Suspense, useEffect, useState } from 'react'; import type { JSX } from 'react'; import { Navigate, Route, Routes } from 'react-router'; import { LandingPage } from './pages/LandingPage'; import { ResourcePage } from './pages/ResourcePage'; import { SearchPage } from './pages/SearchPage'; import { SignInPage } from './pages/SignInPage'; import { TaskPage } from './pages/TaskPage'; import { UploadDataPage } from './pages/UploadDataPage'; const SEARCH_TABLE_FIELDS = ['code', 'owner', 'for', 'priority', 'due-date', '_lastUpdated', 'performerType']; const ALL_TASKS_LINK = { icon: <IconGridDots />, label: 'All Tasks', href: `/Task?_fields=${SEARCH_TABLE_FIELDS.join(',')}`, }; export function App(): JSX.Element | null { const medplum = useMedplum(); const profile = useMedplumProfile(); const [userLinks, setUserLinks] = useState<NavbarLink[]>([]); const showLoadingOverlay = profile && (medplum.isLoading() || userLinks.length === 0); // Update the sidebar links associated with the Medplum profiles useEffect(() => { const profileReferenceString = profile && getReferenceString(profile); if (!profileReferenceString) { return; } // Construct the search for "My Tasks" const myTasksLink = getMyTasksLink(profileReferenceString); // Query the user's `PractitionerRole` resources to find all applicable roles getTasksByRoleLinks(medplum, profileReferenceString) .then((roleLinks) => { setUserLinks([myTasksLink, ...roleLinks, ...stateLinks, ALL_TASKS_LINK]); }) .catch((error) => console.error('Failed to fetch PractitionerRoles', normalizeErrorString(error))); // Construct Search links for all Tasks for patients in the current user's licensed states const stateLinks = getTasksByState(profile as Practitioner); }, [profile, medplum]); return ( <> <AppShell logo={<Logo size={24} />} menus={[ { title: 'Tasks', links: userLinks, }, { title: 'Upload Data', links: [ { icon: <IconDatabaseImport />, label: 'Upload Core ValueSets', href: '/upload/core' }, { icon: <IconChecklist />, label: 'Upload Example Tasks', href: '/upload/task' }, { icon: <IconNurse />, label: 'Upload Example Certifications', href: '/upload/role' }, { icon: <IconRibbonHealth />, label: 'Upload Example Licenses', href: '/upload/qualifications', }, { icon: <IconRobot />, label: 'Upload Example Bots', href: '/upload/bots' }, { icon: <IconReportMedical />, label: 'Upload Example Report', href: '/upload/report' }, { icon: <IconMail />, label: 'Upload Example Messages', href: '/upload/message' }, ], }, ]} headerSearchDisabled={true} > <LoadingOverlay visible={showLoadingOverlay} /> <Suspense fallback={<Loading />}> <Routes> <Route path="/" element={profile ? <Navigate to={ALL_TASKS_LINK.href} /> : <LandingPage />} /> <Route path="/signin" element={<SignInPage />} /> <Route path="/:resourceType" element={<SearchPage />} /> <Route path="/:resourceType/:id"> <Route index element={<ResourcePage />} /> <Route path="*" element={<ResourcePage />} /> </Route> <Route path="/Task/:id"> <Route index element={<TaskPage />} /> <Route path="*" element={<TaskPage />} /> </Route> <Route path="/Task" element={<SearchPage />} /> <Route path="/upload/:dataType" element={<UploadDataPage />} /> </Routes> </Suspense> </AppShell> </> ); } /** * @param profileReference - string representing the current user's profile * @returns a NavBar link to a search for all open `Tasks` assigned to the current user */ function getMyTasksLink(profileReference: string): NavbarLink { const myTasksQuery = formatSearchQuery({ resourceType: 'Task', fields: SEARCH_TABLE_FIELDS, sortRules: [{ code: '-priority-order,due-date' }], filters: [ { code: 'owner', operator: Operator.EQUALS, value: profileReference }, { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, ], }); const myTasksLink = { icon: <IconCategory />, label: 'My Tasks', href: `/Task${myTasksQuery}` }; return myTasksLink; } /** * @param medplum - the MedplumClient * @param profileReference - string representing the current user's profile * @returns an array of NavBarLinks to searches for all open `Tasks` assigned to the current user's roles */ async function getTasksByRoleLinks(medplum: MedplumClient, profileReference: string): Promise<NavbarLink[]> { const roles = await medplum.searchResources('PractitionerRole', { practitioner: profileReference, }); // Query the user's `PractitionerRole` resources to find all applicable roles return roles .map((role) => { // For each role, generate a link to all open Tasks const roleCode = role?.code?.[0]; if (!roleCode?.coding?.[0]?.code) { return undefined; } const search: SearchRequest = { resourceType: 'Task', fields: SEARCH_TABLE_FIELDS, sortRules: [{ code: '-priority-order,due-date' }], filters: [ { code: 'owner:missing', operator: Operator.EQUALS, value: 'true' }, { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, { code: 'performer', operator: Operator.EQUALS, value: roleCode?.coding?.[0]?.code }, ], }; const searchQuery = formatSearchQuery(search); const roleDisplay = formatCodeableConcept(roleCode); return { icon: <IconUser />, label: `${roleDisplay} Tasks`, href: `/Task${searchQuery}` } as NavbarLink; }) .filter((link): link is NavbarLink => !!link); } /** * * Read all the states for which this practitioner is licensed. * Refer to [Modeling Provider Qualifications](https://www.medplum.com/docs/administration/provider-directory/provider-credentials) * for more information on how to represent a clinician's licenses * @param profile - The resource representing the current user * @returns an array of NavBarLinks to searches for all open `Tasks` assigned to patients' in states * where the current user is licensed */ function getTasksByState(profile: Practitioner): NavbarLink[] { const myStates = profile.qualification ?.map( (qualification) => getExtension( qualification, 'http://hl7.org/fhir/us/davinci-pdex-plan-net/StructureDefinition/practitioner-qualification', 'whereValid' )?.valueCodeableConcept?.coding?.find((coding) => coding.system === 'https://www.usps.com/')?.code ) .filter((state): state is string => !!state) ?? []; return myStates.map((state) => { const search: SearchRequest = { resourceType: 'Task', fields: SEARCH_TABLE_FIELDS, sortRules: [{ code: '-priority-order,due-date' }], filters: [ { code: 'owner:missing', operator: Operator.EQUALS, value: 'true' }, { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, { code: 'patient.address-state', operator: Operator.EQUALS, value: state }, ], }; const searchQuery = formatSearchQuery(search); return { icon: <IconUser />, label: `${capitalize(state)} Tasks`, href: `/Task${searchQuery}` } as NavbarLink; }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server