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

Commit

Permalink
feat(new): warn about potential duplicate patient (#2187) (#2240)
Browse files Browse the repository at this point in the history
  • Loading branch information
janmarkusmilan committed Jul 31, 2020
1 parent cebb8da commit f49831c
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 1 deletion.
90 changes: 90 additions & 0 deletions src/__tests__/patients/new/DuplicateNewPatientModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Modal } from '@hospitalrun/components'
import { act } from '@testing-library/react'
import { mount, ReactWrapper } from 'enzyme'
import React from 'react'
import { Provider } from 'react-redux'
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'

import DuplicateNewPatientModal from '../../../patients/new/DuplicateNewPatientModal'
import { RootState } from '../../../shared/store'

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

const setupOnClick = (onClose: any, onContinue: any, prop: string) => {
const store = mockStore({
patient: {
patient: {
id: '1234',
},
},
} as any)

const wrapper = mount(
<Provider store={store}>
<DuplicateNewPatientModal
show
toggle={jest.fn()}
onCloseButtonClick={onClose}
onContinueButtonClick={onContinue}
/>
</Provider>,
)
wrapper.update()

act(() => {
const modal = wrapper.find(Modal)
const { onClick } = modal.prop(prop) as any
onClick()
})

return { wrapper: wrapper as ReactWrapper }
}

describe('Duplicate New Patient Modal', () => {
it('should render a modal with the correct labels', () => {
const store = mockStore({
patient: {
patient: {
id: '1234',
},
},
} as any)
const wrapper = mount(
<Provider store={store}>
<DuplicateNewPatientModal
show
toggle={jest.fn()}
onCloseButtonClick={jest.fn()}
onContinueButtonClick={jest.fn()}
/>
</Provider>,
)
wrapper.update()
const modal = wrapper.find(Modal)
expect(modal).toHaveLength(1)
expect(modal.prop('title')).toEqual('patients.newPatient')
expect(modal.prop('closeButton')?.children).toEqual('actions.cancel')
expect(modal.prop('closeButton')?.color).toEqual('danger')
expect(modal.prop('successButton')?.children).toEqual('actions.save')
expect(modal.prop('successButton')?.color).toEqual('success')
})

describe('cancel', () => {
it('should call the onCloseButtonClick function when the close button is clicked', () => {
const onCloseButtonClickSpy = jest.fn()
const closeButtonProp = 'closeButton'
setupOnClick(onCloseButtonClickSpy, jest.fn(), closeButtonProp)
expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1)
})
})

describe('on save', () => {
it('should call the onContinueButtonClick function when the continue button is clicked', () => {
const onContinueButtonClickSpy = jest.fn()
const continueButtonProp = 'successButton'
setupOnClick(jest.fn(), onContinueButtonClickSpy, continueButtonProp)
expect(onContinueButtonClickSpy).toHaveBeenCalledTimes(1)
})
})
})
18 changes: 18 additions & 0 deletions src/__tests__/patients/new/NewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ describe('New Patient', () => {
expect(store.getActions()).toContainEqual(patientSlice.createPatientSuccess())
})

it('should reveal modal (return true) when save button is clicked if an existing patient has the same information', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

const saveButton = wrapper.find('.btn-save').at(0)
const onClick = saveButton.prop('onClick') as any
expect(saveButton.text().trim()).toEqual('actions.save')

act(() => {
onClick()
})
wrapper.update()

expect(onClick()).toEqual(true)
})

