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

feat(viewpatient): added labs tab to ViewPatient #1987

Merged
merged 11 commits into from
May 3, 2020
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dependencies": {
"@hospitalrun/components": "^1.4.0",
"@reduxjs/toolkit": "~1.3.0",
"@types/escape-string-regexp": "~2.0.1",
"@types/pouchdb-find": "~6.3.4",
"bootstrap": "~4.4.1",
"date-fns": "~2.12.0",
Expand Down
593 changes: 593 additions & 0 deletions report.20200502.130727.1352.0.001.json

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions src/__tests__/patients/labs/LabsTab.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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 { act } from 'react-dom/test-utils'
import LabsTab from '../../../patients/labs/LabsTab'
import Patient from '../../../model/Patient'
import Lab from '../../../model/Lab'
import Permissions from '../../../model/Permissions'
import LabRepository from '../../../clients/db/LabRepository'

const expectedPatient = {
id: '123',
} as Patient

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

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 })
jest.spyOn(LabRepository, 'findAllByPatientId').mockResolvedValue(labs)
const wrapper = mount(
<Router history={history}>
<Provider store={store}>
<LabsTab patientId={patient.id} />
</Provider>
</Router>,
)

return wrapper
}

describe('Labs Tab', () => {
it('should list the patients labs', async () => {
const expectedLabs = labs
let wrapper: any
await act(async () => {
wrapper = await setup()
})
wrapper.update()

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(expectedLabs[0].type)
expect(tableData.at(1).text()).toEqual(
format(new Date(expectedLabs[0].requestedOn), 'yyyy-MM-dd hh:mm a'),
)
expect(tableData.at(2).text()).toEqual(expectedLabs[0].status)
})

it('should render a warning message if the patient does not have any labs', async () => {
let wrapper: any

await act(async () => {
wrapper = await setup({ ...expectedPatient })
})

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')
})
})
31 changes: 29 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 LabRepository from '../../../clients/db/LabRepository'

const mockStore = configureMockStore([thunk])

Expand All @@ -47,9 +49,9 @@ describe('ViewPatient', () => {

const setup = (permissions = [Permissions.ReadPatients]) => {
jest.spyOn(PatientRepository, 'find')
jest.spyOn(LabRepository, 'findAllByPatientId').mockResolvedValue([])
const mockedPatientRepository = mocked(PatientRepository, true)
mockedPatientRepository.find.mockResolvedValue(patient)

history = createMemoryHistory()
store = mockStore({
patient: { patient },
Expand Down Expand Up @@ -127,13 +129,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 +265,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('patientId')).toEqual(patient.id)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import AppointmentDetailForm from 'scheduling/appointments/AppointmentDetailForm
import * as components from '@hospitalrun/components'
import * as titleUtil from '../../../../page-header/useTitle'
import * as appointmentSlice from '../../../../scheduling/appointments/appointment-slice'
import LabRepository from '../../../../clients/db/LabRepository'
import Lab from '../../../../model/Lab'

const mockStore = configureMockStore([thunk])
const mockedComponents = mocked(components, true)
Expand All @@ -32,6 +34,7 @@ describe('New Appointment', () => {
mocked(AppointmentRepository, true).save.mockResolvedValue(
expectedNewAppointment as Appointment,
)
jest.spyOn(LabRepository, 'findAllByPatientId').mockResolvedValue([] as Lab[])

history = createMemoryHistory()
store = mockStore({
Expand Down
12 changes: 12 additions & 0 deletions src/clients/db/LabRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ export class LabRepository extends Repository<Lab> {
index: { fields: ['requestedOn'] },
})
}

async findAllByPatientId(patientId: string): Promise<Lab[]> {
return super.search({
selector: {
$and: [
{
patientId,
},
],
},
})
}
}

export default new LabRepository()
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 @@ -85,6 +85,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
66 changes: 66 additions & 0 deletions src/patients/labs/LabsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useEffect, useState } 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 LabRepository from '../../clients/db/LabRepository'

interface Props {
patientId: string
}

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

const [labs, setLabs] = useState<Lab[]>([])

useEffect(() => {
const fetch = async () => {
const fetchedLabs = await LabRepository.findAllByPatientId(patientId)
setLabs(fetchedLabs)
}

fetch()
}, [patientId])

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

return (
<div>
{(!labs || labs.length === 0) && (
<Alert
color="warning"
title={t('patient.labs.warning.noLabs')}
message={t('patient.labs.noLabsMessage')}
/>
)}
{labs && 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>
{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
9 changes: 9 additions & 0 deletions src/patients/view/ViewPatient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import RelatedPerson from '../related-persons/RelatedPersonTab'
import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
import AppointmentsList from '../appointments/AppointmentsList'
import Note from '../notes/NoteTab'
import Labs from '../labs/LabsTab'

const getPatientCode = (p: Patient): string => {
if (p) {
Expand Down Expand Up @@ -113,6 +114,11 @@ const ViewPatient = () => {
label={t('patient.notes.label')}
onClick={() => history.push(`/patients/${patient.id}/notes`)}
/>
<Tab
active={location.pathname === `/patients/${patient.id}/labs`}
label={t('patient.labs.label')}
onClick={() => history.push(`/patients/${patient.id}/labs`)}
/>
</TabsHeader>
<Panel>
<Route exact path="/patients/:id">
Expand All @@ -133,6 +139,9 @@ const ViewPatient = () => {
<Route exact path="/patients/:id/notes">
<Note patient={patient} />
</Route>
<Route exact path="/patients/:id/labs">
<Labs patientId={patient.id} />
</Route>
</Panel>
</div>
)
Expand Down