Skip to main content
Glama
SearchPopupMenu.tsx11.3 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { Menu } from '@mantine/core'; import type { Filter, SearchRequest } from '@medplum/core'; import { Operator } from '@medplum/core'; import type { SearchParameter } from '@medplum/fhirtypes'; import { IconBleach, IconBleachOff, IconBracketsContain, IconBucket, IconBucketOff, IconCalendar, IconEqual, IconEqualNot, IconMathGreater, IconMathLower, IconSettings, IconSortAscending, IconSortDescending, IconX, } from '@tabler/icons-react'; import type { JSX } from 'react'; import { addLastMonthFilter, addMissingFilter, addNext24HoursFilter, addNextMonthFilter, addThisMonthFilter, addTodayFilter, addTomorrowFilter, addYearToDateFilter, addYesterdayFilter, buildFieldNameString, clearFiltersOnField, setSort, } from '../SearchControl/SearchUtils'; export interface SearchPopupMenuProps { readonly search: SearchRequest; readonly searchParams?: SearchParameter[]; readonly onPrompt: (searchParam: SearchParameter, filter: Filter) => void; readonly onChange: (definition: SearchRequest) => void; } export function SearchPopupMenu(props: SearchPopupMenuProps): JSX.Element | null { if (!props.searchParams) { return null; } function onSort(searchParam: SearchParameter, desc: boolean): void { onChange(setSort(props.search, searchParam.code, desc)); } function onClear(searchParam: SearchParameter): void { onChange(clearFiltersOnField(props.search, searchParam.code)); } function onPrompt(searchParam: SearchParameter, operator: Operator): void { props.onPrompt(searchParam, { code: searchParam.code, operator, value: '' }); } function onChange(definition: SearchRequest): void { props.onChange(definition); } // If there is only one search parameter, then show it directly if (props.searchParams.length === 1) { return ( <SearchParameterSubMenu search={props.search} searchParam={props.searchParams[0]} onSort={onSort} onPrompt={onPrompt} onChange={onChange} onClear={onClear} /> ); } // Otherwise, show a menu, with each search parameter as a sub menu return ( <Menu.Dropdown> {props.searchParams.map((searchParam) => ( <Menu.Item key={searchParam.code}>{buildFieldNameString(searchParam.code)}</Menu.Item> ))} </Menu.Dropdown> ); } interface SearchPopupSubMenuProps { readonly search: SearchRequest; readonly searchParam: SearchParameter; readonly onSort: (searchParam: SearchParameter, descending: boolean) => void; readonly onPrompt: (searchParam: SearchParameter, operator: Operator) => void; readonly onChange: (search: SearchRequest) => void; readonly onClear: (searchParam: SearchParameter) => void; } function SearchParameterSubMenu(props: SearchPopupSubMenuProps): JSX.Element { switch (props.searchParam.type) { case 'date': return <DateFilterSubMenu {...props} />; case 'number': case 'quantity': return <NumericFilterSubMenu {...props} />; case 'reference': return <ReferenceFilterSubMenu {...props} />; case 'string': return <TextFilterSubMenu {...props} />; case 'token': case 'uri': return <TokenFilterSubMenu {...props} />; default: return <>Unknown search param type: {props.searchParam.type}</>; } } function DateFilterSubMenu(props: SearchPopupSubMenuProps): JSX.Element { const { searchParam } = props; const code = searchParam.code; return ( <Menu.Dropdown> <Menu.Item leftSection={<IconSortAscending size={14} />} onClick={() => props.onSort(searchParam, false)}> Sort Oldest to Newest </Menu.Item> <Menu.Item leftSection={<IconSortDescending size={14} />} onClick={() => props.onSort(searchParam, true)}> Sort Newest to Oldest </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconEqual size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)}> Equals... </Menu.Item> <Menu.Item leftSection={<IconEqualNot size={14} />} onClick={() => props.onPrompt(searchParam, Operator.NOT_EQUALS)} > Does not equal... </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconMathLower size={14} />} onClick={() => props.onPrompt(searchParam, Operator.ENDS_BEFORE)} > Before... </Menu.Item> <Menu.Item leftSection={<IconMathGreater size={14} />} onClick={() => props.onPrompt(searchParam, Operator.STARTS_AFTER)} > After... </Menu.Item> <Menu.Item leftSection={<IconBracketsContain size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)} > Between... </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addTomorrowFilter(props.search, code))} > Tomorrow </Menu.Item> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addTodayFilter(props.search, code))} > Today </Menu.Item> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addYesterdayFilter(props.search, code))} > Yesterday </Menu.Item> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addNext24HoursFilter(props.search, code))} > Next 24 Hours </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addNextMonthFilter(props.search, code))} > Next Month </Menu.Item> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addThisMonthFilter(props.search, code))} > This Month </Menu.Item> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addLastMonthFilter(props.search, code))} > Last Month </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconCalendar size={14} />} onClick={() => props.onChange(addYearToDateFilter(props.search, code))} > Year to date </Menu.Item> <CommonMenuItems {...props} /> </Menu.Dropdown> ); } function NumericFilterSubMenu(props: SearchPopupSubMenuProps): JSX.Element { const { searchParam } = props; return ( <Menu.Dropdown> <Menu.Item leftSection={<IconSortAscending size={14} />} onClick={() => props.onSort(searchParam, false)}> Sort Smallest to Largest </Menu.Item> <Menu.Item leftSection={<IconSortDescending size={14} />} onClick={() => props.onSort(searchParam, true)}> Sort Largest to Smallest </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconEqual size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)}> Equals... </Menu.Item> <Menu.Item leftSection={<IconEqualNot size={14} />} onClick={() => props.onPrompt(searchParam, Operator.NOT_EQUALS)} > Does not equal... </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconMathGreater size={14} />} onClick={() => props.onPrompt(searchParam, Operator.GREATER_THAN)} > Greater than... </Menu.Item> <Menu.Item leftSection={<IconSettings size={14} />} onClick={() => props.onPrompt(searchParam, Operator.GREATER_THAN_OR_EQUALS)} > Greater than or equal to... </Menu.Item> <Menu.Item leftSection={<IconMathLower size={14} />} onClick={() => props.onPrompt(searchParam, Operator.LESS_THAN)} > Less than... </Menu.Item> <Menu.Item leftSection={<IconSettings size={14} />} onClick={() => props.onPrompt(searchParam, Operator.LESS_THAN_OR_EQUALS)} > Less than or equal to... </Menu.Item> <CommonMenuItems {...props} /> </Menu.Dropdown> ); } function ReferenceFilterSubMenu(props: SearchPopupSubMenuProps): JSX.Element { const { searchParam } = props; return ( <Menu.Dropdown> <Menu.Item leftSection={<IconEqual size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)}> Equals... </Menu.Item> <Menu.Item leftSection={<IconEqualNot size={14} />} onClick={() => props.onPrompt(searchParam, Operator.NOT)}> Does not equal... </Menu.Item> <CommonMenuItems {...props} /> </Menu.Dropdown> ); } function TextFilterSubMenu(props: SearchPopupSubMenuProps): JSX.Element { const { searchParam } = props; return ( <Menu.Dropdown> <Menu.Item leftSection={<IconSortAscending size={14} />} onClick={() => props.onSort(searchParam, false)}> Sort A to Z </Menu.Item> <Menu.Item leftSection={<IconSortDescending size={14} />} onClick={() => props.onSort(searchParam, true)}> Sort Z to A </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconEqual size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)}> Equals... </Menu.Item> <Menu.Item leftSection={<IconEqualNot size={14} />} onClick={() => props.onPrompt(searchParam, Operator.NOT)}> Does not equal... </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconBucket size={14} />} onClick={() => props.onPrompt(searchParam, Operator.CONTAINS)}> Contains... </Menu.Item> <Menu.Item leftSection={<IconBucketOff size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)}> Does not contain... </Menu.Item> <CommonMenuItems {...props} /> </Menu.Dropdown> ); } function TokenFilterSubMenu(props: SearchPopupSubMenuProps): JSX.Element { const { searchParam } = props; return ( <Menu.Dropdown> <Menu.Item leftSection={<IconEqual size={14} />} onClick={() => props.onPrompt(searchParam, Operator.EQUALS)}> Equals... </Menu.Item> <Menu.Item leftSection={<IconEqualNot size={14} />} onClick={() => props.onPrompt(searchParam, Operator.NOT)}> Does not equal... </Menu.Item> <CommonMenuItems {...props} /> </Menu.Dropdown> ); } function CommonMenuItems(props: SearchPopupSubMenuProps): JSX.Element { const { searchParam } = props; const code = searchParam.code; return ( <> <Menu.Divider /> <Menu.Item leftSection={<IconBleach size={14} />} onClick={() => props.onChange(addMissingFilter(props.search, code))} > Missing </Menu.Item> <Menu.Item leftSection={<IconBleachOff size={14} />} onClick={() => props.onChange(addMissingFilter(props.search, code, false))} > Not missing </Menu.Item> <Menu.Divider /> <Menu.Item leftSection={<IconX size={14} />} onClick={() => props.onClear(searchParam)}> Clear filters </Menu.Item> </> ); }

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