diff --git a/src/__tests__/imagings/search/ImagingRequestTable.test.tsx b/src/__tests__/imagings/search/ImagingRequestTable.test.tsx index 0f821a7708..dfa1dbb019 100644 --- a/src/__tests__/imagings/search/ImagingRequestTable.test.tsx +++ b/src/__tests__/imagings/search/ImagingRequestTable.test.tsx @@ -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: [ { @@ -18,6 +30,8 @@ const defaultSortRequest: SortRequest = { ], } +const mockStore = createMockStore([thunk]) + describe('Imaging Request Table', () => { const expectedImaging = { code: 'I-1234', @@ -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( + + + + + + + , + ) + }) + 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 }), + ) + }) + }) +}) diff --git a/src/imagings/model/ImagingSearchRequest.ts b/src/imagings/model/ImagingSearchRequest.ts index 8f90ec2437..cafaee58cf 100644 --- a/src/imagings/model/ImagingSearchRequest.ts +++ b/src/imagings/model/ImagingSearchRequest.ts @@ -1,4 +1,6 @@ +export type ImagingFilter = 'completed' | 'requested' | 'canceled' | 'all' + export default interface ImagingSearchRequest { - status: 'completed' | 'requested' | 'canceled' | 'all' + status: ImagingFilter text: string } diff --git a/src/imagings/search/ImagingRequestTable.tsx b/src/imagings/search/ImagingRequestTable.tsx index 8f442385a1..88e6c9e9ea 100644 --- a/src/imagings/search/ImagingRequestTable.tsx +++ b/src/imagings/search/ImagingRequestTable.tsx @@ -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' @@ -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 } return ( - 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} - /> + + +
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} + /> + + ) } diff --git a/src/imagings/search/ViewImagings.tsx b/src/imagings/search/ViewImagings.tsx index 8577cbdc00..1499b20011 100644 --- a/src/imagings/search/ViewImagings.tsx +++ b/src/imagings/search/ViewImagings.tsx @@ -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('all') + const [searchText, setSearchText] = useState('') + const debouncedSearchText = useDebounce(searchText, 500) const { t } = useTranslator() const { permissions } = useSelector((state: RootState) => state.user) const history = useHistory() @@ -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({ - status: 'all', - text: '', + status: searchFilter, + text: debouncedSearchText, }) const getButtons = useCallback(() => { @@ -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()) @@ -56,8 +72,34 @@ const ViewImagings = () => { } }, [getButtons, setButtons]) + const onSearchBoxChange = (event: React.ChangeEvent) => { + setSearchText(event.target.value) + } + return ( + + + value === searchFilter)} + onChange={(values) => setSearchFilter(values[0] as ImagingFilter)} + isEditable + /> + + + + + diff --git a/src/medications/ViewMedication.tsx b/src/medications/ViewMedication.tsx index d22f4daf69..70e97a7ba6 100644 --- a/src/medications/ViewMedication.tsx +++ b/src/medications/ViewMedication.tsx @@ -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' @@ -164,7 +164,7 @@ const ViewMedication = () => { } return ( - <> + {status === 'error' && ( )} @@ -311,7 +311,7 @@ const ViewMedication = () => { )} - + ) } return

Loading...

diff --git a/src/shared/locales/enUs/translations/imagings/index.ts b/src/shared/locales/enUs/translations/imagings/index.ts index ff85ba1f78..adef3126c3 100644 --- a/src/shared/locales/enUs/translations/imagings/index.ts +++ b/src/shared/locales/enUs/translations/imagings/index.ts @@ -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',