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

Commit

Permalink
feat(viewpatient): added labs tab to ViewPatient
Browse files Browse the repository at this point in the history
A patient can see all his requests for labs in the Labs tab

fix #1972
  • Loading branch information
rubencgt committed Apr 21, 2020
1 parent a0f709a commit 7bca96e
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/__tests__/labs/Labs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('Labs', () => {
user: { permissions: [Permissions.RequestLab] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
patient: '',
})

const wrapper = mount(
Expand Down
39 changes: 30 additions & 9 deletions src/__tests__/labs/requests/NewLabRequest.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('New Lab Request', () => {
const history = createMemoryHistory()

beforeEach(() => {
const store = mockStore({ title: '' })
const store = mockStore({ title: '', patient: '' })
titleSpy = jest.spyOn(titleUtil, 'default')
history.push('/labs/new')

Expand All @@ -48,7 +48,7 @@ describe('New Lab Request', () => {
const history = createMemoryHistory()

beforeEach(() => {
const store = mockStore({ title: '' })
const store = mockStore({ title: '', patient: '' })
history.push('/labs/new')

wrapper = mount(
Expand Down Expand Up @@ -112,7 +112,7 @@ describe('New Lab Request', () => {

beforeEach(() => {
history.push('/labs/new')
const store = mockStore({ title: '' })
const store = mockStore({ title: '', patient: '' })
wrapper = mount(
<Provider store={store}>
<Router history={history}>
Expand All @@ -138,6 +138,7 @@ describe('New Lab Request', () => {
let wrapper: ReactWrapper
const history = createMemoryHistory()
let labRepositorySaveSpy: any
let patientRepositorySaveOrUpdateSpy: any
const expectedDate = new Date()
const expectedLab = {
patientId: '12345',
Expand All @@ -147,18 +148,21 @@ describe('New Lab Request', () => {
id: '1234',
requestedOn: expectedDate.toISOString(),
} as Lab
const patient = { id: expectedLab.patientId, fullName: 'some full name' }

beforeEach(() => {
jest.resetAllMocks()
Date.now = jest.fn(() => expectedDate.valueOf())
labRepositorySaveSpy = jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedLab as Lab)

jest
.spyOn(PatientRepository, 'search')
.mockResolvedValue([{ id: expectedLab.patientId, fullName: 'some full name' }] as Patient[])
patientRepositorySaveOrUpdateSpy = jest
.spyOn(PatientRepository, 'saveOrUpdate')
.mockResolvedValue(patient as Patient)

jest.spyOn(PatientRepository, 'search').mockResolvedValue([patient] as Patient[])

history.push('/labs/new')
const store = mockStore({ title: '' })
const store = mockStore({ title: '', patient: '' })
wrapper = mount(
<Provider store={store}>
<Router history={history}>
Expand All @@ -168,11 +172,11 @@ describe('New Lab Request', () => {
)
})

it('should save the lab request and navigate to "/labs/:id"', async () => {
it('should save the lab request link new lab to patient and navigate to "/labs/:id"', async () => {
const patientTypeahead = wrapper.find(Typeahead)
await act(async () => {
const onChange = patientTypeahead.prop('onChange')
await onChange([{ id: expectedLab.patientId }] as Patient[])
await onChange([patient] as Patient[])
})

const typeInput = wrapper.find(TextInputWithLabelFormGroup)
Expand Down Expand Up @@ -204,6 +208,23 @@ describe('New Lab Request', () => {
requestedOn: expectedDate.toISOString(),
}),
)

expect(patientRepositorySaveOrUpdateSpy).toHaveBeenCalledTimes(1)
expect(patientRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(
expect.objectContaining({
id: expectedLab.patientId,
fullName: patient.fullName,
labs: [
{
patientId: '12345',
type: 'expected type',
status: 'requested',
notes: 'expected notes',
requestedOn: expectedLab.requestedOn,
},
],
}),
)
expect(history.location.pathname).toEqual(`/labs/${expectedLab.id}`)
})

Expand Down
81 changes: 81 additions & 0 deletions src/__tests__/patients/labs/LabsTab.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import '../../../__mocks__/matchMediaMock'
import React from 'react'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { mount } from 'enzyme'
import { createMemoryHistory } from 'history'
import { Router } from 'react-router'
import { Provider } from 'react-redux'
import * as components from '@hospitalrun/components'
import format from 'date-fns/format'
import LabsTab from '../../../patients/labs/LabsTab'
import Patient from '../../../model/Patient'
import Lab from '../../../model/Lab'
import Permissions from '../../../model/Permissions'

const expectedPatient = {
id: '123',
labs: [
{
patientId: '123',
type: 'type',
status: 'requested',
requestedOn: new Date().toISOString(),
} as Lab,
],
} as Patient

const mockStore = configureMockStore([thunk])
const history = createMemoryHistory()

let user: any
let store: any

const setup = (patient = expectedPatient, permissions = [Permissions.WritePatients]) => {
user = { permissions }
store = mockStore({ patient, user })
const wrapper = mount(
<Router history={history}>
<Provider store={store}>
<LabsTab patient={patient} />
</Provider>
</Router>,
)

return wrapper
}

describe('Labs Tab', () => {
it('should list the patients labs', () => {
const labs = expectedPatient.labs as Lab[]
const wrapper = setup()

const table = wrapper.find('table')
const tableHeader = wrapper.find('thead')
const tableHeaders = wrapper.find('th')
const tableBody = wrapper.find('tbody')
const tableData = wrapper.find('td')

expect(table).toHaveLength(1)
expect(tableHeader).toHaveLength(1)
expect(tableBody).toHaveLength(1)
expect(tableHeaders.at(0).text()).toEqual('labs.lab.type')
expect(tableHeaders.at(1).text()).toEqual('labs.lab.requestedOn')
expect(tableHeaders.at(2).text()).toEqual('labs.lab.status')
expect(tableData.at(0).text()).toEqual(labs[0].type)
expect(tableData.at(1).text()).toEqual(
format(new Date(labs[0].requestedOn), 'yyyy-MM-dd hh:mm a'),
)
expect(tableData.at(2).text()).toEqual(labs[0].status)
})

it('should render a warning message if the patient does not have any labs', () => {
const wrapper = setup({ ...expectedPatient, labs: [] })

const alert = wrapper.find(components.Alert)

expect(alert).toHaveLength(1)
expect(alert.prop('title')).toEqual('patient.labs.warning.noLabs')
expect(alert.prop('message')).toEqual('patient.labs.noLabsMessage')
})
})
32 changes: 30 additions & 2 deletions src/__tests__/patients/view/ViewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import * as titleUtil from '../../../page-header/useTitle'
import ViewPatient from '../../../patients/view/ViewPatient'
import * as patientSlice from '../../../patients/patient-slice'
import Permissions from '../../../model/Permissions'
import LabsTab from '../../../patients/labs/LabsTab'
import Lab from '../../../model/Lab'

const mockStore = configureMockStore([thunk])

Expand Down Expand Up @@ -49,11 +51,12 @@ describe('ViewPatient', () => {
jest.spyOn(PatientRepository, 'find')
const mockedPatientRepository = mocked(PatientRepository, true)
mockedPatientRepository.find.mockResolvedValue(patient)

const labs: Lab[] = []
history = createMemoryHistory()
store = mockStore({
patient: { patient },
user: { permissions },
labs: { labs },
})

history.push('/patients/123')
Expand Down Expand Up @@ -127,13 +130,14 @@ describe('ViewPatient', () => {
const tabs = tabsHeader.find(Tab)
expect(tabsHeader).toHaveLength(1)

expect(tabs).toHaveLength(6)
expect(tabs).toHaveLength(7)
expect(tabs.at(0).prop('label')).toEqual('patient.generalInformation')
expect(tabs.at(1).prop('label')).toEqual('patient.relatedPersons.label')
expect(tabs.at(2).prop('label')).toEqual('scheduling.appointments.label')
expect(tabs.at(3).prop('label')).toEqual('patient.allergies.label')
expect(tabs.at(4).prop('label')).toEqual('patient.diagnoses.label')
expect(tabs.at(5).prop('label')).toEqual('patient.notes.label')
expect(tabs.at(6).prop('label')).toEqual('patient.labs.label')
})

it('should mark the general information tab as active and render the general information component when route is /patients/:id', async () => {
Expand Down Expand Up @@ -262,4 +266,28 @@ describe('ViewPatient', () => {
expect(notesTab).toHaveLength(1)
expect(notesTab.prop('patient')).toEqual(patient)
})

it('should mark the labs tab as active when it is clicked and render the lab component when route is /patients/:id/labs', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

await act(async () => {
const tabsHeader = wrapper.find(TabsHeader)
const tabs = tabsHeader.find(Tab)
tabs.at(6).prop('onClick')()
})

wrapper.update()

const tabsHeader = wrapper.find(TabsHeader)
const tabs = tabsHeader.find(Tab)
const labsTab = wrapper.find(LabsTab)

expect(history.location.pathname).toEqual(`/patients/${patient.id}/labs`)
expect(tabs.at(6).prop('active')).toBeTruthy()
expect(labsTab).toHaveLength(1)
expect(labsTab.prop('patient')).toEqual(patient)
})
})
20 changes: 18 additions & 2 deletions src/labs/requests/NewLabRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import LabRepository from 'clients/db/LabRepository'
import Lab from 'model/Lab'
import TextFieldWithLabelFormGroup from 'components/input/TextFieldWithLabelFormGroup'
import useAddBreadcrumbs from 'breadcrumbs/useAddBreadcrumbs'
import { useDispatch } from 'react-redux'
import { updatePatient } from '../../patients/patient-slice'

const NewLabRequest = () => {
const { t } = useTranslation()
const history = useHistory()
const dispatch = useDispatch()
useTitle(t('labs.requests.new'))

const [isPatientInvalid, setIsPatientInvalid] = useState(false)
const [isTypeInvalid, setIsTypeInvalid] = useState(false)
const [typeFeedback, setTypeFeedback] = useState()

const [searchedPatient, setSearchedPatient] = useState({} as Patient)
const [newLabRequest, setNewLabRequest] = useState({
patientId: '',
type: '',
Expand All @@ -40,6 +43,7 @@ const NewLabRequest = () => {
...previousNewLabRequest,
patientId: patient.id,
}))
setSearchedPatient(patient)
}

const onLabTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -74,7 +78,19 @@ const NewLabRequest = () => {

newLab.requestedOn = new Date(Date.now().valueOf()).toISOString()
const createdLab = await LabRepository.save(newLab)
history.push(`/labs/${createdLab.id}`)

const newLabs: Lab[] = []
if (searchedPatient.labs) {
newLabs.push(...searchedPatient.labs)
}
newLabs.push(newLab)

const patientToUpdate = {
...searchedPatient,
labs: newLabs,
}

dispatch(updatePatient(patientToUpdate, () => history.push(`/labs/${createdLab.id}`)))
}
const onCancel = () => {
history.push('/labs')
Expand Down
8 changes: 8 additions & 0 deletions src/locales/enUs/translations/patient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ export default {
},
addNoteAbove: 'Add a note using the button above.',
},
labs: {
label: 'Labs',
new: 'Add New Lab',
warning: {
noLabs: 'No Labs',
},
noLabsMessage: 'No labs requests for this person.',
},
types: {
charity: 'Charity',
private: 'Private',
Expand Down
2 changes: 2 additions & 0 deletions src/model/Patient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import RelatedPerson from './RelatedPerson'
import Allergy from './Allergy'
import Diagnosis from './Diagnosis'
import Note from './Note'
import Lab from './Lab'

export default interface Patient extends AbstractDBModel, Name, ContactInformation {
sex: string
Expand All @@ -18,4 +19,5 @@ export default interface Patient extends AbstractDBModel, Name, ContactInformati
allergies?: Allergy[]
diagnoses?: Diagnosis[]
notes?: Note[]
labs?: Lab[]
}
55 changes: 55 additions & 0 deletions src/patients/labs/LabsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react'
import { Alert } from '@hospitalrun/components'
import { useTranslation } from 'react-i18next'
import format from 'date-fns/format'
import { useHistory } from 'react-router'
import Lab from '../../model/Lab'
import Patient from '../../model/Patient'

interface Props {
patient: Patient
}

const LabsTab = (props: Props) => {
const history = useHistory()
const { patient } = props
const { t } = useTranslation()

const onTableRowClick = (lab: Lab) => {
history.push(`/labs/${lab.id}`)
}

return (
<div>
{(!patient.labs || patient.labs.length === 0) && (
<Alert
color="warning"
title={t('patient.labs.warning.noLabs')}
message={t('patient.labs.noLabsMessage')}
/>
)}
{patient.labs && patient.labs.length > 0 && (
<table className="table table-hover">
<thead className="thead-light">
<tr>
<th>{t('labs.lab.type')}</th>
<th>{t('labs.lab.requestedOn')}</th>
<th>{t('labs.lab.status')}</th>
</tr>
</thead>
<tbody>
{patient.labs.map((lab) => (
<tr onClick={() => onTableRowClick(lab)} key={lab.id}>
<td>{lab.type}</td>
<td>{format(new Date(lab.requestedOn), 'yyyy-MM-dd hh:mm a')}</td>
<td>{lab.status}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)
}

export default LabsTab
Loading

0 comments on commit 7bca96e

Please sign in to comment.