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

Commit

Permalink
feat(notes): add notes to pacient
Browse files Browse the repository at this point in the history
fix #1771
  • Loading branch information
wwbarros committed Mar 12, 2020
1 parent 0639e17 commit b43ae11
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/__tests__/patients/view/ViewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import RelatedPersonTab from 'patients/related-persons/RelatedPersonTab'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import Allergies from 'patients/allergies/Allergies'
import Diagnoses from 'patients/diagnoses/Diagnoses'
import NotesTab from 'patients/notes/NoteTab'
import Patient from '../../../model/Patient'
import PatientRepository from '../../../clients/db/PatientRepository'
import * as titleUtil from '../../../page-header/useTitle'
Expand Down Expand Up @@ -127,12 +128,13 @@ describe('ViewPatient', () => {
const tabs = tabsHeader.find(Tab)
expect(tabsHeader).toHaveLength(1)

expect(tabs).toHaveLength(5)
expect(tabs).toHaveLength(6)
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')
})

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 @@ -237,4 +239,28 @@ describe('ViewPatient', () => {
expect(diagnosesTab).toHaveLength(1)
expect(diagnosesTab.prop('patient')).toEqual(patient)
})

it('should mark the notes tab as active when it is clicked and render the note component when route is /patients/:id/notes', 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(5).prop('onClick')()
})

wrapper.update()

const tabsHeader = wrapper.find(TabsHeader)
const tabs = tabsHeader.find(Tab)
const notesTab = wrapper.find(NotesTab)

expect(history.location.pathname).toEqual(`/patients/${patient.id}/notes`)
expect(tabs.at(5).prop('active')).toBeTruthy()
expect(notesTab).toHaveLength(1)
expect(notesTab.prop('patient')).toEqual(patient)
})
})
12 changes: 12 additions & 0 deletions src/locales/enUs/translations/patient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ export default {
addDiagnosisAbove: 'Add a diagnosis using the button above.',
successfullyAdded: 'Successfully added a new diagnosis!',
},
note: 'Note',
notes: {
label: 'Notes',
new: 'New Note',
warning: {
noNotes: 'No Notes',
},
error: {
noteRequired: 'Note is required.',
},
addNoteAbove: 'Add a note using the button above.',
},
types: {
charity: 'Charity',
private: 'Private',
Expand Down
1 change: 1 addition & 0 deletions src/locales/enUs/translations/patients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export default {
newPatient: 'New Patient',
successfullyCreated: 'Successfully created patient',
successfullyAddedRelatedPerson: 'Successfully added the new related person',
successfullyAddedNote: 'Successfully added the new note',
},
}
4 changes: 4 additions & 0 deletions src/model/Note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface Note {
noteDate: Date
text: string
}
2 changes: 2 additions & 0 deletions src/model/Patient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ContactInformation from './ContactInformation'
import RelatedPerson from './RelatedPerson'
import Allergy from './Allergy'
import Diagnosis from './Diagnosis'
import Note from './Note'

export default interface Patient extends AbstractDBModel, Name, ContactInformation {
sex: string
Expand All @@ -16,4 +17,5 @@ export default interface Patient extends AbstractDBModel, Name, ContactInformati
relatedPersons?: RelatedPerson[]
allergies?: Allergy[]
diagnoses?: Diagnosis[]
notes?: Note[]
}
90 changes: 90 additions & 0 deletions src/patients/notes/NewNoteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { useState } from 'react'
import { Modal, Alert, RichText, Label } from '@hospitalrun/components'
import { useTranslation } from 'react-i18next'
import Note from '../../model/Note'

interface Props {
show: boolean
toggle: () => void
onCloseButtonClick: () => void
onSave: (note: Note) => void
}

