Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

feat(imaging): add ability to search imaging requests #2522

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 124 additions & 1 deletion src/__tests__/imagings/search/ImagingRequestTable.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { Table } from '@hospitalrun/components'
import { Select, Table, TextInput } from '@hospitalrun/components'
import { mount, ReactWrapper } from 'enzyme'
import { createMemoryHistory } from 'history'
import React from 'react'
import { act } from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { Router } from 'react-router-dom'
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'

import ImagingSearchRequest from '../../../imagings/model/ImagingSearchRequest'
import ImagingRequestTable from '../../../imagings/search/ImagingRequestTable'
import ViewImagings from '../../../imagings/search/ViewImagings'
import * as titleUtil from '../../../page-header/title/TitleContext'
import SelectWithLabelFormGroup from '../../../shared/components/input/SelectWithLabelFormGroup'
import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup'
import ImagingRepository from '../../../shared/db/ImagingRepository'
import SortRequest from '../../../shared/db/SortRequest'
import Imaging from '../../../shared/model/Imaging'
import Permissions from '../../../shared/model/Permissions'
import { RootState } from '../../../shared/store'

const { TitleProvider } = titleUtil
const defaultSortRequest: SortRequest = {
sorts: [
{
Expand All @@ -18,6 +30,8 @@ const defaultSortRequest: SortRequest = {
],
}

const mockStore = createMockStore<RootState, any>([thunk])

describe('Imaging Request Table', () => {
const expectedImaging = {
code: 'I-1234',
Expand Down Expand Up @@ -80,3 +94,112 @@ describe('Imaging Request Table', () => {
expect(table.prop('data')).toEqual([expectedImaging])
})
})

describe('View Imagings Search', () => {
const expectedImaging = {
code: 'I-1234',
id: '1234',
type: 'imaging type',
patient: 'patient',
fullName: 'full name',
status: 'requested',
requestedOn: new Date().toISOString(),
requestedBy: 'some user',
} as Imaging
const expectedImagings = [expectedImaging]

const setup = async (permissions: Permissions[] = []) => {
jest.resetAllMocks()
jest.spyOn(ImagingRepository, 'search').mockResolvedValue(expectedImagings)

const history = createMemoryHistory()
const store = mockStore({
title: '',
user: {
permissions,
},
} as any)

let wrapper: any
await act(async () => {
wrapper = await mount(
<Provider store={store}>
<Router history={history}>
<TitleProvider>
<ViewImagings />
</TitleProvider>
</Router>
</Provider>,
)
})
wrapper.update()

return { wrapper: wrapper as ReactWrapper }
}

describe('imagings dropdown filter', () => {
const searchImagingSpy = jest.spyOn(ImagingRepository, 'search')

beforeEach(() => {
searchImagingSpy.mockClear()
})

it('should render imaging filter field', async () => {
const { wrapper } = await setup([Permissions.ViewImagings])
expect(wrapper.find(SelectWithLabelFormGroup)).toHaveLength(1)
})

it('should filter and cause correct search results', async () => {
const { wrapper } = await setup([Permissions.ViewImagings])
const expectedStatus = 'requested'

await act(async () => {
const onChange = wrapper.find(Select).prop('onChange') as any
await onChange([expectedStatus])
})

expect(searchImagingSpy).toHaveBeenCalledTimes(2)
expect(searchImagingSpy).toHaveBeenCalledWith(
expect.objectContaining({ status: expectedStatus }),
)
})
})

describe('imagings search field', () => {
const searchImagingSpy = jest.spyOn(ImagingRepository, 'search')

beforeEach(() => {
searchImagingSpy.mockClear()
})

it('should render imaging search text field', async () => {
const { wrapper } = await setup([Permissions.ViewImagings])
expect(wrapper.find(TextInputWithLabelFormGroup)).toHaveLength(1)
})

it('should search after 500 debounced typing', async () => {
const { wrapper } = await setup([Permissions.ViewImagings])
const expectedSearchText = '1234'

jest.useFakeTimers()
act(() => {
const onChange = wrapper.find(TextInput).prop('onChange') as any
onChange({
target: {
value: expectedSearchText,
},
preventDefault: jest.fn(),
})
})

act(() => {
jest.advanceTimersByTime(500)
})

expect(searchImagingSpy).toHaveBeenCalledTimes(2)
expect(searchImagingSpy).toHaveBeenCalledWith(
expect.objectContaining({ text: expectedSearchText }),
)
})
})
})
4 changes: 3 additions & 1 deletion src/imagings/model/ImagingSearchRequest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type ImagingFilter = 'completed' | 'requested' | 'canceled' | 'all'

export default interface ImagingSearchRequest {
status: 'completed' | 'requested' | 'canceled' | 'all'
status: ImagingFilter
text: string
}
49 changes: 26 additions & 23 deletions src/imagings/search/ImagingRequestTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Table } from '@hospitalrun/components'
import { Container, Row, Table } from '@hospitalrun/components'
import format from 'date-fns/format'
import React from 'react'

Expand All @@ -16,33 +16,36 @@ const ImagingRequestTable = (props: Props) => {
const { searchRequest } = props
const { t } = useTranslator()
const { data, status } = useImagingSearch(searchRequest)

if (data === undefined || status === 'loading') {
return <Loading />
}

return (
<Table
getID={(row) => row.id}
columns={[
{ label: t('imagings.imaging.code'), key: 'code' },
{ label: t('imagings.imaging.type'), key: 'type' },
{
label: t('imagings.imaging.requestedOn'),
key: 'requestedOn',
formatter: (row) =>
row.requestedOn ? format(new Date(row.requestedOn), 'yyyy-MM-dd hh:mm a') : '',
},
{ label: t('imagings.imaging.patient'), key: 'fullName' },
{
label: t('imagings.imaging.requestedBy'),
key: 'requestedBy',
formatter: (row) => extractUsername(row.requestedByFullName || ''),
},
{ label: t('imagings.imaging.status'), key: 'status' },
]}
data={data}
/>
<Container>
<Row>
<Table
getID={(row) => row.id}
columns={[
{ label: t('imagings.imaging.code'), key: 'code' },
{ label: t('imagings.imaging.type'), key: 'type' },
{
label: t('imagings.imaging.requestedOn'),
key: 'requestedOn',
formatter: (row) =>
row.requestedOn ? format(new Date(row.requestedOn), 'yyyy-MM-dd hh:mm a') : '',
},
{ label: t('imagings.imaging.patient'), key: 'fullName' },
{
label: t('imagings.imaging.requestedBy'),
key: 'requestedBy',
formatter: (row) => extractUsername(row.requestedBy),
},
{ label: t('imagings.imaging.status'), key: 'status' },
]}
data={data}
/>
</Row>
</Container>
)
}

Expand Down
54 changes: 48 additions & 6 deletions src/imagings/search/ViewImagings.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { Button, Container, Row } from '@hospitalrun/components'
import { Button, Container, Row, Column } from '@hospitalrun/components'
import React, { useState, useEffect, useCallback } from 'react'
import { useSelector } from 'react-redux'
import { useHistory } from 'react-router-dom'

import { useButtonToolbarSetter } from '../../page-header/button-toolbar/ButtonBarProvider'
import { useUpdateTitle } from '../../page-header/title/TitleContext'
import SelectWithLabelFormGroup, {
Option,
} from '../../shared/components/input/SelectWithLabelFormGroup'
import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup'
import useDebounce from '../../shared/hooks/useDebounce'
import useTranslator from '../../shared/hooks/useTranslator'
import Permissions from '../../shared/model/Permissions'
import { RootState } from '../../shared/store'
import ImagingSearchRequest from '../model/ImagingSearchRequest'
import ImagingSearchRequest, { ImagingFilter } from '../model/ImagingSearchRequest'
import ImagingRequestTable from './ImagingRequestTable'

const ViewImagings = () => {
const [searchFilter, setSearchFilter] = useState<ImagingFilter>('all')
const [searchText, setSearchText] = useState<string>('')
const debouncedSearchText = useDebounce(searchText, 500)
const { t } = useTranslator()
const { permissions } = useSelector((state: RootState) => state.user)
const history = useHistory()
Expand All @@ -20,9 +28,17 @@ const ViewImagings = () => {
useEffect(() => {
updateTitle(t('imagings.label'))
})

const filterOptions: Option[] = [
{ label: t('imagings.status.requested'), value: 'requested' },
{ label: t('imagings.status.completed'), value: 'completed' },
{ label: t('imagings.status.canceled'), value: 'canceled' },
{ label: t('imagings.filter.all'), value: 'all' },
]

const [searchRequest, setSearchRequest] = useState<ImagingSearchRequest>({
status: 'all',
text: '',
status: searchFilter,
text: debouncedSearchText,
})

const getButtons = useCallback(() => {
Expand All @@ -46,8 +62,8 @@ const ViewImagings = () => {
}, [permissions, history, t])

useEffect(() => {
setSearchRequest((previousRequest) => ({ ...previousRequest, status: 'all' }))
}, [])
setSearchRequest(() => ({ text: debouncedSearchText, status: searchFilter }))
}, [searchFilter, debouncedSearchText])

useEffect(() => {
setButtons(getButtons())
Expand All @@ -56,8 +72,34 @@ const ViewImagings = () => {
}
}, [getButtons, setButtons])

const onSearchBoxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(event.target.value)
}

return (
<Container>
<Row>
<Column md={3} lg={2}>
<SelectWithLabelFormGroup
name="type"
label={t('imagings.filterTitle')}
options={filterOptions}
defaultSelected={filterOptions.filter(({ value }) => value === searchFilter)}
onChange={(values) => setSearchFilter(values[0] as ImagingFilter)}
isEditable
/>
Comment on lines +83 to +90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add the following tests related to this field:

  • make sure that it gets rendered on the page
  • make sure that when its value changes that it performs a search

</Column>
<Column>
<TextInputWithLabelFormGroup
name="searchbox"
label={t('imagings.search')}
placeholder={t('imagings.searchPlaceholder')}
value={searchText}
isEditable
onChange={onSearchBoxChange}
/>
Comment on lines +93 to +100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add the following tests related to this field:

  • make sure that it gets rendered on the page
  • make sure that when its value changes that it performs a search

</Column>
</Row>
<Row>
<ImagingRequestTable searchRequest={searchRequest} />
</Row>
Expand Down
6 changes: 3 additions & 3 deletions src/medications/ViewMedication.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Row, Column, Badge, Button, Alert } from '@hospitalrun/components'
import { Container, Row, Column, Badge, Button, Alert } from '@hospitalrun/components'
import format from 'date-fns/format'
import React, { useEffect, useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
Expand Down Expand Up @@ -164,7 +164,7 @@ const ViewMedication = () => {
}

return (
<>
<Container>
{status === 'error' && (
<Alert color="danger" title={t('states.error')} message={t(error.message || '')} />
)}
Expand Down Expand Up @@ -311,7 +311,7 @@ const ViewMedication = () => {
</div>
)}
</form>
</>
</Container>
)
}
return <h1>Loading...</h1>
Expand Down
6 changes: 6 additions & 0 deletions src/shared/locales/enUs/translations/imagings/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
export default {
imagings: {
label: 'Imagings',
filterTitle: 'Filter by status',
search: 'Search imaging',
searchPlaceholder: 'X-ray, CT, PET, etc.',
status: {
requested: 'Requested',
completed: 'Completed',
canceled: 'Canceled',
},
filter: {
all: 'All Statuses',
},
requests: {
label: 'Imaging Requests',
new: 'New Imaging Request',
Expand Down