diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx index 29a8ad18b7..19f45830f2 100644 --- a/src/__tests__/patients/view/ViewPatient.test.tsx +++ b/src/__tests__/patients/view/ViewPatient.test.tsx @@ -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' @@ -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 () => { @@ -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) + }) }) diff --git a/src/locales/enUs/translations/patient/index.ts b/src/locales/enUs/translations/patient/index.ts index 5605dc712a..e254277670 100644 --- a/src/locales/enUs/translations/patient/index.ts +++ b/src/locales/enUs/translations/patient/index.ts @@ -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', diff --git a/src/locales/enUs/translations/patients/index.ts b/src/locales/enUs/translations/patients/index.ts index 1efd79c4ad..19f3e7a52b 100644 --- a/src/locales/enUs/translations/patients/index.ts +++ b/src/locales/enUs/translations/patients/index.ts @@ -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', }, } diff --git a/src/model/Note.ts b/src/model/Note.ts new file mode 100644 index 0000000000..f0f12baf47 --- /dev/null +++ b/src/model/Note.ts @@ -0,0 +1,4 @@ +export default interface Note { + noteDate: Date + text: string +} diff --git a/src/model/Patient.ts b/src/model/Patient.ts index 5a16deb4aa..27fde51a46 100644 --- a/src/model/Patient.ts +++ b/src/model/Patient.ts @@ -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 @@ -16,4 +17,5 @@ export default interface Patient extends AbstractDBModel, Name, ContactInformati relatedPersons?: RelatedPerson[] allergies?: Allergy[] diagnoses?: Diagnosis[] + notes?: Note[] } diff --git a/src/patients/notes/NewNoteModal.tsx b/src/patients/notes/NewNoteModal.tsx new file mode 100644 index 0000000000..4a71545b0b --- /dev/null +++ b/src/patients/notes/NewNoteModal.tsx @@ -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, + fieldName: string, + ) => { + onFieldChange(fieldName, event) + } + + const body = ( +
+ {errorMessage && } +
+
+
+
+
+
+ + ) + + return ( + { + let newErrorMessage = '' + + if (!note) { + newErrorMessage += `${t('patient.notes.error.noteRequired')} ` + } + + if (!newErrorMessage) { + onSave(note as Note) + } else { + setErrorMessage(newErrorMessage.trim()) + } + }, + }} + /> + ) +} + +export default NewNoteModal diff --git a/src/patients/notes/NoteTab.tsx b/src/patients/notes/NoteTab.tsx new file mode 100644 index 0000000000..9865ac27e2 --- /dev/null +++ b/src/patients/notes/NoteTab.tsx @@ -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(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 ( +
+
+
+ {permissions.includes(Permissions.WritePatients) && ( + + )} +
+
+
+ {(!patient.notes || patient.notes.length === 0) && ( + + )} + + {patient.notes?.map((a: Note) => ( + + {new Date(a.noteDate).toLocaleString()} +
+ + ))} + + +
+ ) +} + +export default NoteTab diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx index dea8cf12f3..27703a5e28 100644 --- a/src/patients/view/ViewPatient.tsx +++ b/src/patients/view/ViewPatient.tsx @@ -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) { @@ -107,6 +108,11 @@ const ViewPatient = () => { label={t('patient.diagnoses.label')} onClick={() => history.push(`/patients/${patient.id}/diagnoses`)} /> + history.push(`/patients/${patient.id}/notes`)} + /> @@ -124,6 +130,9 @@ const ViewPatient = () => { + + +
)