const NewNoteModal = (props: Props) => {
const { show, toggle, onCloseButtonClick, onSave } = props
const { t } = useTranslation()
const [errorMessage, setErrorMessage] = useState('')
const [note, setNote] = useState({
noteDate: new Date(),
text: '',
})

const onFieldChange = (key: string, value: string | any) => {
setNote({
...note,
[key]: value,
})
}

const onRichTextElementChange = (
event: React.KeyboardEvent<HTMLTextAreaElement>,
fieldName: string,
) => {
onFieldChange(fieldName, event)
}

const body = (
<form>
{errorMessage && <Alert color="danger" title={t('states.error')} message={errorMessage} />}
<div className="row">
<div className="col-md-12">
<div className="form-group">
<Label text={t('patient.note')} htmlFor="noteText" />
<RichText
id="noteText"
value={note.text}
onChange={(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
onRichTextElementChange(event, 'text')
}}
/>
</div>
</div>
</div>
</form>
)

return (
<Modal
show={show}
toggle={toggle}
title={t('patient.notes.new')}
body={body}
closeButton={{
children: t('actions.cancel'),
color: 'danger',
onClick: onCloseButtonClick,
}}
successButton={{
children: t('patient.notes.new'),
color: 'success',
icon: 'add',
iconLocation: 'left',
onClick: () => {
let newErrorMessage = ''

if (!note) {
newErrorMessage += `${t('patient.notes.error.noteRequired')} `
}

if (!newErrorMessage) {
onSave(note as Note)
} else {
setErrorMessage(newErrorMessage.trim())
}
},
}}
/>
)
}

export default NewNoteModal
96 changes: 96 additions & 0 deletions src/patients/notes/NoteTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* eslint-disable react/no-danger */
import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next'
import { Button, List, ListItem, Toast, Alert } from '@hospitalrun/components'
import NewNoteModal from 'patients/notes/NewNoteModal'
import Note from 'model/Note'
import Patient from 'model/Patient'
import { updatePatient } from 'patients/patient-slice'
import { RootState } from '../../store'
import Permissions from '../../model/Permissions'

interface Props {
patient: Patient
}

const NoteTab = (props: Props) => {
const dispatch = useDispatch()
const { patient } = props
const { t } = useTranslation()
const { permissions } = useSelector((state: RootState) => state.user)
const [showNewNoteModal, setShowNoteModal] = useState<boolean>(false)

const onNewNoteClick = () => {
setShowNoteModal(true)
}

const closeNewNoteModal = () => {
setShowNoteModal(false)
}

const onAddNoteSuccess = () => {
Toast('success', t('Success!'), t('patients.successfullyAddedNote'))
}

const onNoteSave = (note: Note) => {
const newNotes: Note[] = []

if (patient.notes) {
newNotes.push(...patient.notes)
}

newNotes.push(note)

const patientToUpdate = {
...patient,
notes: newNotes,
}

dispatch(updatePatient(patientToUpdate, onAddNoteSuccess))
closeNewNoteModal()
}
return (
<div>
<div className="row">
<div className="col-md-12 d-flex justify-content-end">
{permissions.includes(Permissions.WritePatients) && (
<Button
outlined
color="success"
icon="add"
iconLocation="left"
onClick={onNewNoteClick}
>
{t('patient.notes.new')}
</Button>
)}
</div>
</div>
<br />
{(!patient.notes || patient.notes.length === 0) && (
<Alert
color="warning"
title={t('patient.notes.warning.noNotes')}
message={t('patient.notes.addNoteAbove')}
/>
)}
<List>
{patient.notes?.map((a: Note) => (
<ListItem>
{new Date(a.noteDate).toLocaleString()}
<div dangerouslySetInnerHTML={{ __html: a.text }} />
</ListItem>
))}
</List>
<NewNoteModal
show={showNewNoteModal}
toggle={closeNewNoteModal}
onCloseButtonClick={closeNewNoteModal}
onSave={onNoteSave}
/>
</div>
)
}

export default NoteTab
9 changes: 9 additions & 0 deletions src/patients/view/ViewPatient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import GeneralInformation from '../GeneralInformation'
import RelatedPerson from '../related-persons/RelatedPersonTab'
import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
import AppointmentsList from '../appointments/AppointmentsList'
import Note from '../notes/NoteTab'

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

0 comments on commit b43ae11

Please sign in to comment.