diff --git a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx new file mode 100644 index 0000000000..2c63aa409d --- /dev/null +++ b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx @@ -0,0 +1,256 @@ +import * as components from '@hospitalrun/components' +import format from 'date-fns/format' +import { mount } 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 NewAllergyModal from '../../../patients/allergies/NewAllergyModal' +import AddDiagnosisModal from '../../../patients/diagnoses/AddDiagnosisModal' +import ImportantPatientInfo from '../../../patients/view/ImportantPatientInfo' +import AddVisitModal from '../../../patients/visits/AddVisitModal' +import PatientRepository from '../../../shared/db/PatientRepository' +import CarePlan from '../../../shared/model/CarePlan' +import Diagnosis from '../../../shared/model/Diagnosis' +import Patient from '../../../shared/model/Patient' +import Permissions from '../../../shared/model/Permissions' +import { RootState } from '../../../shared/store' + +const mockStore = createMockStore([thunk]) + +describe('Important Patient Info Panel', () => { + let history: any + let user: any + let store: any + + const expectedPatient = { + id: '123', + sex: 'male', + fullName: 'full Name', + code: 'P-123', + dateOfBirth: format(new Date(), 'MM/dd/yyyy'), + diagnoses: [ + { id: '123', name: 'diagnosis1', diagnosisDate: new Date().toISOString() } as Diagnosis, + ], + allergies: [ + { id: '1', name: 'allergy1' }, + { id: '2', name: 'allergy2' }, + ], + carePlans: [ + { + id: '123', + title: 'title1', + description: 'test', + diagnosisId: '12345', + status: 'status' as string, + intent: 'intent' as string, + startDate: new Date().toISOString(), + endDate: new Date().toISOString(), + createdOn: new Date().toISOString(), + note: 'note', + } as CarePlan, + ], + } as Patient + + const setup = async (patient = expectedPatient, permissions: Permissions[]) => { + jest.resetAllMocks() + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + history = createMemoryHistory() + user = { permissions } + store = mockStore({ patient, user } as any) + + let wrapper: any + + await act(async () => { + wrapper = await mount( + + + + + , + ) + }) + wrapper.update() + return wrapper + } + + describe("patient's full name, patient's code, sex, and date of birth", () => { + it("should render patient's full name", async () => { + const wrapper = await setup(expectedPatient, []) + const code = wrapper.find('.col-2') + expect(code.at(0).text()).toEqual(expectedPatient.fullName) + }) + + it("should render patient's code", async () => { + const wrapper = await setup(expectedPatient, []) + const code = wrapper.find('.col-2') + expect(code.at(1).text()).toEqual(`patient.code${expectedPatient.code}`) + }) + + it("should render patient's sex", async () => { + const wrapper = await setup(expectedPatient, []) + const sex = wrapper.find('.patient-sex') + expect(sex.text()).toEqual(`patient.sex${expectedPatient.sex}`) + }) + + it("should render patient's dateOfDate", async () => { + const wrapper = await setup(expectedPatient, []) + const sex = wrapper.find('.patient-dateOfBirth') + expect(sex.text()).toEqual(`patient.dateOfBirth${expectedPatient.dateOfBirth}`) + }) + }) + + describe('add new visit button', () => { + it('should render an add visit button if user has correct permissions', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddVisit]) + + const addNewButton = wrapper.find(components.Button) + expect(addNewButton).toHaveLength(1) + expect(addNewButton.text().trim()).toEqual('patient.visits.new') + }) + + it('should open the add visit modal on click', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddVisit]) + + act(() => { + const addNewButton = wrapper.find(components.Button) + const onClick = addNewButton.prop('onClick') as any + onClick() + }) + wrapper.update() + + const modal = wrapper.find(AddVisitModal) + expect(modal.prop('show')).toBeTruthy() + }) + + it('should close the modal when the close button is clicked', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddVisit]) + + act(() => { + const addNewButton = wrapper.find(components.Button) + const onClick = addNewButton.prop('onClick') as any + onClick() + }) + wrapper.update() + + act(() => { + const modal = wrapper.find(AddVisitModal) + const onClose = modal.prop('onCloseButtonClick') as any + onClose() + }) + wrapper.update() + + expect(wrapper.find(AddVisitModal).prop('show')).toBeFalsy() + }) + + it('should not render new visit button if user does not have permissions', async () => { + const wrapper = await setup(expectedPatient, []) + + expect(wrapper.find(components.Button)).toHaveLength(0) + }) + }) + + describe('add new allergy button', () => { + it('should render an add allergy button if user has correct permissions', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddAllergy]) + + const addNewButton = wrapper.find(components.Button) + expect(addNewButton).toHaveLength(1) + expect(addNewButton.text().trim()).toEqual('patient.allergies.new') + }) + + it('should open the add allergy modal on click', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddAllergy]) + + act(() => { + const addNewButton = wrapper.find(components.Button) + const onClick = addNewButton.prop('onClick') as any + onClick() + }) + wrapper.update() + + const modal = wrapper.find(NewAllergyModal) + expect(modal.prop('show')).toBeTruthy() + }) + + it('should close the modal when the close button is clicked', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddAllergy]) + + act(() => { + const addNewButton = wrapper.find(components.Button) + const onClick = addNewButton.prop('onClick') as any + onClick() + }) + wrapper.update() + + act(() => { + const modal = wrapper.find(NewAllergyModal) + const onClose = modal.prop('onCloseButtonClick') as any + onClose() + }) + wrapper.update() + + expect(wrapper.find(NewAllergyModal).prop('show')).toBeFalsy() + }) + + it('should not render new allergy button if user does not have permissions', async () => { + const wrapper = await setup(expectedPatient, []) + + expect(wrapper.find(components.Button)).toHaveLength(0) + }) + }) + + describe('add diagnoses button', () => { + it('should render an add diagnosis button if user has correct permissions', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddDiagnosis]) + + const addNewButton = wrapper.find(components.Button) + expect(addNewButton).toHaveLength(1) + expect(addNewButton.text().trim()).toEqual('patient.diagnoses.new') + }) + + it('should open the add diagnosis modal on click', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddDiagnosis]) + + act(() => { + const addNewButton = wrapper.find(components.Button) + const onClick = addNewButton.prop('onClick') as any + onClick() + }) + wrapper.update() + + const modal = wrapper.find(AddDiagnosisModal) + expect(modal.prop('show')).toBeTruthy() + }) + + it('should close the modal when the close button is clicked', async () => { + const wrapper = await setup(expectedPatient, [Permissions.AddDiagnosis]) + + act(() => { + const addNewButton = wrapper.find(components.Button) + const onClick = addNewButton.prop('onClick') as any + onClick() + }) + wrapper.update() + + act(() => { + const modal = wrapper.find(AddDiagnosisModal) + const onClose = modal.prop('onCloseButtonClick') as any + onClose() + }) + wrapper.update() + + expect(wrapper.find(AddDiagnosisModal).prop('show')).toBeFalsy() + }) + + it('should not render new diagnosis button if user does not have permissions', async () => { + const wrapper = await setup(expectedPatient, []) + + expect(wrapper.find(components.Button)).toHaveLength(0) + }) + }) +}) diff --git a/src/patients/view/ImportantPatientInfo.tsx b/src/patients/view/ImportantPatientInfo.tsx new file mode 100644 index 0000000000..fe89793f50 --- /dev/null +++ b/src/patients/view/ImportantPatientInfo.tsx @@ -0,0 +1,234 @@ +import { Row, Table, Button, Typography } from '@hospitalrun/components' +import format from 'date-fns/format' +import React, { CSSProperties, useState } from 'react' +import { useSelector } from 'react-redux' +import { Link, useHistory } from 'react-router-dom' + +import useTranslator from '../../shared/hooks/useTranslator' +import Allergy from '../../shared/model/Allergy' +import Diagnosis from '../../shared/model/Diagnosis' +import Patient from '../../shared/model/Patient' +import Permissions from '../../shared/model/Permissions' +import { RootState } from '../../shared/store' +import NewAllergyModal from '../allergies/NewAllergyModal' +import AddCarePlanModal from '../care-plans/AddCarePlanModal' +import AddDiagnosisModal from '../diagnoses/AddDiagnosisModal' +import AddVisitModal from '../visits/AddVisitModal' + +interface Props { + patient: Patient +} + +const getPatientCode = (p: Patient): string => { + if (p) { + return p.code + } + + return '' +} + +const ImportantPatientInfo = (props: Props) => { + const { patient } = props + const { t } = useTranslator() + const history = useHistory() + const { permissions } = useSelector((state: RootState) => state.user) + const [showNewAllergyModal, setShowNewAllergyModal] = useState(false) + const [showDiagnosisModal, setShowDiagnosisModal] = useState(false) + const [showAddCarePlanModal, setShowAddCarePlanModal] = useState(false) + const [showAddVisitModal, setShowAddVisitModal] = useState(false) + + const patientCodeStyle: CSSProperties = { + position: 'relative', + color: 'black', + backgroundColor: 'rgba(245,245,245,1)', + fontSize: 'small', + textAlign: 'center', + } + + const allergiesSectionStyle: CSSProperties = { + position: 'relative', + color: 'black', + backgroundColor: 'rgba(245,245,245,1)', + fontSize: 'small', + padding: '10px', + } + + const tableStyle: CSSProperties = { + position: 'relative', + marginLeft: '2px', + marginRight: '2px', + fontSize: 'small', + } + + const addAllergyButtonStyle: CSSProperties = { + fontSize: 'small', + position: 'relative', + top: '5px', + bottom: '5px', + } + + return ( +
+ +
+

{patient.fullName}

+
+
+
+ {t('patient.code')} +
{getPatientCode(patient)}
+
+
+
+ {permissions.includes(Permissions.AddVisit) && ( + + )} +
+
+ +
+
+ {t('patient.sex')} +
{patient.sex}
+
+
+ {t('patient.dateOfBirth')} +
+ {patient.dateOfBirth + ? format(new Date(patient.dateOfBirth), 'MM/dd/yyyy') + : t('patient.unknownDateOfBirth')} +
+
+
+ +
+ {t('patient.allergies.label')} + {patient.allergies ? ( + patient.allergies?.map((a: Allergy) => ( +
  • + {a.name} +
  • + )) + ) : ( + <> + )} + {permissions.includes(Permissions.AddAllergy) && ( + + )} +
    +
    + +
    + {t('patient.diagnoses.label')} +
    + history.push(`/patients/${patient.id}/diagnoses`)} + getID={(row) => row.id} + columns={[ + { label: t('patient.diagnoses.diagnosisName'), key: 'name' }, + { + label: t('patient.diagnoses.diagnosisDate'), + key: 'diagnosisDate', + formatter: (row) => + row.diagnosisDate + ? format(new Date(row.diagnosisDate), 'yyyy-MM-dd hh:mm a') + : '', + }, + { label: t('patient.diagnoses.status'), key: 'status' }, + ]} + data={patient.diagnoses ? (patient.diagnoses as Diagnosis[]) : []} + /> + + {permissions.includes(Permissions.AddDiagnosis) && ( + + )} + +
    + {t('patient.carePlan.label')} +
    +
    history.push(`/patients/${patient.id}/care-plans/${row.id}`)} + getID={(row) => row.id} + data={patient.carePlans || []} + columns={[ + { label: t('patient.carePlan.title'), key: 'title' }, + { + label: t('patient.carePlan.startDate'), + key: 'startDate', + formatter: (row) => format(new Date(row.startDate), 'yyyy-MM-dd'), + }, + { + label: t('patient.carePlan.endDate'), + key: 'endDate', + formatter: (row) => format(new Date(row.endDate), 'yyyy-MM-dd'), + }, + { label: t('patient.carePlan.status'), key: 'status' }, + ]} + /> + + {permissions.includes(Permissions.AddCarePlan) && ( + + )} + + + + setShowNewAllergyModal(false)} + patientId={patient.id} + /> + + setShowDiagnosisModal(false)} + patient={patient} + /> + + setShowAddCarePlanModal(false)} + patient={patient} + /> + + setShowAddVisitModal(false)} + patientId={patient.id} + /> +
    + + ) +} + +export default ImportantPatientInfo diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx index 3b622bd7dd..cc905aa827 100644 --- a/src/patients/view/ViewPatient.tsx +++ b/src/patients/view/ViewPatient.tsx @@ -26,8 +26,9 @@ import usePatient from '../hooks/usePatient' import Labs from '../labs/Labs' import Note from '../notes/NoteTab' import RelatedPerson from '../related-persons/RelatedPersonTab' -import { getPatientCode, getPatientFullName } from '../util/patient-util' +import { getPatientFullName } from '../util/patient-util' import VisitTab from '../visits/VisitTab' +import ImportantPatientInfo from './ImportantPatientInfo' const ViewPatient = () => { const { t } = useTranslator() @@ -41,7 +42,7 @@ const ViewPatient = () => { const { data: patient, status } = usePatient(id) const updateTitle = useUpdateTitle() - updateTitle(`${getPatientFullName(patient)} (${getPatientCode(patient)})`) + updateTitle(t('patient.label')) const breadcrumbs = [ { i18nKey: 'patients.label', location: '/patients' }, @@ -80,90 +81,94 @@ const ViewPatient = () => { return (
    - - history.push(`/patients/${patient.id}`)} - /> - history.push(`/patients/${patient.id}/relatedpersons`)} - /> - history.push(`/patients/${patient.id}/appointments`)} - /> - history.push(`/patients/${patient.id}/allergies`)} - /> - history.push(`/patients/${patient.id}/diagnoses`)} - /> - history.push(`/patients/${patient.id}/notes`)} - /> - history.push(`/patients/${patient.id}/labs`)} - /> - history.push(`/patients/${patient.id}/care-plans`)} - /> - history.push(`/patients/${patient.id}/care-goals`)} - /> - history.push(`/patients/${patient.id}/visits`)} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {' '} + +
    + + history.push(`/patients/${patient.id}`)} + /> + history.push(`/patients/${patient.id}/relatedpersons`)} + /> + history.push(`/patients/${patient.id}/appointments`)} + /> + history.push(`/patients/${patient.id}/allergies`)} + /> + history.push(`/patients/${patient.id}/diagnoses`)} + /> + history.push(`/patients/${patient.id}/notes`)} + /> + history.push(`/patients/${patient.id}/labs`)} + /> + history.push(`/patients/${patient.id}/care-plans`)} + /> + history.push(`/patients/${patient.id}/care-goals`)} + /> + history.push(`/patients/${patient.id}/visits`)} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ) } diff --git a/src/shared/locales/enUs/translations/patient/index.ts b/src/shared/locales/enUs/translations/patient/index.ts index df9098ed62..6c16fdb9cb 100644 --- a/src/shared/locales/enUs/translations/patient/index.ts +++ b/src/shared/locales/enUs/translations/patient/index.ts @@ -1,5 +1,6 @@ export default { patient: { + label: 'Patient', code: 'Patient Code', firstName: 'First Name', lastName: 'Last Name',