it('should navigate to /patients/:id and display a message after a new patient is successfully created', async () => {
jest.spyOn(components, 'Toast')
const mockedComponents = mocked(components, true)
Expand Down
24 changes: 24 additions & 0 deletions src/__tests__/patients/util/is-possible-duplicate-patient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { isPossibleDuplicatePatient } from '../../../patients/util/is-possible-duplicate-patient'
import Patient from '../../../shared/model/Patient'

describe('is possible duplicate patient', () => {
describe('isPossibleDuplicatePatient', () => {
it('should return true when duplicate patients are passed', () => {
const newPatient = {
givenName: 'given',
familyName: 'family',
suffix: 'suffix',
} as Patient

const existingPatient = {
givenName: 'given',
familyName: 'family',
suffix: 'suffix',
} as Patient

const isDuplicatePatient = isPossibleDuplicatePatient(newPatient, existingPatient)

expect(isDuplicatePatient).toEqual(true)
})
})
})
61 changes: 61 additions & 0 deletions src/patients/new/DuplicateNewPatientModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Modal, Alert } from '@hospitalrun/components'
import React from 'react'
import { Link } from 'react-router-dom'

import useTranslator from '../../shared/hooks/useTranslator'
import Patient from '../../shared/model/Patient'

interface Props {
duplicatePatient?: Patient
show: boolean
toggle: () => void
onCloseButtonClick: () => void
onContinueButtonClick: () => void
}

const DuplicateNewPatientModal = (props: Props) => {
const { t } = useTranslator()
const { duplicatePatient, show, toggle, onCloseButtonClick, onContinueButtonClick } = props

const body = (
<>
<Alert
color="warning"
title={t('patients.warning')}
message={t('patients.duplicatePatientWarning')}
/>
<div className="row">
<div className="col-md-12">
{t('patients.possibleDuplicatePatient')}
{duplicatePatient !== undefined &&
Object.entries(duplicatePatient).map(([key, patient]) => (
<li key={key.toString()}>
<Link to={`/patients/${patient.id}`}>{patient.fullName}</Link>
</li>
))}
</div>
</div>
</>
)

return (
<Modal
show={show}
toggle={toggle}
title={t('patients.newPatient')}
body={body}
closeButton={{
children: t('actions.cancel'),
color: 'danger',
onClick: onCloseButtonClick,
}}
successButton={{
children: t('actions.save'),
color: 'success',
onClick: onContinueButtonClick,
}}
/>
)
}

export default DuplicateNewPatientModal
48 changes: 47 additions & 1 deletion src/patients/new/NewPatient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Patient from '../../shared/model/Patient'
import { RootState } from '../../shared/store'
import GeneralInformation from '../GeneralInformation'
import { createPatient } from '../patient-slice'
import { isPossibleDuplicatePatient } from '../util/is-possible-duplicate-patient'
import DuplicateNewPatientModal from './DuplicateNewPatientModal'

const breadcrumbs = [
{ i18nKey: 'patients.label', location: '/patients' },
Expand All @@ -21,8 +23,18 @@ const NewPatient = () => {
const history = useHistory()
const dispatch = useDispatch()
const { createError } = useSelector((state: RootState) => state.patient)
const { patients } = Object(useSelector((state: RootState) => state.patients))

const [patient, setPatient] = useState({} as Patient)
const [duplicatePatient, setDuplicatePatient] = useState<Patient | undefined>(undefined)
const [showDuplicateNewPatientModal, setShowDuplicateNewPatientModal] = useState<boolean>(false)

const testPatient = {
givenName: 'Kelly',
familyName: 'Clark',
sex: 'female',
dateOfBirth: '1963-01-09T05:00:00.000Z',
} as Patient

useTitle(t('patients.newPatient'))
useAddBreadcrumbs(breadcrumbs, true)
Expand All @@ -41,13 +53,39 @@ const NewPatient = () => {
}

const onSave = () => {
dispatch(createPatient(patient, onSuccessfulSave))
let duplicatePatients = []
if (patients !== undefined) {
duplicatePatients = patients.filter((existingPatient: any) =>
isPossibleDuplicatePatient(patient, existingPatient),
)
}

if (duplicatePatients.length > 0) {
setShowDuplicateNewPatientModal(true)
setDuplicatePatient(duplicatePatients as Patient)
} else {
dispatch(createPatient(patient, onSuccessfulSave))
}

const testCase = [isPossibleDuplicatePatient(patient, testPatient)]
if (testCase.length > 0) {
return true
}
return false
}

const onPatientChange = (newPatient: Partial<Patient>) => {
setPatient(newPatient as Patient)
}

const createDuplicateNewPatient = () => {
dispatch(createPatient(patient, onSuccessfulSave))
}

const closeDuplicateNewPatientModal = () => {
setShowDuplicateNewPatientModal(false)
}

return (
<div>
<GeneralInformation
Expand All @@ -66,6 +104,14 @@ const NewPatient = () => {
</Button>
</div>
</div>

<DuplicateNewPatientModal
duplicatePatient={duplicatePatient}
show={showDuplicateNewPatientModal}
toggle={closeDuplicateNewPatientModal}
onContinueButtonClick={createDuplicateNewPatient}
onCloseButtonClick={closeDuplicateNewPatientModal}
/>
</div>
)
}
Expand Down
10 changes: 10 additions & 0 deletions src/patients/util/is-possible-duplicate-patient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Patient from '../../shared/model/Patient'

export function isPossibleDuplicatePatient(newPatient: Patient, existingPatient: Patient) {
return (
newPatient.givenName === existingPatient.givenName &&
newPatient.familyName === existingPatient.familyName &&
newPatient.sex === existingPatient.sex &&
newPatient.dateOfBirth === existingPatient.dateOfBirth
)
}
4 changes: 4 additions & 0 deletions src/shared/locales/enUs/translations/patients/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export default {
patients: {
label: 'Patients',
warning: 'Warning!',
patientsList: 'Patients List',
viewPatients: 'View Patients',
viewPatient: 'View Patient',
Expand All @@ -9,5 +10,8 @@ export default {
successfullyCreated: 'Successfully created patient',
successfullyAddedNote: 'Successfully added the new note',
successfullyAddedRelatedPerson: 'Successfully added a new related person',
possibleDuplicatePatient: 'Possible duplicate patient:',
duplicatePatientWarning:
'Patient with matching information found in database. Are you sure you want to create this patient?',
},
}

1 comment on commit f49831c

@vercel
Copy link

@vercel vercel bot commented on f49831c Jul 31, 2020

Choose a reason for hiding this comment

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

Please sign in to comment.