From b4401e543e3147fd2b47258447c45b205376e621 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Fri, 6 Nov 2020 09:45:01 -0800 Subject: [PATCH 01/38] feat(lab): link visit to labs Add functionality to link visit to lab, and updated the schema to have a visitId in the Lab model. fix #2184 --- .../labs/requests/NewLabRequest.test.tsx | 10 +++ src/labs/requests/NewLabRequest.tsx | 75 +++++++++++++++---- src/shared/model/Lab.ts | 1 + 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/src/__tests__/labs/requests/NewLabRequest.test.tsx b/src/__tests__/labs/requests/NewLabRequest.test.tsx index f97dd730fe..b7a72382dd 100644 --- a/src/__tests__/labs/requests/NewLabRequest.test.tsx +++ b/src/__tests__/labs/requests/NewLabRequest.test.tsx @@ -12,6 +12,7 @@ import NewLabRequest from '../../../labs/requests/NewLabRequest' import * as validationUtil from '../../../labs/utils/validate-lab' import { LabError } from '../../../labs/utils/validate-lab' import * as titleUtil from '../../../page-header/title/TitleContext' +import SelectWithLabelFormGroup from '../../../shared/components/input/SelectWithLabelFormGroup' import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' import LabRepository from '../../../shared/db/LabRepository' @@ -90,6 +91,15 @@ describe('New Lab Request', () => { expect(notesTextField.prop('isEditable')).toBeTruthy() }) + it('should render a visit select', async () => { + const { wrapper } = await setup() + const visitSelect = wrapper.find(SelectWithLabelFormGroup) + + expect(visitSelect).toBeDefined() + expect(visitSelect.prop('label') as string).toEqual('patient.visit') + expect(visitSelect.prop('isRequired')).toBeTruthy() + }) + it('should render a save button', async () => { const { wrapper } = await setup() const saveButton = wrapper.find(Button).at(0) diff --git a/src/labs/requests/NewLabRequest.tsx b/src/labs/requests/NewLabRequest.tsx index d28bcead71..5543f4395b 100644 --- a/src/labs/requests/NewLabRequest.tsx +++ b/src/labs/requests/NewLabRequest.tsx @@ -1,10 +1,14 @@ -import { Typeahead, Label, Button, Alert, Toast } from '@hospitalrun/components' +import { Typeahead, Label, Button, Alert, Toast, Column, Row } from '@hospitalrun/components' +import format from 'date-fns/format' import React, { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' +import SelectWithLabelFormGroup, { + Option, +} from '../../shared/components/input/SelectWithLabelFormGroup' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup' import PatientRepository from '../../shared/db/PatientRepository' @@ -22,6 +26,8 @@ const NewLabRequest = () => { const [mutate] = useRequestLab() const [newNote, setNewNote] = useState('') const [error, setError] = useState(undefined) + const [visitOptions, setVisitOptions] = useState([] as Option[]) + const updateTitle = useUpdateTitle() useEffect(() => { updateTitle(t('labs.requests.new')) @@ -31,6 +37,8 @@ const NewLabRequest = () => { type: '', status: 'requested', requestedBy: user?.id || '', + requestedOn: '', + visitId: '', }) const breadcrumbs = [ @@ -42,6 +50,12 @@ const NewLabRequest = () => { useAddBreadcrumbs(breadcrumbs) const onPatientChange = (patient: Patient) => { + const visits = patient.visits?.map((v) => ({ + label: `${v.type} at ${format(new Date(v.startDateTime), 'yyyy-MM-dd hh:mm a')}`, + value: v.id, + })) as Option[] + setVisitOptions(visits) + setNewLabRequest((previousNewLabRequest) => ({ ...previousNewLabRequest, patient: patient.id, @@ -83,27 +97,60 @@ const NewLabRequest = () => { } } + const onVisitChange = (value: string) => { + setNewLabRequest((previousNewLabRequest) => ({ + ...previousNewLabRequest, + visitId: value, + })) + } + const onCancel = () => { history.push('/labs') } + const defaultSelectedVisitsOption = () => { + if (visitOptions !== undefined) { + return visitOptions.filter(({ value }) => value === newLabRequest.visitId) + } + return [] + } + return ( <> {error && }
-
-
+ + +
+
+
+ +
+ { + onVisitChange(values[0]) + }} + /> +
+
+
Date: Mon, 14 Dec 2020 16:18:22 -0500 Subject: [PATCH 02/38] feat(incident): add ability to document notes --- src/incidents/Incidents.tsx | 1 - src/incidents/view/ViewIncident.tsx | 65 +++++++++++++++++++- src/incidents/view/ViewIncidentDetails.tsx | 71 +++++----------------- src/shared/model/Incident.ts | 3 +- 4 files changed, 80 insertions(+), 60 deletions(-) diff --git a/src/incidents/Incidents.tsx b/src/incidents/Incidents.tsx index 2dcd39be90..9791308c3f 100644 --- a/src/incidents/Incidents.tsx +++ b/src/incidents/Incidents.tsx @@ -43,7 +43,6 @@ const Incidents = () => { /> diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index faa3a99146..2d32b40ad5 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -1,16 +1,24 @@ +import { Button, Tab, Panel, TabsHeader } from '@hospitalrun/components' import React from 'react' import { useSelector } from 'react-redux' -import { useParams } from 'react-router-dom' +import { useParams, useHistory, useLocation, Route } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' import useTranslator from '../../shared/hooks/useTranslator' import { RootState } from '../../shared/store' +import Permissions from '../../shared/model/Permissions' import ViewIncidentDetails from './ViewIncidentDetails' +import useIncident from '../hooks/useIncident' +import useResolveIncident from '../hooks/useResolveIncident' const ViewIncident = () => { - const { id } = useParams() + const { id } = useParams() as any const { permissions } = useSelector((root: RootState) => root.user) + const { data, isLoading } = useIncident(id) + const [mutate] = useResolveIncident() + const location = useLocation() + const history = useHistory() const { t } = useTranslator() const updateTitle = useUpdateTitle() updateTitle(t('incidents.reports.view')) @@ -25,7 +33,58 @@ const ViewIncident = () => { return <> } - return + const onResolve = async () => { + await mutate(data) + history.push('/incidents') + } + + + return ( +
+ + + history.push(`/incidents/${id}/notes`)} + /> + + + + + + + { + data && + data.resolvedOn === undefined && + data.status !== 'resolved' && + permissions.includes(Permissions.ResolveIncident) && + (
+
+ +
+
) + } + +
+ + ) + } export default ViewIncident diff --git a/src/incidents/view/ViewIncidentDetails.tsx b/src/incidents/view/ViewIncidentDetails.tsx index 56f8690c29..bf8eedcdf8 100644 --- a/src/incidents/view/ViewIncidentDetails.tsx +++ b/src/incidents/view/ViewIncidentDetails.tsx @@ -1,66 +1,32 @@ -import { Button, Column, Row, Spinner } from '@hospitalrun/components' +import { Column, Row, Spinner } from '@hospitalrun/components' import format from 'date-fns/format' import React from 'react' -import { useHistory } from 'react-router' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup' import useTranslator from '../../shared/hooks/useTranslator' -import Permissions from '../../shared/model/Permissions' +import Incident from '../../shared/model/Incident' import { extractUsername } from '../../shared/util/extractUsername' -import useIncident from '../hooks/useIncident' -import useResolveIncident from '../hooks/useResolveIncident' - interface Props { - incidentId: string - permissions: (Permissions | null)[] + incident?: Incident + isLoading: boolean } function ViewIncidentDetails(props: Props) { - const { incidentId, permissions } = props - const history = useHistory() + const { incident, isLoading } = props const { t } = useTranslator() - const { data, isLoading } = useIncident(incidentId) - const [mutate] = useResolveIncident() - if (data === undefined || isLoading) { + if (incident === undefined || isLoading) { return } - const onResolve = async () => { - await mutate(data) - history.push('/incidents') - } - - const getButtons = () => { - const buttons: React.ReactNode[] = [] - if (data.status === 'resolved') { - return buttons - } - - if (permissions.includes(Permissions.ResolveIncident)) { - buttons.push( - , - ) - } - - return buttons - } - const getResolvedOnDate = () => { - if (data.status === 'resolved' && data.resolvedOn) { + if (incident.status === 'resolved' && incident.resolvedOn) { return (

{t('incidents.reports.resolvedOn')}

-
{format(new Date(data.resolvedOn), 'yyyy-MM-dd hh:mm a')}
+
{format(new Date(incident.resolvedOn), 'yyyy-MM-dd hh:mm a')}
) @@ -74,25 +40,25 @@ function ViewIncidentDetails(props: Props) {

{t('incidents.reports.dateOfIncident')}

-
{format(new Date(data.date || ''), 'yyyy-MM-dd hh:mm a')}
+
{format(new Date(incident.date || ''), 'yyyy-MM-dd hh:mm a')}

{t('incidents.reports.status')}

-
{data.status}
+
{incident.status}

{t('incidents.reports.reportedBy')}

-
{extractUsername(data.reportedBy)}
+
{extractUsername(incident.reportedBy)}

{t('incidents.reports.reportedOn')}

-
{format(new Date(data.reportedOn || ''), 'yyyy-MM-dd hh:mm a')}
+
{format(new Date(incident.reportedOn || ''), 'yyyy-MM-dd hh:mm a')}
{getResolvedOnDate()} @@ -103,7 +69,7 @@ function ViewIncidentDetails(props: Props) { @@ -112,14 +78,14 @@ function ViewIncidentDetails(props: Props) { @@ -128,15 +94,10 @@ function ViewIncidentDetails(props: Props) { - {data.resolvedOn === undefined && ( -
-
{getButtons()}
-
- )} ) } diff --git a/src/shared/model/Incident.ts b/src/shared/model/Incident.ts index 9487835de2..de8078de82 100644 --- a/src/shared/model/Incident.ts +++ b/src/shared/model/Incident.ts @@ -1,5 +1,5 @@ import AbstractDBModel from './AbstractDBModel' - +import Note from './Note' export default interface Incident extends AbstractDBModel { reportedBy: string reportedOn: string @@ -11,4 +11,5 @@ export default interface Incident extends AbstractDBModel { description: string status: 'reported' | 'resolved' resolvedOn: string + notes?: Note[] } From e214866aef4efaef44bc1b966766a4dd4bfba2e7 Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Thu, 17 Dec 2020 17:19:55 -0800 Subject: [PATCH 03/38] feat(incident):added incident note table --- src/incidents/view/NotesTable.tsx | 56 ++++++++++++++++++++++++++ src/incidents/view/ViewIncident.tsx | 61 ++++++++++++----------------- src/shared/model/Note.ts | 3 +- 3 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 src/incidents/view/NotesTable.tsx diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx new file mode 100644 index 0000000000..ef0a71631b --- /dev/null +++ b/src/incidents/view/NotesTable.tsx @@ -0,0 +1,56 @@ +import React from 'react' + +import { Table } from '@hospitalrun/components' + +const NotesTable = () => { + return ( + <>{new Date(row.date).toLocaleDateString()}, + }, + { + key: 'given-by-key', + label: 'Given By', + formatter: (row) => <>{row.givenBy}, + }, + { + key: 'note-key', + label: 'Note', + formatter: (row) => <>{row.text}, + }, + { + key: 'actions-key', + label: 'Actions', + formatter: () => { + return ( + <> + + + + ) + }, + }, + ]} + data={[ + { + id: 'agupta07', + date: '2020-12-18T00:34:51.414Z', + text: 'Vaccine Failed', + givenBy: 'Dr. Gupta', + }, + { + id: 'drewgreg', + date: '2020-12-17T00:34:51.414Z', + text: 'Vaccine Success!', + givenBy: 'Dr. Gregory', + }, + ]} + getID={(r) => r.id} + /> + ) +} + +export default NotesTable diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 2d32b40ad5..101ae5cfa9 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -1,7 +1,7 @@ import { Button, Tab, Panel, TabsHeader } from '@hospitalrun/components' import React from 'react' import { useSelector } from 'react-redux' -import { useParams, useHistory, useLocation, Route } from 'react-router-dom' +import { useParams, useHistory, useLocation } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' @@ -11,6 +11,7 @@ import Permissions from '../../shared/model/Permissions' import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' +import NotesTable from './NotesTable' const ViewIncident = () => { const { id } = useParams() as any @@ -38,53 +39,43 @@ const ViewIncident = () => { history.push('/incidents') } - return (
history.push(`/incidents/${id}/notes`)} + active={location.pathname === `/incidents/${id}/notes`} + label={t('patient.notes.label')} + onClick={() => history.push(`/incidents/${id}/notes`)} /> - - - +
+ +
+
- { - data && + {data && data.resolvedOn === undefined && - data.status !== 'resolved' && - permissions.includes(Permissions.ResolveIncident) && - (
-
- + data.status !== 'resolved' && + permissions.includes(Permissions.ResolveIncident) && ( +
+
+ +
-
) - } - + )}
- ) - } export default ViewIncident diff --git a/src/shared/model/Note.ts b/src/shared/model/Note.ts index 4a4a988526..b87c43dfe8 100644 --- a/src/shared/model/Note.ts +++ b/src/shared/model/Note.ts @@ -1,5 +1,6 @@ export default interface Note { id: string date: string - text: string + text: string, + givenBy?: string, } From 87cf4fb0190df9747ee3840434ea7176101b98fc Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Thu, 17 Dec 2020 18:11:28 -0800 Subject: [PATCH 04/38] started new note modal work --- src/incidents/view/ViewIncident.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 101ae5cfa9..958b3194a1 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -1,5 +1,5 @@ import { Button, Tab, Panel, TabsHeader } from '@hospitalrun/components' -import React from 'react' +import React, { useState } from 'react' import { useSelector } from 'react-redux' import { useParams, useHistory, useLocation } from 'react-router-dom' @@ -12,6 +12,7 @@ import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' import NotesTable from './NotesTable' +import NewNoteModal from '../../patients/notes/NewNoteModal' const ViewIncident = () => { const { id } = useParams() as any @@ -30,6 +31,15 @@ const ViewIncident = () => { }, ]) + //New Note Modal + const [showNewNoteModal, setShowNoteModal] = useState(false) + const onNewNoteClick = () => { + setShowNoteModal(true) + } + const closeNewNoteModal = () => { + setShowNoteModal(false) + } + if (id === undefined || permissions === undefined) { return <> } @@ -51,7 +61,7 @@ const ViewIncident = () => {
-
@@ -74,6 +84,13 @@ const ViewIncident = () => {
)} + + ) } From 1033f16ddcf9b1b219b158deddd4c10366c09c3d Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Thu, 17 Dec 2020 21:32:02 -0500 Subject: [PATCH 05/38] feat(incident): note modal generalizations --- src/incidents/view/ViewIncident.tsx | 5 ++- src/patients/hooks/useAddPatientNote.tsx | 1 - .../notes/NewNoteModal.tsx | 31 ++++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) rename src/{patients => shared}/notes/NewNoteModal.tsx (79%) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 958b3194a1..5b47bb5de2 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -12,7 +12,6 @@ import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' import NotesTable from './NotesTable' -import NewNoteModal from '../../patients/notes/NewNoteModal' const ViewIncident = () => { const { id } = useParams() as any @@ -85,12 +84,12 @@ const ViewIncident = () => { )} - + /> */} ) } diff --git a/src/patients/hooks/useAddPatientNote.tsx b/src/patients/hooks/useAddPatientNote.tsx index 01f301cc8b..430ccb63a8 100644 --- a/src/patients/hooks/useAddPatientNote.tsx +++ b/src/patients/hooks/useAddPatientNote.tsx @@ -3,7 +3,6 @@ import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' import Note from '../../shared/model/Note' -import { uuid } from '../../shared/util/uuid' import validateNote from '../util/validate-note' interface AddNoteRequest { diff --git a/src/patients/notes/NewNoteModal.tsx b/src/shared/notes/NewNoteModal.tsx similarity index 79% rename from src/patients/notes/NewNoteModal.tsx rename to src/shared/notes/NewNoteModal.tsx index 93885a6475..bd2a36afa9 100644 --- a/src/patients/notes/NewNoteModal.tsx +++ b/src/shared/notes/NewNoteModal.tsx @@ -3,37 +3,40 @@ import React, { useState } from 'react' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import useTranslator from '../../shared/hooks/useTranslator' -import useAddPatientNote from '../hooks/useAddPatientNote' +import { uuid } from '../../shared/util/uuid' +import Note from '../../shared/model/Note' import { NoteError } from '../util/validate-note' interface Props { show: boolean toggle: () => void onCloseButtonClick: () => void - patientId: string + onSave: (note: Note) => void } -const initialNoteState = { text: '', date: new Date().toISOString() } const NewNoteModal = (props: Props) => { - const { show, toggle, onCloseButtonClick, patientId } = props + + + + const { show, toggle, onCloseButtonClick, onSave } = props const { t } = useTranslator() - const [mutate] = useAddPatientNote() const [noteError, setNoteError] = useState(undefined) - const [note, setNote] = useState(initialNoteState) + const [text, setText] = useState('') const onNoteTextChange = (event: React.ChangeEvent) => { - const text = event.currentTarget.value - setNote({ - ...note, - text, - }) + setText(event.currentTarget.value) } const onSaveButtonClick = async () => { try { - await mutate({ patientId, note }) - setNote(initialNoteState) + onSave({ + id: uuid(), + date: new Date().toISOString(), + // TODO: Implement givenBy + text, + }) + setText('') onCloseButtonClick() } catch (e) { setNoteError(e) @@ -57,7 +60,7 @@ const NewNoteModal = (props: Props) => { isRequired name="noteTextField" label={t('patient.note')} - value={note.text} + value={text} isInvalid={!!noteError?.noteError} feedback={t(noteError?.noteError || '')} onChange={onNoteTextChange} From 296e42cc4a4d8598a2470c34f36eab05d1da3108 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Thu, 17 Dec 2020 21:32:49 -0500 Subject: [PATCH 06/38] feat(incident): note modal generalizations --- src/shared/notes/NewNoteModal.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/shared/notes/NewNoteModal.tsx b/src/shared/notes/NewNoteModal.tsx index bd2a36afa9..82e4ed9e93 100644 --- a/src/shared/notes/NewNoteModal.tsx +++ b/src/shared/notes/NewNoteModal.tsx @@ -1,11 +1,11 @@ import { Modal, Alert } from '@hospitalrun/components' import React, { useState } from 'react' -import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' -import useTranslator from '../../shared/hooks/useTranslator' -import { uuid } from '../../shared/util/uuid' -import Note from '../../shared/model/Note' -import { NoteError } from '../util/validate-note' +import TextFieldWithLabelFormGroup from '../components/input/TextFieldWithLabelFormGroup' +import useTranslator from '../hooks/useTranslator' +import { uuid } from '../util/uuid' +import Note from '../model/Note' +import { NoteError } from '../../patients/util/validate-note' interface Props { show: boolean @@ -16,8 +16,6 @@ interface Props { const NewNoteModal = (props: Props) => { - - const { show, toggle, onCloseButtonClick, onSave } = props const { t } = useTranslator() From 558f379ff4872ab2b5175477e2b2adb99035b071 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Mon, 21 Dec 2020 18:42:51 -0500 Subject: [PATCH 07/38] finish NewNoteModal refactor, begin react query for incident notes --- .../patients/notes/NewNoteModal.test.tsx | 2 +- src/incidents/hooks/useAddIncidentNote.tsx | 39 +++++++++++++++++++ src/incidents/view/ViewIncident.tsx | 9 +++-- src/patients/hooks/useAddPatientNote.tsx | 8 +--- src/patients/notes/NoteTab.tsx | 8 +++- src/patients/notes/NotesList.tsx | 3 -- 6 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 src/incidents/hooks/useAddIncidentNote.tsx diff --git a/src/__tests__/patients/notes/NewNoteModal.test.tsx b/src/__tests__/patients/notes/NewNoteModal.test.tsx index 59cf0c35a9..d60e85e0a4 100644 --- a/src/__tests__/patients/notes/NewNoteModal.test.tsx +++ b/src/__tests__/patients/notes/NewNoteModal.test.tsx @@ -5,7 +5,7 @@ import { act } from '@testing-library/react' import { mount } from 'enzyme' import React from 'react' -import NewNoteModal from '../../../patients/notes/NewNoteModal' +import NewNoteModal from '../../../shared/notes/NewNoteModal' import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx new file mode 100644 index 0000000000..752d7834e1 --- /dev/null +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -0,0 +1,39 @@ +import { isEmpty } from 'lodash' +import { queryCache, useMutation } from 'react-query' + +import PatientRepository from '../../shared/db/PatientRepository' +import Note from '../../shared/model/Note' +import validateNote from '../util/validate-note' + +interface AddNoteRequest { + patientId: string + note: Note +} + +async function addNote(request: AddNoteRequest): Promise { + const error = validateNote(request.note) + + if (isEmpty(error)) { + const patient = await PatientRepository.find(request.patientId) + const notes = patient.notes ? [...patient.notes] : [] + notes.push(request.note) + + await PatientRepository.saveOrUpdate({ + ...patient, + notes, + }) + + return notes + } + + throw error +} + +export default function useAddPatientNote() { + return useMutation(addNote, { + onSuccess: async (data, variables) => { + await queryCache.setQueryData(['notes', variables.patientId], data) + }, + throwOnError: true, + }) +} diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 5b47bb5de2..fdec97dbc4 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -12,6 +12,7 @@ import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' import NotesTable from './NotesTable' +import NewNoteModal from '../../shared/notes/NewNoteModal' const ViewIncident = () => { const { id } = useParams() as any @@ -60,7 +61,7 @@ const ViewIncident = () => {
-
@@ -84,12 +85,12 @@ const ViewIncident = () => { )} - {/* */} + onSave={() => { /* TODO */}} + /> ) } diff --git a/src/patients/hooks/useAddPatientNote.tsx b/src/patients/hooks/useAddPatientNote.tsx index 430ccb63a8..752d7834e1 100644 --- a/src/patients/hooks/useAddPatientNote.tsx +++ b/src/patients/hooks/useAddPatientNote.tsx @@ -7,7 +7,7 @@ import validateNote from '../util/validate-note' interface AddNoteRequest { patientId: string - note: Omit + note: Note } async function addNote(request: AddNoteRequest): Promise { @@ -16,11 +16,7 @@ async function addNote(request: AddNoteRequest): Promise { if (isEmpty(error)) { const patient = await PatientRepository.find(request.patientId) const notes = patient.notes ? [...patient.notes] : [] - const newNote: Note = { - id: uuid(), - ...request.note, - } - notes.push(newNote) + notes.push(request.note) await PatientRepository.saveOrUpdate({ ...patient, diff --git a/src/patients/notes/NoteTab.tsx b/src/patients/notes/NoteTab.tsx index 31235a2eb5..aba88bd8fc 100644 --- a/src/patients/notes/NoteTab.tsx +++ b/src/patients/notes/NoteTab.tsx @@ -8,7 +8,8 @@ import useTranslator from '../../shared/hooks/useTranslator' import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' -import NewNoteModal from './NewNoteModal' +import NewNoteModal from '../../shared/notes/NewNoteModal' +import useAddPatientNote from '../hooks/useAddPatientNote' import NotesList from './NotesList' import ViewNote from './ViewNote' @@ -21,6 +22,7 @@ const NoteTab = (props: Props) => { const { t } = useTranslator() const { permissions } = useSelector((state: RootState) => state.user) const [showNewNoteModal, setShowNoteModal] = useState(false) + const [ mutate ] = useAddPatientNote() const breadcrumbs = [ { @@ -69,7 +71,9 @@ const NoteTab = (props: Props) => { show={showNewNoteModal} toggle={closeNewNoteModal} onCloseButtonClick={closeNewNoteModal} - patientId={patient.id} + onSave={async (note) => { + await mutate({ note, patientId: patient.id }) + }} /> ) diff --git a/src/patients/notes/NotesList.tsx b/src/patients/notes/NotesList.tsx index cd62c0df3f..e6e7c18214 100644 --- a/src/patients/notes/NotesList.tsx +++ b/src/patients/notes/NotesList.tsx @@ -1,6 +1,5 @@ import { Alert, List, ListItem } from '@hospitalrun/components' import React from 'react' -import { useHistory } from 'react-router-dom' import Loading from '../../shared/components/Loading' import useTranslator from '../../shared/hooks/useTranslator' @@ -13,7 +12,6 @@ interface Props { const NotesList = (props: Props) => { const { patientId } = props - const history = useHistory() const { t } = useTranslator() const { data, status } = usePatientNotes(patientId) @@ -37,7 +35,6 @@ const NotesList = (props: Props) => { history.push(`/patients/${patientId}/notes/${note.id}`)} >

{new Date(note.date).toLocaleString()}

{note.text}

From 06954fb23a545c19322f5bbe8c042f3ca4ce103a Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Tue, 22 Dec 2020 23:52:39 -0500 Subject: [PATCH 08/38] feat(incident): use react-query to save/view notes --- src/incidents/hooks/useAddIncidentNote.tsx | 24 ++++++++++++---------- src/incidents/view/NotesTable.tsx | 21 ++++++------------- src/incidents/view/ViewIncident.tsx | 6 ++++-- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx index 752d7834e1..51cb008b33 100644 --- a/src/incidents/hooks/useAddIncidentNote.tsx +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -1,26 +1,28 @@ import { isEmpty } from 'lodash' import { queryCache, useMutation } from 'react-query' -import PatientRepository from '../../shared/db/PatientRepository' +import IncidentRepository from '../../shared/db/IncidentRepository' import Note from '../../shared/model/Note' -import validateNote from '../util/validate-note' +// import validateNote from '../util/validate-note' interface AddNoteRequest { - patientId: string + incidentId: string note: Note } async function addNote(request: AddNoteRequest): Promise { - const error = validateNote(request.note) + const error = [] as any // TODO validateNote(request.note) if (isEmpty(error)) { - const patient = await PatientRepository.find(request.patientId) - const notes = patient.notes ? [...patient.notes] : [] + const incident = await IncidentRepository.find(request.incidentId) + + const notes = incident.notes ? [...incident.notes] : [] + notes.push(request.note) - await PatientRepository.saveOrUpdate({ - ...patient, - notes, + await IncidentRepository.saveOrUpdate({ + ...incident, + notes }) return notes @@ -29,10 +31,10 @@ async function addNote(request: AddNoteRequest): Promise { throw error } -export default function useAddPatientNote() { +export default function useAddIncidentNote() { return useMutation(addNote, { onSuccess: async (data, variables) => { - await queryCache.setQueryData(['notes', variables.patientId], data) + await queryCache.setQueryData(['notes', variables.incidentId], data) }, throwOnError: true, }) diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx index ef0a71631b..406d958819 100644 --- a/src/incidents/view/NotesTable.tsx +++ b/src/incidents/view/NotesTable.tsx @@ -1,8 +1,12 @@ import React from 'react' import { Table } from '@hospitalrun/components' +import Note from '../../shared/model/Note' +interface Props { + notes: Note[] +} -const NotesTable = () => { +const NotesTable = ({ notes }: Props) => { return (
{ }, }, ]} - data={[ - { - id: 'agupta07', - date: '2020-12-18T00:34:51.414Z', - text: 'Vaccine Failed', - givenBy: 'Dr. Gupta', - }, - { - id: 'drewgreg', - date: '2020-12-17T00:34:51.414Z', - text: 'Vaccine Success!', - givenBy: 'Dr. Gregory', - }, - ]} + data={notes} getID={(r) => r.id} /> ) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index fdec97dbc4..2270467d85 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -11,6 +11,7 @@ import Permissions from '../../shared/model/Permissions' import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' +import useAddIncidentNote from '../hooks/useAddIncidentNote' import NotesTable from './NotesTable' import NewNoteModal from '../../shared/notes/NewNoteModal' @@ -19,6 +20,7 @@ const ViewIncident = () => { const { permissions } = useSelector((root: RootState) => root.user) const { data, isLoading } = useIncident(id) const [mutate] = useResolveIncident() + const [mutateAddNote] = useAddIncidentNote() const location = useLocation() const history = useHistory() const { t } = useTranslator() @@ -65,7 +67,7 @@ const ViewIncident = () => { {t('patient.notes.new')} - + {data && data.resolvedOn === undefined && @@ -89,7 +91,7 @@ const ViewIncident = () => { show={showNewNoteModal} toggle={closeNewNoteModal} onCloseButtonClick={closeNewNoteModal} - onSave={() => { /* TODO */}} + onSave={async (note) => { await mutateAddNote({note, incidentId: id })}} /> ) From 71ada1ac96b2d39d7d49bcdd3d0a9488aa3a2fc6 Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Wed, 23 Dec 2020 11:32:03 -0800 Subject: [PATCH 09/38] feat(incident): add no note support --- src/incidents/view/NotesTable.tsx | 15 ++++++++++++++- src/incidents/view/ViewIncident.tsx | 9 ++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx index 406d958819..81d72484ec 100644 --- a/src/incidents/view/NotesTable.tsx +++ b/src/incidents/view/NotesTable.tsx @@ -1,12 +1,25 @@ import React from 'react' -import { Table } from '@hospitalrun/components' +import { Alert, Table } from '@hospitalrun/components' import Note from '../../shared/model/Note' +import useTranslator from '../../shared/hooks/useTranslator' interface Props { notes: Note[] } const NotesTable = ({ notes }: Props) => { + const { t } = useTranslator() + + if (notes.length === 0) { + return ( + + ) + } + return (
{
-
- +
{data && data.resolvedOn === undefined && @@ -91,7 +91,10 @@ const ViewIncident = () => { show={showNewNoteModal} toggle={closeNewNoteModal} onCloseButtonClick={closeNewNoteModal} - onSave={async (note) => { await mutateAddNote({note, incidentId: id })}} + onSave={async (note) => { + await mutateAddNote({ note, incidentId: id }) + window.location.reload() + }} /> ) From 1fb746489d9da51545841c5114e11f96a5521e1e Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Wed, 23 Dec 2020 12:06:20 -0800 Subject: [PATCH 10/38] feat(incident): button formatting --- src/incidents/view/NotesTable.tsx | 16 ++++++++++++++++ src/shared/notes/NewNoteModal.tsx | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx index 81d72484ec..d5a7837386 100644 --- a/src/incidents/view/NotesTable.tsx +++ b/src/incidents/view/NotesTable.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { useHistory } from 'react-router' import { Alert, Table } from '@hospitalrun/components' import Note from '../../shared/model/Note' @@ -9,6 +10,7 @@ interface Props { const NotesTable = ({ notes }: Props) => { const { t } = useTranslator() + const history = useHistory() if (notes.length === 0) { return ( @@ -38,6 +40,7 @@ const NotesTable = ({ notes }: Props) => { label: 'Note', formatter: (row) => <>{row.text}, }, + /* { key: 'actions-key', label: 'Actions', @@ -49,6 +52,19 @@ const NotesTable = ({ notes }: Props) => { ) }, + },*/ + ]} + actionsHeaderText={t('actions.label')} + actions={[ + { + label: t('actions.edit'), + action: (row) => history.push(`incidents/${row.id}`), //TODO: fix + buttonColor: 'dark', + }, + { + label: t('actions.delete'), + action: (row) => history.push(`incidents/${row.id}`), //TODO: fix + buttonColor: 'danger', }, ]} data={notes} diff --git a/src/shared/notes/NewNoteModal.tsx b/src/shared/notes/NewNoteModal.tsx index 82e4ed9e93..78e5ca7882 100644 --- a/src/shared/notes/NewNoteModal.tsx +++ b/src/shared/notes/NewNoteModal.tsx @@ -15,7 +15,6 @@ interface Props { } const NewNoteModal = (props: Props) => { - const { show, toggle, onCloseButtonClick, onSave } = props const { t } = useTranslator() @@ -31,7 +30,7 @@ const NewNoteModal = (props: Props) => { onSave({ id: uuid(), date: new Date().toISOString(), - // TODO: Implement givenBy + givenBy: 'some user', text, }) setText('') From 1aa28fc0e1425814dd4ee51e58d5737ef37cc06e Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Sun, 3 Jan 2021 17:37:11 -0500 Subject: [PATCH 11/38] feat(incident): beginnings of editing notes support --- src/incidents/hooks/useAddIncidentNote.tsx | 2 +- src/incidents/view/NotesTable.tsx | 19 ++++----------- src/incidents/view/ViewIncident.tsx | 26 ++++++++++++++++++--- src/patients/notes/NoteTab.tsx | 14 ++++++++++- src/shared/notes/NewNoteModal.tsx | 27 ++++++++++++---------- 5 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx index 51cb008b33..096eeebc19 100644 --- a/src/incidents/hooks/useAddIncidentNote.tsx +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -16,7 +16,7 @@ async function addNote(request: AddNoteRequest): Promise { if (isEmpty(error)) { const incident = await IncidentRepository.find(request.incidentId) - const notes = incident.notes ? [...incident.notes] : [] + const notes = (incident.notes && incident.notes.filter((note) => note.id !== request.note.id)) || [] notes.push(request.note) diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx index d5a7837386..5a0fb6e36d 100644 --- a/src/incidents/view/NotesTable.tsx +++ b/src/incidents/view/NotesTable.tsx @@ -5,10 +5,11 @@ import { Alert, Table } from '@hospitalrun/components' import Note from '../../shared/model/Note' import useTranslator from '../../shared/hooks/useTranslator' interface Props { + onEditNote: (note: Note) => void notes: Note[] } -const NotesTable = ({ notes }: Props) => { +const NotesTable = ({ onEditNote, notes }: Props) => { const { t } = useTranslator() const history = useHistory() @@ -40,25 +41,12 @@ const NotesTable = ({ notes }: Props) => { label: 'Note', formatter: (row) => <>{row.text}, }, - /* - { - key: 'actions-key', - label: 'Actions', - formatter: () => { - return ( - <> - - - - ) - }, - },*/ ]} actionsHeaderText={t('actions.label')} actions={[ { label: t('actions.edit'), - action: (row) => history.push(`incidents/${row.id}`), //TODO: fix + action: onEditNote, buttonColor: 'dark', }, { @@ -70,6 +58,7 @@ const NotesTable = ({ notes }: Props) => { data={notes} getID={(r) => r.id} /> + ) } diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 9da2753a0a..af1d332429 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -3,11 +3,14 @@ import React, { useState } from 'react' import { useSelector } from 'react-redux' import { useParams, useHistory, useLocation } from 'react-router-dom' + import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' import useTranslator from '../../shared/hooks/useTranslator' import { RootState } from '../../shared/store' import Permissions from '../../shared/model/Permissions' +import Note from '../../shared/model/Note' +import { uuid } from '../../shared/util/uuid' import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' @@ -35,6 +38,12 @@ const ViewIncident = () => { //New Note Modal const [showNewNoteModal, setShowNoteModal] = useState(false) + const [editedNote, setEditedNote] = useState({ + id: uuid(), + givenBy: "some user", // TODO + text: '', + date: '', + }) const onNewNoteClick = () => { setShowNoteModal(true) } @@ -67,7 +76,13 @@ const ViewIncident = () => { {t('patient.notes.new')} - + { + setEditedNote(note) + setShowNoteModal(true) + }} + notes={(data && data.notes) || []} + /> {data && data.resolvedOn === undefined && @@ -91,10 +106,15 @@ const ViewIncident = () => { show={showNewNoteModal} toggle={closeNewNoteModal} onCloseButtonClick={closeNewNoteModal} - onSave={async (note) => { - await mutateAddNote({ note, incidentId: id }) + setNote={setEditedNote} + onSave={async () => { + await mutateAddNote({ + note: editedNote, + incidentId: id + }) window.location.reload() }} + note={editedNote} /> ) diff --git a/src/patients/notes/NoteTab.tsx b/src/patients/notes/NoteTab.tsx index aba88bd8fc..d09c40e37d 100644 --- a/src/patients/notes/NoteTab.tsx +++ b/src/patients/notes/NoteTab.tsx @@ -5,10 +5,12 @@ import { Route, Switch } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import useTranslator from '../../shared/hooks/useTranslator' +import Note from '../../shared/model/Note' import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' import NewNoteModal from '../../shared/notes/NewNoteModal' +import { uuid } from '../../shared/util/uuid' import useAddPatientNote from '../hooks/useAddPatientNote' import NotesList from './NotesList' import ViewNote from './ViewNote' @@ -22,6 +24,13 @@ const NoteTab = (props: Props) => { const { t } = useTranslator() const { permissions } = useSelector((state: RootState) => state.user) const [showNewNoteModal, setShowNoteModal] = useState(false) + const defaultNoteValue = { + id: uuid(), + givenBy: "some user", // TODO + text: '', + date: '', + } + const [newNote, setNewNote] = useState(defaultNoteValue) const [ mutate ] = useAddPatientNote() const breadcrumbs = [ @@ -38,6 +47,7 @@ const NoteTab = (props: Props) => { const closeNewNoteModal = () => { setShowNoteModal(false) + setNewNote(defaultNoteValue) } return ( @@ -71,9 +81,11 @@ const NoteTab = (props: Props) => { show={showNewNoteModal} toggle={closeNewNoteModal} onCloseButtonClick={closeNewNoteModal} - onSave={async (note) => { + onSave={async (note: Note) => { await mutate({ note, patientId: patient.id }) }} + note={newNote} + setNote={setNewNote} /> ) diff --git a/src/shared/notes/NewNoteModal.tsx b/src/shared/notes/NewNoteModal.tsx index 78e5ca7882..6228daca31 100644 --- a/src/shared/notes/NewNoteModal.tsx +++ b/src/shared/notes/NewNoteModal.tsx @@ -3,7 +3,6 @@ import React, { useState } from 'react' import TextFieldWithLabelFormGroup from '../components/input/TextFieldWithLabelFormGroup' import useTranslator from '../hooks/useTranslator' -import { uuid } from '../util/uuid' import Note from '../model/Note' import { NoteError } from '../../patients/util/validate-note' @@ -12,28 +11,32 @@ interface Props { toggle: () => void onCloseButtonClick: () => void onSave: (note: Note) => void + setNote: (note: Note) => void + note: Note } -const NewNoteModal = (props: Props) => { - const { show, toggle, onCloseButtonClick, onSave } = props +const NewNoteModal = ( + { note, onCloseButtonClick, onSave, setNote, show, toggle}: Props + ) => { const { t } = useTranslator() const [noteError, setNoteError] = useState(undefined) - const [text, setText] = useState('') const onNoteTextChange = (event: React.ChangeEvent) => { - setText(event.currentTarget.value) + setNote({ + ...note, + text: event.currentTarget.value, + }) } const onSaveButtonClick = async () => { try { - onSave({ - id: uuid(), + const updatedNote = { + ...note, date: new Date().toISOString(), - givenBy: 'some user', - text, - }) - setText('') + } + setNote(updatedNote) + onSave(updatedNote) onCloseButtonClick() } catch (e) { setNoteError(e) @@ -57,7 +60,7 @@ const NewNoteModal = (props: Props) => { isRequired name="noteTextField" label={t('patient.note')} - value={text} + value={note.text} isInvalid={!!noteError?.noteError} feedback={t(noteError?.noteError || '')} onChange={onNoteTextChange} From b4e2ca6053eb518fdb79729242650acd5e6daa39 Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Mon, 4 Jan 2021 10:23:08 -0800 Subject: [PATCH 12/38] feat(incident) editing and deleting notes --- src/incidents/hooks/useAddIncidentNote.tsx | 15 +++++--- src/incidents/hooks/useDeleteIncidentNote.tsx | 38 +++++++++++++++++++ src/incidents/view/NotesTable.tsx | 10 ++--- src/incidents/view/ViewIncident.tsx | 33 ++++++++++------ src/patients/notes/NoteTab.tsx | 5 ++- .../enUs/translations/patient/index.ts | 1 + src/shared/notes/NewNoteModal.tsx | 12 +++--- 7 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 src/incidents/hooks/useDeleteIncidentNote.tsx diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx index 096eeebc19..3be3441510 100644 --- a/src/incidents/hooks/useAddIncidentNote.tsx +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -15,14 +15,19 @@ async function addNote(request: AddNoteRequest): Promise { if (isEmpty(error)) { const incident = await IncidentRepository.find(request.incidentId) - - const notes = (incident.notes && incident.notes.filter((note) => note.id !== request.note.id)) || [] - - notes.push(request.note) + const notes = incident.notes || [] + let noteIdx = notes.findIndex((note) => note.id === request.note.id) + if (noteIdx === -1) { + // This note is new. + notes.push(request.note) + } else { + // We're editing an already existing note. + notes[noteIdx] = request.note + } await IncidentRepository.saveOrUpdate({ ...incident, - notes + notes, }) return notes diff --git a/src/incidents/hooks/useDeleteIncidentNote.tsx b/src/incidents/hooks/useDeleteIncidentNote.tsx new file mode 100644 index 0000000000..01d74a7ea4 --- /dev/null +++ b/src/incidents/hooks/useDeleteIncidentNote.tsx @@ -0,0 +1,38 @@ +import { isEmpty } from 'lodash' +import { queryCache, useMutation } from 'react-query' + +import IncidentRepository from '../../shared/db/IncidentRepository' +import Note from '../../shared/model/Note' + +interface DeleteNoteRequest { + incidentId: string + note: Note +} + +async function deleteNote(request: DeleteNoteRequest): Promise { + const error = [] as any + + if (isEmpty(error)) { + const incident = await IncidentRepository.find(request.incidentId) + let notes = incident.notes || [] + notes = notes.filter((note) => note.id !== request.note.id) + + await IncidentRepository.saveOrUpdate({ + ...incident, + notes, + }) + + return notes + } + + throw error +} + +export default function useDeleteIncidentNote() { + return useMutation(deleteNote, { + onSuccess: async (data, variables) => { + await queryCache.setQueryData(['notes', variables.incidentId], data) + }, + throwOnError: true, + }) +} diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx index 5a0fb6e36d..aac4f0c6ec 100644 --- a/src/incidents/view/NotesTable.tsx +++ b/src/incidents/view/NotesTable.tsx @@ -1,17 +1,18 @@ import React from 'react' -import { useHistory } from 'react-router' +//import { useHistory } from 'react-router' import { Alert, Table } from '@hospitalrun/components' import Note from '../../shared/model/Note' import useTranslator from '../../shared/hooks/useTranslator' interface Props { onEditNote: (note: Note) => void + onDeleteNote: (note: Note) => void notes: Note[] } -const NotesTable = ({ onEditNote, notes }: Props) => { +const NotesTable = ({ onEditNote, onDeleteNote, notes }: Props) => { const { t } = useTranslator() - const history = useHistory() + //const history = useHistory() if (notes.length === 0) { return ( @@ -51,14 +52,13 @@ const NotesTable = ({ onEditNote, notes }: Props) => { }, { label: t('actions.delete'), - action: (row) => history.push(`incidents/${row.id}`), //TODO: fix + action: onDeleteNote, buttonColor: 'danger', }, ]} data={notes} getID={(r) => r.id} /> - ) } diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index af1d332429..de3cf87fe8 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -3,7 +3,6 @@ import React, { useState } from 'react' import { useSelector } from 'react-redux' import { useParams, useHistory, useLocation } from 'react-router-dom' - import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' import useTranslator from '../../shared/hooks/useTranslator' @@ -15,6 +14,7 @@ import ViewIncidentDetails from './ViewIncidentDetails' import useIncident from '../hooks/useIncident' import useResolveIncident from '../hooks/useResolveIncident' import useAddIncidentNote from '../hooks/useAddIncidentNote' +import useDeleteIncidentNote from '../hooks/useDeleteIncidentNote' import NotesTable from './NotesTable' import NewNoteModal from '../../shared/notes/NewNoteModal' @@ -24,6 +24,7 @@ const ViewIncident = () => { const { data, isLoading } = useIncident(id) const [mutate] = useResolveIncident() const [mutateAddNote] = useAddIncidentNote() + const [deleteNote] = useDeleteIncidentNote() const location = useLocation() const history = useHistory() const { t } = useTranslator() @@ -37,14 +38,16 @@ const ViewIncident = () => { ]) //New Note Modal - const [showNewNoteModal, setShowNoteModal] = useState(false) - const [editedNote, setEditedNote] = useState({ + const newNoteState = { id: uuid(), - givenBy: "some user", // TODO + givenBy: '', // TODO text: '', date: '', - }) + } + const [showNewNoteModal, setShowNoteModal] = useState(false) + const [editedNote, setEditedNote] = useState(newNoteState) const onNewNoteClick = () => { + setEditedNote(newNoteState) setShowNoteModal(true) } const closeNewNoteModal = () => { @@ -76,12 +79,19 @@ const ViewIncident = () => { {t('patient.notes.new')} - { setEditedNote(note) setShowNoteModal(true) }} - notes={(data && data.notes) || []} + onDeleteNote={async (note: Note) => { + await deleteNote({ + note: note, + incidentId: id, + }) + window.location.reload() + }} + notes={(data && data.notes) || []} /> {data && @@ -107,11 +117,12 @@ const ViewIncident = () => { toggle={closeNewNoteModal} onCloseButtonClick={closeNewNoteModal} setNote={setEditedNote} - onSave={async () => { - await mutateAddNote({ - note: editedNote, - incidentId: id + onSave={async (note: Note) => { + await mutateAddNote({ + note: note, + incidentId: id, }) + setEditedNote(newNoteState) window.location.reload() }} note={editedNote} diff --git a/src/patients/notes/NoteTab.tsx b/src/patients/notes/NoteTab.tsx index d09c40e37d..1ec3110f32 100644 --- a/src/patients/notes/NoteTab.tsx +++ b/src/patients/notes/NoteTab.tsx @@ -26,12 +26,12 @@ const NoteTab = (props: Props) => { const [showNewNoteModal, setShowNoteModal] = useState(false) const defaultNoteValue = { id: uuid(), - givenBy: "some user", // TODO + givenBy: 'some user', // TODO text: '', date: '', } const [newNote, setNewNote] = useState(defaultNoteValue) - const [ mutate ] = useAddPatientNote() + const [mutate] = useAddPatientNote() const breadcrumbs = [ { @@ -42,6 +42,7 @@ const NoteTab = (props: Props) => { useAddBreadcrumbs(breadcrumbs) const onNewNoteClick = () => { + setNewNote(defaultNoteValue) setShowNoteModal(true) } diff --git a/src/shared/locales/enUs/translations/patient/index.ts b/src/shared/locales/enUs/translations/patient/index.ts index 9eafb44d61..fd7bb5aec3 100644 --- a/src/shared/locales/enUs/translations/patient/index.ts +++ b/src/shared/locales/enUs/translations/patient/index.ts @@ -103,6 +103,7 @@ export default { notes: { label: 'Notes', new: 'Add New Note', + edit: 'Edit Note', warning: { noNotes: 'No Notes', }, diff --git a/src/shared/notes/NewNoteModal.tsx b/src/shared/notes/NewNoteModal.tsx index 6228daca31..2f9885b3f9 100644 --- a/src/shared/notes/NewNoteModal.tsx +++ b/src/shared/notes/NewNoteModal.tsx @@ -12,12 +12,10 @@ interface Props { onCloseButtonClick: () => void onSave: (note: Note) => void setNote: (note: Note) => void - note: Note + note: Note // New if note.date === '' } -const NewNoteModal = ( - { note, onCloseButtonClick, onSave, setNote, show, toggle}: Props - ) => { +const NewNoteModal = ({ note, onCloseButtonClick, onSave, setNote, show, toggle }: Props) => { const { t } = useTranslator() const [noteError, setNoteError] = useState(undefined) @@ -70,12 +68,12 @@ const NewNoteModal = ( ) - + const actionString = note.date ? t('patient.notes.edit') : t('patient.notes.new') return ( Date: Tue, 5 Jan 2021 14:25:30 -0800 Subject: [PATCH 13/38] fixed spacing --- src/incidents/view/NotesTable.tsx | 3 ++- src/incidents/view/ViewIncident.tsx | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/incidents/view/NotesTable.tsx b/src/incidents/view/NotesTable.tsx index aac4f0c6ec..151843a02a 100644 --- a/src/incidents/view/NotesTable.tsx +++ b/src/incidents/view/NotesTable.tsx @@ -4,6 +4,7 @@ import React from 'react' import { Alert, Table } from '@hospitalrun/components' import Note from '../../shared/model/Note' import useTranslator from '../../shared/hooks/useTranslator' +import { extractUsername } from '../../shared/util/extractUsername' interface Props { onEditNote: (note: Note) => void onDeleteNote: (note: Note) => void @@ -35,7 +36,7 @@ const NotesTable = ({ onEditNote, onDeleteNote, notes }: Props) => { { key: 'given-by-key', label: 'Given By', - formatter: (row) => <>{row.givenBy}, + formatter: (row) => <>{extractUsername(row.givenBy || '')}, }, { key: 'note-key', diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index de3cf87fe8..e0c72697ae 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -20,7 +20,7 @@ import NewNoteModal from '../../shared/notes/NewNoteModal' const ViewIncident = () => { const { id } = useParams() as any - const { permissions } = useSelector((root: RootState) => root.user) + const { permissions, user } = useSelector((root: RootState) => root.user) const { data, isLoading } = useIncident(id) const [mutate] = useResolveIncident() const [mutateAddNote] = useAddIncidentNote() @@ -40,7 +40,7 @@ const ViewIncident = () => { //New Note Modal const newNoteState = { id: uuid(), - givenBy: '', // TODO + givenBy: user?.id, text: '', date: '', } @@ -74,7 +74,7 @@ const ViewIncident = () => { /> -
+
From 86512a230bd27f87bf4ebef41a6934592edca33b Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Tue, 5 Jan 2021 15:02:34 -0800 Subject: [PATCH 14/38] feat(incident) moved hooks to top of file --- src/incidents/view/ViewIncident.tsx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index e0c72697ae..2dd451bd64 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -28,24 +28,27 @@ const ViewIncident = () => { const location = useLocation() const history = useHistory() const { t } = useTranslator() - const updateTitle = useUpdateTitle() - updateTitle(t('incidents.reports.view')) - useAddBreadcrumbs([ - { - i18nKey: 'incidents.reports.view', - location: `/incidents/${id}`, - }, - ]) - //New Note Modal + const [showNewNoteModal, setShowNoteModal] = useState(false) const newNoteState = { id: uuid(), givenBy: user?.id, text: '', date: '', } - const [showNewNoteModal, setShowNoteModal] = useState(false) const [editedNote, setEditedNote] = useState(newNoteState) + + const updateTitle = useUpdateTitle() + + useAddBreadcrumbs([ + { + i18nKey: 'incidents.reports.view', + location: `/incidents/${id}`, + }, + ]) + + updateTitle(t('incidents.reports.view')) + const onNewNoteClick = () => { setEditedNote(newNoteState) setShowNoteModal(true) From bf58189fe7a4f391034138b5ebe80755389707c9 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Tue, 5 Jan 2021 21:11:45 -0500 Subject: [PATCH 15/38] wip: debug react hooks --- package.json | 2 +- .../incidents/view/ViewIncident.test.tsx | 21 ++++--- src/incidents/view/ViewIncident.tsx | 62 ++++++++++--------- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 502a764748..485e387d8e 100644 --- a/package.json +++ b/package.json @@ -160,4 +160,4 @@ "git add ." ] } -} \ No newline at end of file +} diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index b0706f7eb9..960680fd49 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -7,6 +7,9 @@ import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' +import { ReactQueryCacheProvider, QueryCache } from 'react-query' + + import ViewIncident from '../../../incidents/view/ViewIncident' import ViewIncidentDetails from '../../../incidents/view/ViewIncidentDetails' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' @@ -59,6 +62,14 @@ describe('View Incident', () => { wrapper.update() return { wrapper: wrapper as ReactWrapper, history } } + it('should render ViewIncidentDetails', async () => { + const { wrapper } = await setup([Permissions.ViewIncident]) + + // const viewIncidentDetails = wrapper.find(ViewIncidentDetails) + // expect(viewIncidentDetails.exists()).toBeTruthy() + // expect(viewIncidentDetails.prop('permissions')).toEqual(permissions) + // expect(viewIncidentDetails.prop('incidentId')).toEqual('1234') + }) it('should set the breadcrumbs properly', async () => { await setup([Permissions.ViewIncident]) @@ -68,13 +79,5 @@ describe('View Incident', () => { ]) }) - it('should render ViewIncidentDetails', async () => { - const permissions = [Permissions.ReportIncident, Permissions.ResolveIncident] - const { wrapper } = await setup(permissions) - - const viewIncidentDetails = wrapper.find(ViewIncidentDetails) - expect(viewIncidentDetails.exists()).toBeTruthy() - expect(viewIncidentDetails.prop('permissions')).toEqual(permissions) - expect(viewIncidentDetails.prop('incidentId')).toEqual('1234') - }) + }) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index e0c72697ae..7f91acc481 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -20,16 +20,24 @@ import NewNoteModal from '../../shared/notes/NewNoteModal' const ViewIncident = () => { const { id } = useParams() as any - const { permissions, user } = useSelector((root: RootState) => root.user) - const { data, isLoading } = useIncident(id) - const [mutate] = useResolveIncident() - const [mutateAddNote] = useAddIncidentNote() - const [deleteNote] = useDeleteIncidentNote() const location = useLocation() const history = useHistory() const { t } = useTranslator() - const updateTitle = useUpdateTitle() - updateTitle(t('incidents.reports.view')) + const { permissions, user } = useSelector((root: RootState) => root.user) + const { data, isLoading } = useIncident(id) + // // const [mutate] = useResolveIncident() + // const [mutateAddNote] = useAddIncidentNote() + // const [deleteNote] = useDeleteIncidentNote() + + // const updateTitle = useUpdateTitle() + // const [showNewNoteModal, setShowNoteModal] = useState(false) + // const newNoteState = { + // id: uuid(), + // givenBy: user?.id, + // text: '', + // date: '', + // } + // const [editedNote, setEditedNote] = useState(newNoteState) useAddBreadcrumbs([ { i18nKey: 'incidents.reports.view', @@ -37,35 +45,31 @@ const ViewIncident = () => { }, ]) - //New Note Modal - const newNoteState = { - id: uuid(), - givenBy: user?.id, - text: '', - date: '', - } - const [showNewNoteModal, setShowNoteModal] = useState(false) - const [editedNote, setEditedNote] = useState(newNoteState) - const onNewNoteClick = () => { - setEditedNote(newNoteState) - setShowNoteModal(true) - } - const closeNewNoteModal = () => { - setShowNoteModal(false) - } + // updateTitle(t('incidents.reports.view')) + + + // const onNewNoteClick = () => { + // setEditedNote(newNoteState) + // setShowNoteModal(true) + // } + // const closeNewNoteModal = () => { + // setShowNoteModal(false) + // } + + // const onResolve = async () => { + // await mutate(data) + // history.push('/incidents') + // } if (id === undefined || permissions === undefined) { return <> } - const onResolve = async () => { - await mutate(data) - history.push('/incidents') - } + return (
- + {/* { window.location.reload() }} note={editedNote} - /> + /> */}
) } From fb875b2bb11e47a5a4df56fb1c25b6cb18e59b08 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Wed, 6 Jan 2021 18:22:36 -0500 Subject: [PATCH 16/38] test(incident): fixed view incident test --- .../incidents/view/ViewIncident.test.tsx | 39 +++++++++----- src/incidents/view/ViewIncident.tsx | 52 +++++++++---------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index 960680fd49..60263b0533 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -24,16 +24,18 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore([thunk]) describe('View Incident', () => { + const queryCache = new QueryCache() + const mockedIncident : Incident = { + id: '1234', + date: new Date().toISOString(), + code: 'some code', + reportedOn: new Date().toISOString(), + } as Incident const setup = async (permissions: Permissions[]) => { jest.resetAllMocks() jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(breadcrumbUtil, 'default') - jest.spyOn(IncidentRepository, 'find').mockResolvedValue({ - id: '1234', - date: new Date().toISOString(), - code: 'some code', - reportedOn: new Date().toISOString(), - } as Incident) + jest.spyOn(IncidentRepository, 'find').mockResolvedValue(mockedIncident) const history = createMemoryHistory() history.push(`/incidents/1234`) @@ -43,10 +45,12 @@ describe('View Incident', () => { }, } as any) + let wrapper: any await act(async () => { wrapper = await mount( - + + @@ -56,21 +60,29 @@ describe('View Incident', () => { - , + , + ) }) wrapper.update() return { wrapper: wrapper as ReactWrapper, history } } + + afterEach(() => { + jest.restoreAllMocks() + queryCache.clear() + }) + it('should render ViewIncidentDetails', async () => { - const { wrapper } = await setup([Permissions.ViewIncident]) + const permissions = [Permissions.ResolveIncident, Permissions.ReportIncident] + const { wrapper } = await setup(permissions) - // const viewIncidentDetails = wrapper.find(ViewIncidentDetails) - // expect(viewIncidentDetails.exists()).toBeTruthy() - // expect(viewIncidentDetails.prop('permissions')).toEqual(permissions) - // expect(viewIncidentDetails.prop('incidentId')).toEqual('1234') + const viewIncidentDetails = wrapper.find(ViewIncidentDetails) + expect(viewIncidentDetails.exists()).toBeTruthy() + expect(viewIncidentDetails.prop('incident')).toEqual(mockedIncident) }) + it('should set the breadcrumbs properly', async () => { await setup([Permissions.ViewIncident]) @@ -80,4 +92,5 @@ describe('View Incident', () => { }) + }) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 7f91acc481..a0f208ec29 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -25,19 +25,19 @@ const ViewIncident = () => { const { t } = useTranslator() const { permissions, user } = useSelector((root: RootState) => root.user) const { data, isLoading } = useIncident(id) - // // const [mutate] = useResolveIncident() - // const [mutateAddNote] = useAddIncidentNote() - // const [deleteNote] = useDeleteIncidentNote() + const [mutate] = useResolveIncident() + const [mutateAddNote] = useAddIncidentNote() + const [deleteNote] = useDeleteIncidentNote() - // const updateTitle = useUpdateTitle() - // const [showNewNoteModal, setShowNoteModal] = useState(false) - // const newNoteState = { - // id: uuid(), - // givenBy: user?.id, - // text: '', - // date: '', - // } - // const [editedNote, setEditedNote] = useState(newNoteState) + const updateTitle = useUpdateTitle() + const [showNewNoteModal, setShowNoteModal] = useState(false) + const newNoteState = { + id: uuid(), + givenBy: user?.id, + text: '', + date: '', + } + const [editedNote, setEditedNote] = useState(newNoteState) useAddBreadcrumbs([ { i18nKey: 'incidents.reports.view', @@ -45,21 +45,21 @@ const ViewIncident = () => { }, ]) - // updateTitle(t('incidents.reports.view')) + updateTitle(t('incidents.reports.view')) - // const onNewNoteClick = () => { - // setEditedNote(newNoteState) - // setShowNoteModal(true) - // } - // const closeNewNoteModal = () => { - // setShowNoteModal(false) - // } + const onNewNoteClick = () => { + setEditedNote(newNoteState) + setShowNoteModal(true) + } + const closeNewNoteModal = () => { + setShowNoteModal(false) + } - // const onResolve = async () => { - // await mutate(data) - // history.push('/incidents') - // } + const onResolve = async () => { + await mutate(data) + history.push('/incidents') + } if (id === undefined || permissions === undefined) { return <> @@ -69,7 +69,7 @@ const ViewIncident = () => { return (
- {/* + { window.location.reload() }} note={editedNote} - /> */} + />
) } From 52e0f99eb96d20f9fe94d4904ee4c79f57e62330 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Wed, 6 Jan 2021 18:42:47 -0500 Subject: [PATCH 17/38] test(incident): fixed view incident test --- src/__tests__/incidents/view/ViewIncident.test.tsx | 6 ++++++ src/__tests__/incidents/view/ViewIncidentDetails.test.tsx | 8 +------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index 60263b0533..2448ce656e 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -82,6 +82,12 @@ describe('View Incident', () => { expect(viewIncidentDetails.prop('incident')).toEqual(mockedIncident) }) + it('should call find incident by id', async () => { + await setup([Permissions.ViewIncident]) + + expect(IncidentRepository.find).toHaveBeenCalledTimes(1) + expect(IncidentRepository.find).toHaveBeenCalledWith(mockedIncident.id) + }) it('should set the breadcrumbs properly', async () => { await setup([Permissions.ViewIncident]) diff --git a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx index 69619d1626..dab4820ac3 100644 --- a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx +++ b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx @@ -56,13 +56,7 @@ describe('View Incident Details', () => { } describe('view details', () => { - it('should call find incident by id', async () => { - await setup(expectedIncident, [Permissions.ViewIncident]) - - expect(IncidentRepository.find).toHaveBeenCalledTimes(1) - expect(IncidentRepository.find).toHaveBeenCalledWith(expectedIncidentId) - }) - + it('should render the date of incident', async () => { const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) From 73606bb70316ffe2715c9ae23bfeb6a8330621f1 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Wed, 6 Jan 2021 19:10:09 -0500 Subject: [PATCH 18/38] test(incident): NewNoteModal --- .../patients/notes/NewNoteModal.test.tsx | 27 ++++++++++++------- src/patients/notes/NotesList.tsx | 3 +++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/__tests__/patients/notes/NewNoteModal.test.tsx b/src/__tests__/patients/notes/NewNoteModal.test.tsx index d60e85e0a4..e310db458b 100644 --- a/src/__tests__/patients/notes/NewNoteModal.test.tsx +++ b/src/__tests__/patients/notes/NewNoteModal.test.tsx @@ -8,6 +8,7 @@ import React from 'react' import NewNoteModal from '../../../shared/notes/NewNoteModal' import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import PatientRepository from '../../../shared/db/PatientRepository' +import Note from '../../../shared/model/Note' import Patient from '../../../shared/model/Patient' describe('New Note Modal', () => { @@ -16,7 +17,16 @@ describe('New Note Modal', () => { givenName: 'someName', } as Patient - const setup = (onCloseSpy = jest.fn()) => { + + const mockNote : Note = { + id: "note id", + date: "note date", + text: "note text", + givenBy: "given by person" + + } + + const setup = (onCloseSpy = jest.fn(), onSaveSpy = jest.fn()) => { jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(mockPatient) jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient) const wrapper = mount( @@ -24,7 +34,9 @@ describe('New Note Modal', () => { show onCloseButtonClick={onCloseSpy} toggle={jest.fn()} - patientId={mockPatient.id} + onSave={onSaveSpy} + setNote={jest.fn()} + note={mockNote} />, ) return { wrapper } @@ -96,7 +108,8 @@ describe('New Note Modal', () => { describe('on save', () => { it('should dispatch add note', async () => { const expectedNote = 'some note' - const { wrapper } = setup() + const onSaveSpy = jest.fn() + const { wrapper } = setup(jest.fn(), onSaveSpy) const noteTextField = wrapper.find(TextFieldWithLabelFormGroup) await act(async () => { @@ -113,12 +126,8 @@ describe('New Note Modal', () => { wrapper.update() }) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith( - expect.objectContaining({ - notes: [expect.objectContaining({ text: expectedNote })], - }), - ) + expect(onSaveSpy).toHaveBeenCalledTimes(1) + expect(onSaveSpy).toHaveBeenCalledWith(mockNote) // Does the form reset value back to blank? expect(noteTextField.prop('value')).toEqual('') diff --git a/src/patients/notes/NotesList.tsx b/src/patients/notes/NotesList.tsx index e6e7c18214..cd62c0df3f 100644 --- a/src/patients/notes/NotesList.tsx +++ b/src/patients/notes/NotesList.tsx @@ -1,5 +1,6 @@ import { Alert, List, ListItem } from '@hospitalrun/components' import React from 'react' +import { useHistory } from 'react-router-dom' import Loading from '../../shared/components/Loading' import useTranslator from '../../shared/hooks/useTranslator' @@ -12,6 +13,7 @@ interface Props { const NotesList = (props: Props) => { const { patientId } = props + const history = useHistory() const { t } = useTranslator() const { data, status } = usePatientNotes(patientId) @@ -35,6 +37,7 @@ const NotesList = (props: Props) => { history.push(`/patients/${patientId}/notes/${note.id}`)} >

{new Date(note.date).toLocaleString()}

{note.text}

From b1907fa88b85bb541532f2c0918afbd188ac545a Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Tue, 12 Jan 2021 10:23:04 -0800 Subject: [PATCH 19/38] (test) fixed some view incident details tests --- .../incidents/view/ViewIncident.test.tsx | 59 +++++++++++-------- .../view/ViewIncidentDetails.test.tsx | 45 +++++--------- .../patients/notes/NewNoteModal.test.tsx | 18 +++--- 3 files changed, 57 insertions(+), 65 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index 2448ce656e..d4f50a742d 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -1,15 +1,14 @@ +import { Button } from '@hospitalrun/components' import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' import { act } from 'react-dom/test-utils' +import { ReactQueryCacheProvider, QueryCache } from 'react-query' import { Provider } from 'react-redux' import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import { ReactQueryCacheProvider, QueryCache } from 'react-query' - - import ViewIncident from '../../../incidents/view/ViewIncident' import ViewIncidentDetails from '../../../incidents/view/ViewIncidentDetails' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' @@ -25,17 +24,23 @@ const mockStore = createMockStore([thunk]) describe('View Incident', () => { const queryCache = new QueryCache() - const mockedIncident : Incident = { + const mockedIncident: Incident = { id: '1234', - date: new Date().toISOString(), code: 'some code', + department: 'some department', + description: 'some description', + category: 'some category', + categoryItem: 'some category item', + status: 'reported', + reportedBy: 'some user id', reportedOn: new Date().toISOString(), + date: new Date().toISOString(), } as Incident - const setup = async (permissions: Permissions[]) => { + const setup = async (permissions: Permissions[], incident: Incident) => { jest.resetAllMocks() jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(breadcrumbUtil, 'default') - jest.spyOn(IncidentRepository, 'find').mockResolvedValue(mockedIncident) + jest.spyOn(IncidentRepository, 'find').mockResolvedValue(incident) const history = createMemoryHistory() history.push(`/incidents/1234`) @@ -45,23 +50,23 @@ describe('View Incident', () => { }, } as any) - let wrapper: any await act(async () => { wrapper = await mount( - - - - - - - - - - , - + + + + + + + + + + + , + , ) }) wrapper.update() @@ -73,9 +78,16 @@ describe('View Incident', () => { queryCache.clear() }) + it('should display a resolve incident button if the incident is in a reported state', async () => { + const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + + const buttons = wrapper.find(Button) + expect(buttons.at(0).text().trim()).toEqual('incidents.reports.resolve') + }) + it('should render ViewIncidentDetails', async () => { const permissions = [Permissions.ResolveIncident, Permissions.ReportIncident] - const { wrapper } = await setup(permissions) + const { wrapper } = await setup(permissions, mockedIncident) const viewIncidentDetails = wrapper.find(ViewIncidentDetails) expect(viewIncidentDetails.exists()).toBeTruthy() @@ -83,20 +95,17 @@ describe('View Incident', () => { }) it('should call find incident by id', async () => { - await setup([Permissions.ViewIncident]) + await setup([Permissions.ViewIncident], mockedIncident) expect(IncidentRepository.find).toHaveBeenCalledTimes(1) expect(IncidentRepository.find).toHaveBeenCalledWith(mockedIncident.id) }) it('should set the breadcrumbs properly', async () => { - await setup([Permissions.ViewIncident]) + await setup([Permissions.ViewIncident], mockedIncident) expect(breadcrumbUtil.default).toHaveBeenCalledWith([ { i18nKey: 'incidents.reports.view', location: '/incidents/1234' }, ]) }) - - - }) diff --git a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx index dab4820ac3..d1e75eb8bc 100644 --- a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx +++ b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx @@ -10,7 +10,6 @@ import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcru import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' import IncidentRepository from '../../../shared/db/IncidentRepository' import Incident from '../../../shared/model/Incident' -import Permissions from '../../../shared/model/Permissions' describe('View Incident Details', () => { const expectedDate = new Date(2020, 5, 1, 19, 48) @@ -30,7 +29,7 @@ describe('View Incident Details', () => { date: expectedDate.toISOString(), } as Incident - const setup = async (mockIncident: Incident, permissions: Permissions[]) => { + const setup = async (mockIncident: Incident) => { jest.resetAllMocks() Date.now = jest.fn(() => expectedResolveDate.valueOf()) jest.spyOn(breadcrumbUtil, 'default') @@ -46,7 +45,7 @@ describe('View Incident Details', () => { wrapper = await mount( - + , ) @@ -56,9 +55,8 @@ describe('View Incident Details', () => { } describe('view details', () => { - it('should render the date of incident', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const dateOfIncidentFormGroup = wrapper.find('.incident-date') expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.dateOfIncident') @@ -66,7 +64,7 @@ describe('View Incident Details', () => { }) it('should render the status', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const dateOfIncidentFormGroup = wrapper.find('.incident-status') expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.status') @@ -74,7 +72,7 @@ describe('View Incident Details', () => { }) it('should render the reported by', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const dateOfIncidentFormGroup = wrapper.find('.incident-reported-by') expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.reportedBy') @@ -82,7 +80,7 @@ describe('View Incident Details', () => { }) it('should render the reported on', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const dateOfIncidentFormGroup = wrapper.find('.incident-reported-on') expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.reportedOn') @@ -95,7 +93,7 @@ describe('View Incident Details', () => { status: 'resolved', resolvedOn: '2020-07-10 06:33 PM', } as Incident - const { wrapper } = await setup(mockIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(mockIncident) const dateOfResolutionFormGroup = wrapper.find('.incident-resolved-on') expect(dateOfResolutionFormGroup.find('h4').text()).toEqual('incidents.reports.resolvedOn') @@ -103,14 +101,14 @@ describe('View Incident Details', () => { }) it('should not render the resolved on if incident status is not resolved', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const completedOn = wrapper.find('.incident-resolved-on') expect(completedOn).toHaveLength(0) }) it('should render the department', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const departmentInput = wrapper.findWhere((w: any) => w.prop('name') === 'department') expect(departmentInput.prop('label')).toEqual('incidents.reports.department') @@ -118,7 +116,7 @@ describe('View Incident Details', () => { }) it('should render the category', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const categoryInput = wrapper.findWhere((w: any) => w.prop('name') === 'category') expect(categoryInput.prop('label')).toEqual('incidents.reports.category') @@ -126,7 +124,7 @@ describe('View Incident Details', () => { }) it('should render the category item', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const categoryItemInput = wrapper.findWhere((w: any) => w.prop('name') === 'categoryItem') expect(categoryItemInput.prop('label')).toEqual('incidents.reports.categoryItem') @@ -134,25 +132,15 @@ describe('View Incident Details', () => { }) it('should render the description', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const descriptionTextInput = wrapper.findWhere((w: any) => w.prop('name') === 'description') expect(descriptionTextInput.prop('label')).toEqual('incidents.reports.description') expect(descriptionTextInput.prop('value')).toEqual(expectedIncident.description) }) - it('should display a resolve incident button if the incident is in a reported state', async () => { - const { wrapper } = await setup(expectedIncident, [ - Permissions.ViewIncident, - Permissions.ResolveIncident, - ]) - - const buttons = wrapper.find(Button) - expect(buttons.at(0).text().trim()).toEqual('incidents.reports.resolve') - }) - it('should not display a resolve incident button if the user has no access ResolveIncident access', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(expectedIncident) const resolveButton = wrapper.find(Button) expect(resolveButton).toHaveLength(0) @@ -160,7 +148,7 @@ describe('View Incident Details', () => { it('should not display a resolve incident button if the incident is resolved', async () => { const mockIncident = { ...expectedIncident, status: 'resolved' } as Incident - const { wrapper } = await setup(mockIncident, [Permissions.ViewIncident]) + const { wrapper } = await setup(mockIncident) const resolveButton = wrapper.find(Button) expect(resolveButton).toHaveLength(0) @@ -169,10 +157,7 @@ describe('View Incident Details', () => { describe('on resolve', () => { it('should mark the status as resolved and fill in the resolved date with the current time', async () => { - const { wrapper, history } = await setup(expectedIncident, [ - Permissions.ViewIncident, - Permissions.ResolveIncident, - ]) + const { wrapper, history } = await setup(expectedIncident) const resolveButton = wrapper.find(Button).at(0) await act(async () => { diff --git a/src/__tests__/patients/notes/NewNoteModal.test.tsx b/src/__tests__/patients/notes/NewNoteModal.test.tsx index e310db458b..e691a8a05c 100644 --- a/src/__tests__/patients/notes/NewNoteModal.test.tsx +++ b/src/__tests__/patients/notes/NewNoteModal.test.tsx @@ -5,11 +5,11 @@ import { act } from '@testing-library/react' import { mount } from 'enzyme' import React from 'react' -import NewNoteModal from '../../../shared/notes/NewNoteModal' import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import PatientRepository from '../../../shared/db/PatientRepository' import Note from '../../../shared/model/Note' import Patient from '../../../shared/model/Patient' +import NewNoteModal from '../../../shared/notes/NewNoteModal' describe('New Note Modal', () => { const mockPatient = { @@ -17,13 +17,11 @@ describe('New Note Modal', () => { givenName: 'someName', } as Patient - - const mockNote : Note = { - id: "note id", - date: "note date", - text: "note text", - givenBy: "given by person" - + const mockNote: Note = { + id: 'note id', + date: 'note date', + text: 'note text', + givenBy: 'given by person', } const setup = (onCloseSpy = jest.fn(), onSaveSpy = jest.fn()) => { @@ -108,7 +106,7 @@ describe('New Note Modal', () => { describe('on save', () => { it('should dispatch add note', async () => { const expectedNote = 'some note' - const onSaveSpy = jest.fn() + const onSaveSpy = jest.fn() const { wrapper } = setup(jest.fn(), onSaveSpy) const noteTextField = wrapper.find(TextFieldWithLabelFormGroup) @@ -127,7 +125,7 @@ describe('New Note Modal', () => { }) expect(onSaveSpy).toHaveBeenCalledTimes(1) - expect(onSaveSpy).toHaveBeenCalledWith(mockNote) + expect(onSaveSpy).toHaveBeenCalledWith(mockNote) // Does the form reset value back to blank? expect(noteTextField.prop('value')).toEqual('') From cda6b511fe8cd0475e1b3b9e5d341de46b8d202d Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Tue, 12 Jan 2021 13:29:45 -0800 Subject: [PATCH 20/38] (test) fixed incident details and migrated some tests to view incident --- package.json | 1 + .../incidents/view/ViewIncident.test.tsx | 57 ++++++++++++++++++- .../view/ViewIncidentDetails.test.tsx | 38 ------------- src/incidents/view/ViewIncident.tsx | 2 +- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 485e387d8e..ce55f18538 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "prepublishOnly": "npm run build", "test": "npm run translation:check && react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --detectOpenHandles", "test:ci": "cross-env CI=true react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --passWithNoTests", + "test:debug": "cross-env CI=true react-scripts --inspect-brk test --runInBand --no-cache", "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"scripts/check-translations/**/*.{js,ts}\"", "lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"scripts/check-translations/**/*.{js,ts}\" --fix", "lint-staged": "lint-staged", diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index d4f50a742d..42c36a8bdb 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -23,6 +23,7 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore([thunk]) describe('View Incident', () => { + let incidentRepositorySaveSpy: any const queryCache = new QueryCache() const mockedIncident: Incident = { id: '1234', @@ -43,6 +44,9 @@ describe('View Incident', () => { jest.spyOn(IncidentRepository, 'find').mockResolvedValue(incident) const history = createMemoryHistory() history.push(`/incidents/1234`) + incidentRepositorySaveSpy = jest + .spyOn(IncidentRepository, 'saveOrUpdate') + .mockResolvedValue(mockedIncident) const store = mockStore({ user: { @@ -78,10 +82,33 @@ describe('View Incident', () => { queryCache.clear() }) - it('should display a resolve incident button if the incident is in a reported state', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + it('should not display a resolve incident button if the user has no access ResolveIncident access', async () => { + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ResolveIncident], + mockedIncident, + ) + + const resolveButton = wrapper.find(Button) + expect(resolveButton).toHaveLength(0) + }) + + it('should not display a resolve incident button if the incident is resolved', async () => { + const mockIncident = { ...mockedIncident, status: 'resolved' } as Incident + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ResolveIncident], + mockIncident, + ) - const buttons = wrapper.find(Button) + const resolveButton = wrapper.find(Button) + expect(resolveButton).toHaveLength(0) + }) + + it('should display a resolve incident button if the incident is in a reported state', async () => { + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ResolveIncident], + mockedIncident, + ) + const buttons = wrapper.find('Button[color="primary"]') expect(buttons.at(0).text().trim()).toEqual('incidents.reports.resolve') }) @@ -108,4 +135,28 @@ describe('View Incident', () => { { i18nKey: 'incidents.reports.view', location: '/incidents/1234' }, ]) }) + + it('should mark the status as resolved and fill in the resolved date with the current time', async () => { + const { wrapper, history } = await setup( + [Permissions.ViewIncident, Permissions.ResolveIncident], + mockedIncident, + ) + + const resolveButton = wrapper.find(Button).at(0) + await act(async () => { + const onClick = resolveButton.prop('onClick') as any + await onClick() + }) + wrapper.update() + + expect(incidentRepositorySaveSpy).toHaveBeenCalledTimes(1) + expect(incidentRepositorySaveSpy).toHaveBeenCalledWith( + expect.objectContaining({ + ...mockedIncident, + status: 'resolved', + resolvedOn: expectedResolveDate.toISOString(), + }), + ) + expect(history.location.pathname).toEqual('/incidents') + }) }) diff --git a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx index d1e75eb8bc..228c5e7802 100644 --- a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx +++ b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx @@ -138,43 +138,5 @@ describe('View Incident Details', () => { expect(descriptionTextInput.prop('label')).toEqual('incidents.reports.description') expect(descriptionTextInput.prop('value')).toEqual(expectedIncident.description) }) - - it('should not display a resolve incident button if the user has no access ResolveIncident access', async () => { - const { wrapper } = await setup(expectedIncident) - - const resolveButton = wrapper.find(Button) - expect(resolveButton).toHaveLength(0) - }) - - it('should not display a resolve incident button if the incident is resolved', async () => { - const mockIncident = { ...expectedIncident, status: 'resolved' } as Incident - const { wrapper } = await setup(mockIncident) - - const resolveButton = wrapper.find(Button) - expect(resolveButton).toHaveLength(0) - }) - }) - - describe('on resolve', () => { - it('should mark the status as resolved and fill in the resolved date with the current time', async () => { - const { wrapper, history } = await setup(expectedIncident) - - const resolveButton = wrapper.find(Button).at(0) - await act(async () => { - const onClick = resolveButton.prop('onClick') as any - await onClick() - }) - wrapper.update() - - expect(incidentRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(incidentRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - ...expectedIncident, - status: 'resolved', - resolvedOn: expectedResolveDate.toISOString(), - }), - ) - expect(history.location.pathname).toEqual('/incidents') - }) }) }) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 8f35b99db2..94314e65e4 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -102,7 +102,7 @@ const ViewIncident = () => {
From 0188b72c21997a2df027421bbc875a91b91a677d Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Fri, 5 Feb 2021 20:23:03 -0800 Subject: [PATCH 27/38] tests: fixed merge conflicts --- .../incidents/view/NotesTable.test.tsx | 92 +++++++++++++++++++ .../patients/notes/NotesTable.test.tsx | 58 ------------ 2 files changed, 92 insertions(+), 58 deletions(-) create mode 100644 src/__tests__/incidents/view/NotesTable.test.tsx delete mode 100644 src/__tests__/patients/notes/NotesTable.test.tsx diff --git a/src/__tests__/incidents/view/NotesTable.test.tsx b/src/__tests__/incidents/view/NotesTable.test.tsx new file mode 100644 index 0000000000..f9ccb02af5 --- /dev/null +++ b/src/__tests__/incidents/view/NotesTable.test.tsx @@ -0,0 +1,92 @@ +import { Alert, Table } from '@hospitalrun/components' +import { mount, ReactWrapper } from 'enzyme' +import React from 'react' +import { act } from 'react-dom/test-utils' + +import NotesTable from '../../../incidents/view/NotesTable' +import Note from '../../../shared/model/Note' + +describe('Notes Table', () => { + const setup = async (notes: Note[]) => { + const onEditSpy = jest.fn() + const onDeleteSpy = jest.fn() + + let wrapper: any + await act(async () => { + wrapper = await mount( + , + ) + }) + wrapper.update() + return { wrapper: wrapper as ReactWrapper, onEditSpy, onDeleteSpy } + } + + it('should render a notes table if at least note is in list.', async () => { + const { wrapper } = await setup([ + { + id: '1234', + date: new Date().toISOString(), + text: 'some text', + givenBy: 'some user', + }, + ]) + expect(wrapper.find(Table)).toHaveLength(1) + }) + it('should display edit and delete buttons if notes exist', async () => { + const { wrapper } = await setup([ + { + id: '1234', + date: new Date().toISOString(), + text: 'some text', + givenBy: 'some user', + }, + ]) + const notesTable = wrapper.find(Table) + expect(notesTable.prop('actions')).toEqual([ + expect.objectContaining({ label: 'actions.edit', buttonColor: 'dark' }), + expect.objectContaining({ label: 'actions.delete', buttonColor: 'danger' }), + ]) + }) + it('should display no notes message if no notes exist', async () => { + const { wrapper } = await setup([]) + const alert = wrapper.find(Alert) + expect(alert).toHaveLength(1) + expect(alert.prop('color')).toEqual('warning') + expect(alert.prop('title')).toEqual('patient.notes.warning.noNotes') + expect(wrapper.find(Table)).toHaveLength(0) + }) + it('calls on edit note when edit note button clicked', async () => { + const { wrapper, onEditSpy } = await setup([ + { + id: '1234', + date: new Date().toISOString(), + text: 'some text', + givenBy: 'some user', + }, + ]) + act(() => { + const table = wrapper.find(Table) as any + const onViewClick = table.prop('actions')[0].action as any + onViewClick() + }) + + expect(onEditSpy).toHaveBeenCalledTimes(1) + }) + it('calls on delete note when edit note button clicked', async () => { + const { wrapper, onDeleteSpy } = await setup([ + { + id: '1234', + date: new Date().toISOString(), + text: 'some text', + givenBy: 'some user', + }, + ]) + act(() => { + const table = wrapper.find(Table) as any + const onViewClick = table.prop('actions')[1].action as any + onViewClick() + }) + + expect(onDeleteSpy).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/__tests__/patients/notes/NotesTable.test.tsx b/src/__tests__/patients/notes/NotesTable.test.tsx deleted file mode 100644 index 9695b5637e..0000000000 --- a/src/__tests__/patients/notes/NotesTable.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { mount, ReactWrapper } from 'enzyme' -import NotesTable from '../../../incidents/view/NotesTable' -import React from 'react' -import { act } from 'react-dom/test-utils' -import Note from '../../../shared/model/Note' -import { Alert, Table } from '@hospitalrun/components' -describe('Notes Table', () => { - - const setup = async (notes: Note[]) => { - - let wrapper: any - await act(async () => { - wrapper = await mount( - {}} - onDeleteNote={() => {}} - notes={notes} - /> - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } - } - - it('should render a notes table if at least note is in list.', async () => { - const { wrapper } = await setup([{ - id: '1234', - date: new Date().toISOString(), - text: 'some text', - givenBy: 'some user', - }]); - expect(wrapper.find(Table)).toHaveLength(1) - }) - it('should display edit and delete buttons if notes exist', async () => { - const expectedNote = { - id: '1234', - date: new Date().toISOString(), - text: 'some12324 text', - givenBy: 'some user', - } as Note - }) - it('should display no notes message if no notes exist', async () => { - const { wrapper } = await setup([]); - const alert = wrapper.find(Alert) - expect(alert).toHaveLength(1) - expect(alert.prop("color")).toEqual("warning") - expect(alert.prop("title")).toEqual("patient.notes.warning.noNotes") - expect(wrapper.find(Table)).toHaveLength(0) - }) - it('calls on edit note when edit note button clicked', async () => { - - }) - it('calls on delete note when edit note button clicked', async () => { - - }) - it('should format the data correctly', async () => { - }) -}) \ No newline at end of file From 3aee48e4ff70cd4eec094c8b67a1a36456efb6f0 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Tue, 9 Feb 2021 14:55:32 -0700 Subject: [PATCH 28/38] test(notes): fix last incident test --- .../incidents/view/ViewIncident.test.tsx | 28 +++++++++++-------- src/incidents/hooks/useAddIncidentNote.tsx | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index 4a66658f0f..ba74c214ec 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -1,4 +1,4 @@ -import { Button, Tab, TabsHeader, Table } from '@hospitalrun/components' +import { Button, Tab, TabsHeader } from '@hospitalrun/components' import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' @@ -269,31 +269,35 @@ describe('View Incident', () => { }) wrapper.update() + const newNoteText = 'new note text' const modal = wrapper.find(NewNoteModal) - const successButton = modal.find('Button[color="success"]') - await act(async () => { const onChange = modal.find(TextFieldWithLabelFormGroup).prop('onChange') as any - await onChange({ currentTarget: { value: 'new note text' } }) + await onChange({ currentTarget: { value: newNoteText } }) + }) + wrapper.update() + + expect(wrapper.find(NewNoteModal).prop('note').text).toEqual(newNoteText) + const successButton = wrapper.find(NewNoteModal).find('Button[color="success"]') + expect(successButton).toHaveLength(1); + await act(async () => { const onClick = successButton.prop('onClick') as any - await onClick({ stopPropagation: jest.fn() }) + await onClick() }) wrapper.update() expect(incidentRepositorySaveSpy).toHaveBeenCalledTimes(1) expect(incidentRepositorySaveSpy).toHaveBeenCalledWith( expect.objectContaining({ - ...mockedIncident, - notes: [ - ...(mockedIncident.notes || []), - { + notes: expect.arrayContaining( + [{ id: '7777', date: expectedResolveDate.toISOString(), - text: 'new note text', + text: newNoteText, givenBy: '8542', - }, - ], + }] + ), }), ) }) diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx index 3be3441510..88faa7294d 100644 --- a/src/incidents/hooks/useAddIncidentNote.tsx +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -12,7 +12,6 @@ interface AddNoteRequest { async function addNote(request: AddNoteRequest): Promise { const error = [] as any // TODO validateNote(request.note) - if (isEmpty(error)) { const incident = await IncidentRepository.find(request.incidentId) const notes = incident.notes || [] From 6603ed8b77e0748976a4854683833a0b653fd6be Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Tue, 9 Feb 2021 16:19:09 -0700 Subject: [PATCH 29/38] remove dummy validation --- src/incidents/hooks/useAddIncidentNote.tsx | 36 +++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx index 88faa7294d..e85d6f3ecf 100644 --- a/src/incidents/hooks/useAddIncidentNote.tsx +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -3,7 +3,6 @@ import { queryCache, useMutation } from 'react-query' import IncidentRepository from '../../shared/db/IncidentRepository' import Note from '../../shared/model/Note' -// import validateNote from '../util/validate-note' interface AddNoteRequest { incidentId: string @@ -11,28 +10,23 @@ interface AddNoteRequest { } async function addNote(request: AddNoteRequest): Promise { - const error = [] as any // TODO validateNote(request.note) - if (isEmpty(error)) { - const incident = await IncidentRepository.find(request.incidentId) - const notes = incident.notes || [] - let noteIdx = notes.findIndex((note) => note.id === request.note.id) - if (noteIdx === -1) { - // This note is new. - notes.push(request.note) - } else { - // We're editing an already existing note. - notes[noteIdx] = request.note - } - - await IncidentRepository.saveOrUpdate({ - ...incident, - notes, - }) - - return notes + const incident = await IncidentRepository.find(request.incidentId) + const notes = incident.notes || [] + let noteIdx = notes.findIndex((note) => note.id === request.note.id) + if (noteIdx === -1) { + // This note is new. + notes.push(request.note) + } else { + // We're editing an already existing note. + notes[noteIdx] = request.note } - throw error + await IncidentRepository.saveOrUpdate({ + ...incident, + notes, + }) + + return notes } export default function useAddIncidentNote() { From 18e8fdf4dcc663ed1dbabca37d4c2614422fd271 Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Tue, 9 Feb 2021 16:25:20 -0700 Subject: [PATCH 30/38] revert package.json --- package.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce55f18538..8816f8befb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": false, "license": "MIT", "dependencies": { - "@hospitalrun/components": "~3.1.0", + "@hospitalrun/components": "~3.3.0", "@reduxjs/toolkit": "~1.5.0", "@types/escape-string-regexp": "~2.0.1", "@types/json2csv": "~5.0.1", @@ -97,7 +97,7 @@ "eslint-plugin-jest": "~24.1.0", "eslint-plugin-jsx-a11y": "~6.4.1", "eslint-plugin-prettier": "~3.3.0", - "eslint-plugin-react": "~7.21.0", + "eslint-plugin-react": "~7.22.0", "eslint-plugin-react-hooks": "~4.1.0", "history": "4.10.1", "husky": "~4.3.0", @@ -109,7 +109,7 @@ "redux-mock-store": "~1.5.4", "rimraf": "~3.0.2", "source-map-explorer": "^2.2.2", - "standard-version": "~9.0.0" + "standard-version": "~9.1.0" }, "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", @@ -120,7 +120,6 @@ "prepublishOnly": "npm run build", "test": "npm run translation:check && react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --detectOpenHandles", "test:ci": "cross-env CI=true react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --passWithNoTests", - "test:debug": "cross-env CI=true react-scripts --inspect-brk test --runInBand --no-cache", "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"scripts/check-translations/**/*.{js,ts}\"", "lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"scripts/check-translations/**/*.{js,ts}\" --fix", "lint-staged": "lint-staged", @@ -161,4 +160,4 @@ "git add ." ] } -} +} \ No newline at end of file From 25ed56f7cb65b6d902533d5590fbfd6f8b8c34c8 Mon Sep 17 00:00:00 2001 From: aguptamusic Date: Wed, 17 Mar 2021 11:30:42 -0700 Subject: [PATCH 31/38] test(incident): resolve anthony's comments on PR --- .../incidents/view/ViewIncident.test.tsx | 65 ++++++++++++------- src/incidents/hooks/useAddIncidentNote.tsx | 3 +- src/incidents/view/ViewIncident.tsx | 26 ++++---- src/patients/notes/NoteTab.tsx | 4 +- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index ba74c214ec..df010b1308 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -76,19 +76,17 @@ describe('View Incident', () => { let wrapper: any await act(async () => { wrapper = await mount( - - - - - - - - - - - - - , + + + + + + + + + + + , ) }) wrapper.update() @@ -198,21 +196,36 @@ describe('View Incident', () => { }) it('should display add new note button', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ReportIncident], + mockedIncident, + ) const button = wrapper.find('Button[color="success"]').at(0) expect(button.exists()).toBeTruthy() expect(button.text().trim()).toEqual('patient.notes.new') }) - it('should not display modal before new note button clicked', async () => { + it('should not display add new note button without permission to report', async () => { const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + const button = wrapper.find('Button[color="success"]').at(0) + expect(button.exists()).toBeFalsy() + }) + + it('should not display modal before new note button clicked', async () => { + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ReportIncident], + mockedIncident, + ) const modal = wrapper.find(NewNoteModal) expect(modal.prop('show')).toBeFalsy() }) it('should display modal after new note button clicked', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ReportIncident], + mockedIncident, + ) const newNoteButton = wrapper.find({ className: 'create-new-note-button' }).at(0) act(() => { @@ -225,7 +238,10 @@ describe('View Incident', () => { }) it('modal should appear when edit note is clicked', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ReportIncident], + mockedIncident, + ) const tableRow = wrapper.find('tr').at(1) await act(async () => { @@ -260,7 +276,10 @@ describe('View Incident', () => { }) it('new note should appear when new note is created', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) + const { wrapper } = await setup( + [Permissions.ViewIncident, Permissions.ReportIncident], + mockedIncident, + ) const newNoteButton = wrapper.find({ className: 'create-new-note-button' }).at(0) act(() => { @@ -280,7 +299,7 @@ describe('View Incident', () => { expect(wrapper.find(NewNoteModal).prop('note').text).toEqual(newNoteText) const successButton = wrapper.find(NewNoteModal).find('Button[color="success"]') - expect(successButton).toHaveLength(1); + expect(successButton).toHaveLength(1) await act(async () => { const onClick = successButton.prop('onClick') as any await onClick() @@ -290,14 +309,14 @@ describe('View Incident', () => { expect(incidentRepositorySaveSpy).toHaveBeenCalledTimes(1) expect(incidentRepositorySaveSpy).toHaveBeenCalledWith( expect.objectContaining({ - notes: expect.arrayContaining( - [{ + notes: expect.arrayContaining([ + { id: '7777', date: expectedResolveDate.toISOString(), text: newNoteText, givenBy: '8542', - }] - ), + }, + ]), }), ) }) diff --git a/src/incidents/hooks/useAddIncidentNote.tsx b/src/incidents/hooks/useAddIncidentNote.tsx index e85d6f3ecf..6dfde69268 100644 --- a/src/incidents/hooks/useAddIncidentNote.tsx +++ b/src/incidents/hooks/useAddIncidentNote.tsx @@ -1,4 +1,3 @@ -import { isEmpty } from 'lodash' import { queryCache, useMutation } from 'react-query' import IncidentRepository from '../../shared/db/IncidentRepository' @@ -12,7 +11,7 @@ interface AddNoteRequest { async function addNote(request: AddNoteRequest): Promise { const incident = await IncidentRepository.find(request.incidentId) const notes = incident.notes || [] - let noteIdx = notes.findIndex((note) => note.id === request.note.id) + const noteIdx = notes.findIndex((note) => note.id === request.note.id) if (noteIdx === -1) { // This note is new. notes.push(request.note) diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index cb196894ad..6689b7dced 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -75,18 +75,20 @@ const ViewIncident = () => { /> -
- -
+ {permissions.includes(Permissions.ReportIncident) && ( +
+ +
+ )} { setEditedNote(note) diff --git a/src/patients/notes/NoteTab.tsx b/src/patients/notes/NoteTab.tsx index 1ec3110f32..96740183f0 100644 --- a/src/patients/notes/NoteTab.tsx +++ b/src/patients/notes/NoteTab.tsx @@ -8,8 +8,8 @@ import useTranslator from '../../shared/hooks/useTranslator' import Note from '../../shared/model/Note' import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' -import { RootState } from '../../shared/store' import NewNoteModal from '../../shared/notes/NewNoteModal' +import { RootState } from '../../shared/store' import { uuid } from '../../shared/util/uuid' import useAddPatientNote from '../hooks/useAddPatientNote' import NotesList from './NotesList' @@ -26,7 +26,7 @@ const NoteTab = (props: Props) => { const [showNewNoteModal, setShowNoteModal] = useState(false) const defaultNoteValue = { id: uuid(), - givenBy: 'some user', // TODO + givenBy: 'some user', text: '', date: '', } From 8164f2e6109406a0b045979ddaf51bf1520693fb Mon Sep 17 00:00:00 2001 From: Drew Gregory Date: Fri, 30 Apr 2021 19:29:15 -0700 Subject: [PATCH 32/38] merge conflicts --- .../incidents/view/ViewIncident.test.tsx | 15 --------- src/incidents/view/ViewIncident.tsx | 6 +--- src/incidents/view/ViewIncidentDetails.tsx | 32 ++----------------- 3 files changed, 3 insertions(+), 50 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index a7f0946b5a..b89947d44a 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -1,15 +1,6 @@ -<<<<<<< HEAD -import { Button, Tab, TabsHeader } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' -import { createMemoryHistory } from 'history' -import React from 'react' -import { act } from 'react-dom/test-utils' -import { ReactQueryCacheProvider, QueryCache } from 'react-query' -======= import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' ->>>>>>> master import { Provider } from 'react-redux' import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -17,14 +8,8 @@ import thunk from 'redux-thunk' import ViewIncident from '../../../incidents/view/ViewIncident' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' -<<<<<<< HEAD -import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' -import * as titleUtil from '../../../page-header/title/TitleContext' -import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' -======= import { ButtonBarProvider } from '../../../page-header/button-toolbar/ButtonBarProvider' import { TitleProvider } from '../../../page-header/title/TitleContext' ->>>>>>> master import IncidentRepository from '../../../shared/db/IncidentRepository' import Incident from '../../../shared/model/Incident' import Note from '../../../shared/model/Note' diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index b5352bacac..8a22ef84aa 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -1,9 +1,5 @@ -<<<<<<< HEAD import { Button, Tab, Panel, TabsHeader } from '@hospitalrun/components' -import React, { useState } from 'react' -======= -import React, { useEffect } from 'react' ->>>>>>> master +import React, { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import { useParams, useHistory, useLocation } from 'react-router-dom' diff --git a/src/incidents/view/ViewIncidentDetails.tsx b/src/incidents/view/ViewIncidentDetails.tsx index a17b9b56dc..c4525ed1de 100644 --- a/src/incidents/view/ViewIncidentDetails.tsx +++ b/src/incidents/view/ViewIncidentDetails.tsx @@ -16,35 +16,12 @@ interface Props { function ViewIncidentDetails(props: Props) { const { incident, isLoading } = props const { t } = useTranslator() - const { data, isLoading } = useIncident(incidentId) - const { data: patient } = usePatient(data?.patient) - const [mutate] = useResolveIncident() + const { data: patient } = usePatient(incident?.patient) if (incident === undefined || isLoading) { return } - const onResolve = async () => { - await mutate(data) - history.push('/incidents') - } - - const getButtons = () => { - const buttons: React.ReactNode[] = [] - if (data.status === 'resolved') { - return buttons - } - - if (permissions.includes(Permissions.ResolveIncident)) { - buttons.push( - , - ) - } - - return buttons - } const getResolvedOnDate = () => { if (incident.status === 'resolved' && incident.resolvedOn) { @@ -124,7 +101,7 @@ function ViewIncidentDetails(props: Props) { /> - {data.patient && ( + {incident.patient && ( )} - {data.resolvedOn === undefined && ( -
-
{getButtons()}
-
- )} ) } From 62e4759db0dfb7f381d3c290d2922fb5062bb957 Mon Sep 17 00:00:00 2001 From: jrmkim50 Date: Sat, 1 May 2021 19:10:13 -0400 Subject: [PATCH 33/38] Removed a duplicate updateTitle in ViewIncident Co-authored-by: Drew Gregory --- src/__tests__/incidents/view/ViewIncident.test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index b89947d44a..36f8c040e1 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -65,12 +65,12 @@ it('should not render ViewIncidentDetails if there are no Permissions', async () ).not.toBeInTheDocument() }) - // it('should render tabs header', async () => { - // const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) +it('should render tabs header', async () => { + const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) - // const tabs = wrapper.find(TabsHeader) - // expect(tabs.exists()).toBeTruthy() - // }) + const tabs = wrapper.find(TabsHeader) + expect(tabs.exists()).toBeTruthy() +}) // it('should render notes tab when clicked', async () => { // const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) From eed0032555f66adcad4a178ce5eed7cb4de4e1a9 Mon Sep 17 00:00:00 2001 From: jrmkim50 Date: Sat, 1 May 2021 19:10:33 -0400 Subject: [PATCH 34/38] Removed a duplicate updateTitle in ViewIncident --- .../incidents/view/NotesTable.test.tsx | 115 +++++++----------- src/__tests__/labs/Labs.test.tsx | 2 +- src/incidents/view/ViewIncident.tsx | 2 - 3 files changed, 43 insertions(+), 76 deletions(-) diff --git a/src/__tests__/incidents/view/NotesTable.test.tsx b/src/__tests__/incidents/view/NotesTable.test.tsx index f9ccb02af5..173e0d151b 100644 --- a/src/__tests__/incidents/view/NotesTable.test.tsx +++ b/src/__tests__/incidents/view/NotesTable.test.tsx @@ -1,92 +1,61 @@ import { Alert, Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' import React from 'react' import { act } from 'react-dom/test-utils' import NotesTable from '../../../incidents/view/NotesTable' import Note from '../../../shared/model/Note' -describe('Notes Table', () => { - const setup = async (notes: Note[]) => { - const onEditSpy = jest.fn() - const onDeleteSpy = jest.fn() +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; + +const MOCK_NOTE = { + id: '1234', + date: new Date().toISOString(), + text: 'some text', + givenBy: 'some user', +} + +const setup = ( + notes: Note[] +) => { + + const onEditSpy = jest.fn(); + const onDeleteSpy = jest.fn(); + + render( + + ) - let wrapper: any - await act(async () => { - wrapper = await mount( - , - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, onEditSpy, onDeleteSpy } - } + return { onEditSpy, onDeleteSpy } +} + +describe('Notes Table', () => { it('should render a notes table if at least note is in list.', async () => { - const { wrapper } = await setup([ - { - id: '1234', - date: new Date().toISOString(), - text: 'some text', - givenBy: 'some user', - }, - ]) - expect(wrapper.find(Table)).toHaveLength(1) + setup([ MOCK_NOTE ] ) + + expect(screen.getByRole("table")).toBeInTheDocument(); }) it('should display edit and delete buttons if notes exist', async () => { - const { wrapper } = await setup([ - { - id: '1234', - date: new Date().toISOString(), - text: 'some text', - givenBy: 'some user', - }, - ]) - const notesTable = wrapper.find(Table) - expect(notesTable.prop('actions')).toEqual([ - expect.objectContaining({ label: 'actions.edit', buttonColor: 'dark' }), - expect.objectContaining({ label: 'actions.delete', buttonColor: 'danger' }), - ]) + setup([ MOCK_NOTE ] ) + expect(screen.getByRole("button", { name: "actions.edit" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "actions.delete" })).toBeInTheDocument(); }) + it('should display no notes message if no notes exist', async () => { - const { wrapper } = await setup([]) - const alert = wrapper.find(Alert) - expect(alert).toHaveLength(1) - expect(alert.prop('color')).toEqual('warning') - expect(alert.prop('title')).toEqual('patient.notes.warning.noNotes') - expect(wrapper.find(Table)).toHaveLength(0) + setup([]) + expect(screen.getByRole("alert")).toBeInTheDocument(); + expect(screen.getByText("patient.notes.warning.noNotes")).toBeInTheDocument(); }) - it('calls on edit note when edit note button clicked', async () => { - const { wrapper, onEditSpy } = await setup([ - { - id: '1234', - date: new Date().toISOString(), - text: 'some text', - givenBy: 'some user', - }, - ]) - act(() => { - const table = wrapper.find(Table) as any - const onViewClick = table.prop('actions')[0].action as any - onViewClick() - }) - expect(onEditSpy).toHaveBeenCalledTimes(1) + it('calls on edit note when edit note button clicked', async () => { + const { onEditSpy } = setup([ MOCK_NOTE ]) + fireEvent.click(screen.getByRole("button", { name: "actions.edit" })) + expect(onEditSpy).toHaveBeenCalled(); }) - it('calls on delete note when edit note button clicked', async () => { - const { wrapper, onDeleteSpy } = await setup([ - { - id: '1234', - date: new Date().toISOString(), - text: 'some text', - givenBy: 'some user', - }, - ]) - act(() => { - const table = wrapper.find(Table) as any - const onViewClick = table.prop('actions')[1].action as any - onViewClick() - }) - - expect(onDeleteSpy).toHaveBeenCalledTimes(1) + + it('calls on delete note when delete note button clicked', async () => { + const { onDeleteSpy } = setup([ MOCK_NOTE ]) + fireEvent.click(screen.getByRole("button", { name: "actions.delete" })); + expect(onDeleteSpy).toHaveBeenCalled(); }) }) diff --git a/src/__tests__/labs/Labs.test.tsx b/src/__tests__/labs/Labs.test.tsx index 38821a8df5..36f12e6cd1 100644 --- a/src/__tests__/labs/Labs.test.tsx +++ b/src/__tests__/labs/Labs.test.tsx @@ -20,7 +20,7 @@ const mockStore = createMockStore([thunk]) const Title = () => { const { title } = useTitle() - return

{title}

+ retur:n

{title}

} const expectedLab = { diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 8a22ef84aa..a1e5f3b99b 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -48,8 +48,6 @@ const ViewIncident = () => { }, ]) - updateTitle(t('incidents.reports.view')) - const onNewNoteClick = () => { setEditedNote(newNoteState) setShowNoteModal(true) From 10b409274eb316535fb3269a1df6ed5906ffd943 Mon Sep 17 00:00:00 2001 From: jrmkim50 Date: Sat, 1 May 2021 19:40:58 -0400 Subject: [PATCH 35/38] ViewIncidentTest edit note test in progress right now --- .../incidents/view/ViewIncident.test.tsx | 134 +++++++----------- 1 file changed, 51 insertions(+), 83 deletions(-) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index 36f8c040e1..f3995e83fe 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -16,18 +16,26 @@ import Note from '../../../shared/model/Note' import Permissions from '../../../shared/model/Permissions' import NewNoteModal from '../../../shared/notes/NewNoteModal' import { RootState } from '../../../shared/store' -import * as uuid from '../../../shared/util/uuid' const mockStore = createMockStore([thunk]) -const setup = (permissions: Permissions[], id: string | undefined) => { +const MOCK_NOTE = { + id: '1234', + date: new Date().toISOString(), + text: 'some text', + givenBy: 'some user', +} + +const setup = (permissions: Permissions[], id: string | undefined, notes: Note[] = []) => { jest.resetAllMocks() jest.spyOn(breadcrumbUtil, 'default') + console.log("NOTES", notes) jest.spyOn(IncidentRepository, 'find').mockResolvedValue({ id, date: new Date().toISOString(), code: 'some code', reportedOn: new Date().toISOString(), + notes } as Incident) const history = createMemoryHistory({ initialEntries: [`/incidents/${id}`] }) @@ -56,7 +64,7 @@ const setup = (permissions: Permissions[], id: string | undefined) => { } it('should not render ViewIncidentDetails if there are no Permissions', async () => { - setup(undefined, '1234') + setup([], '1234') expect( screen.queryByRole('heading', { @@ -66,83 +74,43 @@ it('should not render ViewIncidentDetails if there are no Permissions', async () }) it('should render tabs header', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) - - const tabs = wrapper.find(TabsHeader) - expect(tabs.exists()).toBeTruthy() + setup([Permissions.ViewIncident], '1234') + expect(screen.getByText("patient.notes.label")).toBeInTheDocument(); }) - // it('should render notes tab when clicked', async () => { - // const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) - - // const notesTab = wrapper.find(Tab) - // act(() => { - // const onClick = notesTab.prop('onClick') as any - // onClick() - // }) - // wrapper.update() - // expect(notesTab.exists()).toBeTruthy() - // expect(notesTab.prop('label')).toEqual('patient.notes.label') - // }) - - // it('should display add new note button', async () => { - // const { wrapper } = await setup( - // [Permissions.ViewIncident, Permissions.ReportIncident], - // mockedIncident, - // ) - - // const button = wrapper.find('Button[color="success"]').at(0) - // expect(button.exists()).toBeTruthy() - // expect(button.text().trim()).toEqual('patient.notes.new') - // }) - - // it('should not display add new note button without permission to report', async () => { - // const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) - // const button = wrapper.find('Button[color="success"]').at(0) - // expect(button.exists()).toBeFalsy() - // }) - - // it('should not display modal before new note button clicked', async () => { - // const { wrapper } = await setup( - // [Permissions.ViewIncident, Permissions.ReportIncident], - // mockedIncident, - // ) - // const modal = wrapper.find(NewNoteModal) - // expect(modal.prop('show')).toBeFalsy() - // }) - - // it('should display modal after new note button clicked', async () => { - // const { wrapper } = await setup( - // [Permissions.ViewIncident, Permissions.ReportIncident], - // mockedIncident, - // ) +it('should render notes tab and add new note button when clicked', async () => { + setup([Permissions.ViewIncident, Permissions.ReportIncident], '1234') + fireEvent.click(screen.getByText("patient.notes.label")) + expect(screen.getByRole("button", { name: "patient.notes.new" })).toBeInTheDocument(); +}) - // const newNoteButton = wrapper.find({ className: 'create-new-note-button' }).at(0) - // act(() => { - // const onClick = newNoteButton.prop('onClick') as any - // onClick() - // }) - // wrapper.update() - // const modal = wrapper.find(NewNoteModal) - // expect(modal.prop('show')).toBeTruthy() - // }) +it('should not display add new note button without permission to report', async () => { + setup([Permissions.ViewIncident], '1234') + fireEvent.click(screen.getByText("patient.notes.label")) + expect(screen.queryByRole("button", { name: "patient.notes.new" })).not.toBeInTheDocument(); +}) - // it('modal should appear when edit note is clicked', async () => { - // const { wrapper } = await setup( - // [Permissions.ViewIncident, Permissions.ReportIncident], - // mockedIncident, - // ) +it('should not display modal before new note button clicked', async () => { + setup([Permissions.ViewIncident, Permissions.ReportIncident], '1234') + fireEvent.click(screen.getByText("patient.notes.label")) + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); +}) - // const tableRow = wrapper.find('tr').at(1) - // await act(async () => { - // const onClick = tableRow.find('button').at(0).prop('onClick') as any - // await onClick({ stopPropagation: jest.fn() }) - // }) - // wrapper.update() +it('should display modal after new note button clicked', async () => { + setup([Permissions.ViewIncident, Permissions.ReportIncident], '1234'); + fireEvent.click(screen.getByText("patient.notes.label")) + fireEvent.click(screen.getByRole("button", { name: "patient.notes.new" })) + expect(screen.queryByRole("dialog")).toBeInTheDocument(); +}) - // const modal = wrapper.find(NewNoteModal) - // expect(modal.prop('show')).toBeTruthy() - // }) +// it('modal should appear when edit note is clicked', async () => { +// setup([Permissions.ViewIncident, Permissions.ReportIncident], '1234', [MOCK_NOTE]) +// fireEvent.click(screen.getByRole("button", { name: "actions.edit" })) +// await waitFor(() => { +// expect(screen.queryByRole("dialog")).toBeInTheDocument(); +// }) + +// }) // it('one note should disappear when delete button clicked', async () => { // const { wrapper } = await setup([Permissions.ViewIncident], mockedIncident) @@ -211,12 +179,12 @@ it('should render tabs header', async () => { // ) // }) - it('should not render ViewIncidentDetails when there is no ID', async () => { - setup([Permissions.ReportIncident, Permissions.ResolveIncident], undefined) + // it('should not render ViewIncidentDetails when there is no ID', async () => { + // setup([Permissions.ReportIncident, Permissions.ResolveIncident], undefined) - expect( - screen.queryByRole('heading', { - name: /incidents\.reports\.dateofincident/i, - }), - ).not.toBeInTheDocument() - }) + // expect( + // screen.queryByRole('heading', { + // name: /incidents\.reports\.dateofincident/i, + // }), + // ).not.toBeInTheDocument() + // }) From 1e6b05a75353fdef4799b7042279e42e7ace19a1 Mon Sep 17 00:00:00 2001 From: Anthony Perez Date: Sat, 19 Feb 2022 11:37:18 -0800 Subject: [PATCH 36/38] Merged incident-notes with most recent master branch --- .eslintrc.js | 1 + .github/autoapproval.yml | 7 -- .github/workflows/auto-merge.yml | 14 +++ .github/workflows/pr-updater.yml | 17 +++ package.json | 66 +++++----- src/__tests__/labs/ViewLab.test.tsx | 110 ++++++++++++++++- src/__tests__/labs/hooks/useUpdateLab.test.ts | 9 +- .../labs/requests/NewLabRequest.test.tsx | 10 +- .../requests/NewMedicationRequest.test.tsx | 42 +++++-- .../patients/diagnoses/Diagnoses.test.tsx | 40 +++--- .../patients/diagnoses/DiagnosesList.test.tsx | 42 ------- .../diagnoses/DiagnosisTable.test.tsx | 86 +++++++++++++ .../patients/diagnoses/ViewDiagnoses.test.tsx | 48 ++++++++ .../patients/diagnoses/ViewDiagnosis.test.tsx | 57 +++++++++ .../patients/edit/EditPatient.test.tsx | 2 + .../patients/hooks/useAddPatientNote.test.ts | 7 +- .../patients/hooks/useDiagnosis.test.tsx | 65 ++++++++++ src/__tests__/patients/patient-slice.test.ts | 5 + .../view/ImportantPatientInfo.test.tsx | 35 +++++- .../input/SelectWithLabelFormGroup.test.tsx | 78 ------------ src/imagings/requests/NewImagingRequest.tsx | 34 +++--- src/incidents/list/ViewIncidents.tsx | 16 ++- src/labs/ViewLab.tsx | 62 ++++++++-- src/labs/ViewLabs.tsx | 18 ++- src/labs/requests/NewLabRequest.tsx | 50 +++++--- src/medications/ViewMedication.tsx | 48 +++----- .../requests/NewMedicationRequest.tsx | 70 ++++++----- .../search/MedicationRequestSearch.tsx | 18 ++- src/patients/ContactInfo.tsx | 25 ++-- src/patients/GeneralInformation.tsx | 68 ++++++----- src/patients/care-goals/CareGoalForm.tsx | 113 ++++++++++------- src/patients/care-plans/CarePlanForm.tsx | 114 +++++++++++------- src/patients/diagnoses/Diagnoses.tsx | 33 +++-- src/patients/diagnoses/DiagnosesList.tsx | 41 ------- src/patients/diagnoses/DiagnosisForm.tsx | 64 +++++----- src/patients/diagnoses/DiagnosisTable.tsx | 71 +++++++++++ src/patients/diagnoses/ViewDiagnoses.tsx | 12 ++ src/patients/diagnoses/ViewDiagnosis.tsx | 31 +++++ src/patients/hooks/useDiagnosis.tsx | 18 +++ src/patients/util/set-patient-helper.ts | 1 + src/patients/view/ImportantPatientInfo.tsx | 2 +- src/patients/view/ViewPatient.tsx | 6 +- src/patients/visits/VisitForm.tsx | 17 ++- .../appointments/AppointmentDetailForm.tsx | 37 +++--- .../components/input/LanguageSelector.tsx | 22 ++-- src/shared/components/input/SelectOption.tsx | 6 + .../input/SelectWithLabelFormGroup.tsx | 59 --------- src/shared/components/input/index.tsx | 5 +- .../locales/enUs/translations/labs/index.ts | 1 + src/shared/model/Lab.ts | 3 +- src/shared/model/Note.ts | 5 +- src/shared/notes/NewNoteModal.tsx | 4 + 52 files changed, 1150 insertions(+), 665 deletions(-) delete mode 100644 .github/autoapproval.yml create mode 100644 .github/workflows/auto-merge.yml create mode 100644 .github/workflows/pr-updater.yml delete mode 100644 src/__tests__/patients/diagnoses/DiagnosesList.test.tsx create mode 100644 src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx create mode 100644 src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx create mode 100644 src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx create mode 100644 src/__tests__/patients/hooks/useDiagnosis.test.tsx delete mode 100644 src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx delete mode 100644 src/patients/diagnoses/DiagnosesList.tsx create mode 100644 src/patients/diagnoses/DiagnosisTable.tsx create mode 100644 src/patients/diagnoses/ViewDiagnoses.tsx create mode 100644 src/patients/diagnoses/ViewDiagnosis.tsx create mode 100644 src/patients/hooks/useDiagnosis.tsx create mode 100644 src/shared/components/input/SelectOption.tsx delete mode 100644 src/shared/components/input/SelectWithLabelFormGroup.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 2559631af4..e6831de61c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,7 @@ module.exports = { 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'prettier', + 'prettier/react', 'prettier/@typescript-eslint', 'plugin:prettier/recommended', 'eslint-config-prettier', diff --git a/.github/autoapproval.yml b/.github/autoapproval.yml deleted file mode 100644 index c568ceb323..0000000000 --- a/.github/autoapproval.yml +++ /dev/null @@ -1,7 +0,0 @@ -from_owner: - - dependabot-preview[bot] - - dependabot[bot] -required_labels: - - dependencies -apply_labels: - - autoapproved diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000000..84c4319c9c --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,14 @@ +name: Dependabot PR merge + +on: + pull_request: + +jobs: + auto-merge: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + target: minor + github-token: ${{ secrets.BOT_PAT }} diff --git a/.github/workflows/pr-updater.yml b/.github/workflows/pr-updater.yml new file mode 100644 index 0000000000..058b8106af --- /dev/null +++ b/.github/workflows/pr-updater.yml @@ -0,0 +1,17 @@ +name: PR update + +on: + push: + branches: + - master + +jobs: + autoupdate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: PR updater + uses: maxkomarychev/pr-updater-action@v1.0.0 + with: + # token: ${{ secrets.GH_TOKEN }} + token: ${{ secrets.BOT_PAT }} diff --git a/package.json b/package.json index 7c19d9deec..3efe2c61d9 100644 --- a/package.json +++ b/package.json @@ -6,37 +6,37 @@ "license": "MIT", "dependencies": { "@hospitalrun/components": "~3.4.0", - "@reduxjs/toolkit": "~1.5.0", + "@reduxjs/toolkit": "~1.7.0", "@types/escape-string-regexp": "~2.0.1", "@types/json2csv": "~5.0.1", "@types/pouchdb-find": "~6.3.4", - "bootstrap": "~4.6.0", - "date-fns": "~2.21.0", + "bootstrap": "~5.1.0", + "date-fns": "~2.28.0", "escape-string-regexp": "~4.0.0", - "i18next": "~20.2.0", + "i18next": "~21.6.0", "i18next-browser-languagedetector": "~6.1.0", "i18next-xhr-backend": "~3.2.2", "json2csv": "~5.0.1", "lodash": "^4.17.15", - "node-sass": "~5.0.0", + "node-sass": "~7.0.0", "pouchdb": "~7.2.1", "pouchdb-adapter-memory": "~7.2.1", "pouchdb-authentication": "~1.1.3", "pouchdb-find": "~7.2.1", "pouchdb-quick-search": "~1.3.0", "react": "~17.0.1", - "react-bootstrap": "~1.5.0", - "react-bootstrap-typeahead": "~5.1.0", + "react-bootstrap": "~1.6.0", + "react-bootstrap-typeahead": "~5.2.0", "react-dom": "~16.13.0", - "react-i18next": "~11.8.3", + "react-i18next": "~11.15.0", "react-query": "~2.25.2", "react-query-devtools": "~2.6.0", "react-redux": "~7.2.0", "react-router": "~5.2.0", - "react-router-dom": "~5.2.0", + "react-router-dom": "~5.3.0", "react-scripts": "~3.4.0", "redux": "~4.1.0", - "redux-thunk": "~2.3.0", + "redux-thunk": "~2.4.0", "relational-pouch": "~4.0.0", "shortid": "^2.2.15", "typescript": "~3.8.3", @@ -59,59 +59,59 @@ "Stefano Casasola" ], "devDependencies": { - "@commitlint/cli": "~12.1.0", - "@commitlint/config-conventional": "~12.1.0", - "@commitlint/core": "~12.1.0", - "@commitlint/prompt": "~12.1.1", - "@testing-library/dom": "~7.30.0", - "@testing-library/jest-dom": "~5.12.0", + "@commitlint/cli": "~16.0.1", + "@commitlint/config-conventional": "~16.0.0", + "@commitlint/core": "~15.0.0", + "@commitlint/prompt": "~16.0.0", + "@testing-library/dom": "~7.31.0", + "@testing-library/jest-dom": "~5.16.1", "@testing-library/react": "~11.2.2", - "@testing-library/react-hooks": "~4.0.0", + "@testing-library/react-hooks": "~7.0.0", "@testing-library/user-event": "~12.8.3", - "@types/jest": "~26.0.0", + "@types/jest": "~27.0.3", "@types/lodash": "^4.14.150", - "@types/node": "~15.0.1", + "@types/node": "~17.0.0", "@types/pouchdb": "~6.4.0", "@types/react": "~17.0.0", "@types/react-dom": "~17.0.0", "@types/react-redux": "~7.1.5", "@types/react-router": "~5.1.2", - "@types/react-router-dom": "~5.1.0", + "@types/react-router-dom": "~5.3.0", "@types/redux-mock-store": "~1.0.1", "@types/shortid": "^0.0.29", "@types/uuid": "^8.0.0", - "@types/validator": "~13.1.0", + "@types/validator": "~13.7.0", "@typescript-eslint/eslint-plugin": "~3.10.0", "@typescript-eslint/parser": "~3.10.0", - "chalk": "^4.0.0", + "chalk": "^5.0.0", "commitizen": "~4.2.0", "commitlint-config-cz": "~0.13.0", "cross-env": "~7.0.0", "cz-conventional-changelog": "~3.3.0", - "dateformat": "~4.5.0", + "dateformat": "~5.0.2", "eslint": "~6.8.0", "eslint-config-airbnb": "~18.2.0", "eslint-config-prettier": "~7.2.0", - "eslint-import-resolver-typescript": "~2.4.0", - "eslint-plugin-import": "~2.22.0", - "eslint-plugin-jest": "~24.3.0", - "eslint-plugin-jsx-a11y": "~6.4.1", + "eslint-import-resolver-typescript": "~2.5.0", + "eslint-plugin-import": "~2.25.1", + "eslint-plugin-jest": "~25.3.0", + "eslint-plugin-jsx-a11y": "~6.5.1", "eslint-plugin-prettier": "~3.4.0", - "eslint-plugin-react": "~7.23.0", - "eslint-plugin-react-hooks": "~4.1.0", + "eslint-plugin-react": "~7.28.0", + "eslint-plugin-react-hooks": "~4.3.0", "history": "4.10.1", - "husky": "~6.0.0", + "husky": "~7.0.0", "jest-canvas-mock": "~2.3.0", "jest-environment-jsdom-sixteen": "~2.0.0", - "lint-staged": "~10.5.0", - "memdown": "~6.0.0", + "lint-staged": "~12.1.2", + "memdown": "~6.1.0", "prettier": "~2.2.0", "react-select-event": "~5.3.0", "react-test-renderer": "~17.0.1", "redux-mock-store": "~1.5.4", "rimraf": "~3.0.2", "source-map-explorer": "^2.2.2", - "standard-version": "~9.2.0" + "standard-version": "~9.3.0" }, "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", diff --git a/src/__tests__/labs/ViewLab.test.tsx b/src/__tests__/labs/ViewLab.test.tsx index f4a49a1c08..10f524a3a5 100644 --- a/src/__tests__/labs/ViewLab.test.tsx +++ b/src/__tests__/labs/ViewLab.test.tsx @@ -1,5 +1,12 @@ import { Toaster } from '@hospitalrun/components' -import { render, screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react' +import { + act, + render, + screen, + waitFor, + waitForElementToBeRemoved, + within, +} from '@testing-library/react' import userEvent from '@testing-library/user-event' import format from 'date-fns/format' import { createMemoryHistory } from 'history' @@ -28,7 +35,7 @@ const mockPatient = { fullName: 'Full Name' } const setup = (lab?: Partial, permissions = [Permissions.ViewLab], error = {}) => { const expectedDate = new Date() - const mockLab = { + let mockLab = { ...{ code: 'L-1234', id: '12456', @@ -44,7 +51,10 @@ const setup = (lab?: Partial, permissions = [Permissions.ViewLab], error = jest.resetAllMocks() Date.now = jest.fn(() => expectedDate.valueOf()) jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient as Patient) - jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(mockLab) + jest.spyOn(LabRepository, 'saveOrUpdate').mockImplementation(async (newOrUpdatedLab) => { + mockLab = newOrUpdatedLab + return mockLab + }) jest.spyOn(LabRepository, 'find').mockResolvedValue(mockLab) const history = createMemoryHistory({ initialEntries: [`/labs/${mockLab.id}`] }) @@ -142,11 +152,29 @@ describe('View Lab', () => { expect(screen.queryAllByTestId('note')).toHaveLength(0) }) - it('should display the past notes', async () => { - const expectedNotes = 'expected notes' + it('should display the past notes that are not deleted', async () => { + const expectedNotes = { + id: 'test-note-id', + date: new Date().toISOString(), + text: 'expected notes', + deleted: false, + } + setup({ notes: [expectedNotes] }) - expect(await screen.findByTestId('note')).toHaveTextContent(expectedNotes) + expect(await screen.findByTestId('note')).toHaveTextContent(expectedNotes.text) + }) + + it('should not display the past notes that are deleted', async () => { + const deletedNote = { + id: 'test-note-id', + date: new Date().toISOString(), + text: 'deleted note', + deleted: true, + } + + setup({ notes: [deletedNote] }) + expect(await screen.queryByText('deleted note')).toBe(null) }) it('should display the notes text field empty', async () => { @@ -308,6 +336,32 @@ describe('View Lab', () => { expect(screen.getByLabelText(/labs\.lab\.result/i)).toHaveTextContent(expectedResult) expect(screen.getByTestId('note')).toHaveTextContent(newNotes) }) + + it('should be able delete an note from the lab', async () => { + const notes = [ + { + id: 'earth-test-id', + date: new Date().toISOString(), + text: 'Hello earth, first note', + deleted: false, + }, + { + id: 'mars-test-id', + date: new Date().toISOString(), + text: 'Hello mars, second note', + deleted: false, + }, + ] + setup({ notes }) + + expect(await screen.findByText(notes[0].text)).toBeInTheDocument() + expect(await screen.findByText(notes[1].text)).toBeInTheDocument() + + act(() => userEvent.click(screen.getByTestId(`delete-note-${notes[1].id}`))) + + expect(await screen.findByText(notes[0].text)).toBeInTheDocument() + expect(await screen.queryByText(notes[1].text)).not.toBeInTheDocument() + }) }) describe('on complete', () => { @@ -335,6 +389,28 @@ describe('View Lab', () => { within(screen.getByRole('alert')).getByText(/labs\.successfullyCompleted/i), ).toBeInTheDocument() }) + + it('should disallow deleting notes', async () => { + const labNote = { + id: 'earth-test-id', + date: new Date().toISOString(), + text: 'A note from the lab!', + deleted: false, + } + + setup( + { + notes: [labNote], + status: 'completed', + }, + [Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab], + ) + + const notes = await screen.findAllByTestId('note') + expect(notes).toHaveLength(1) + expect(notes[0]).toHaveTextContent(labNote.text) + expect(screen.queryAllByRole('button', { name: 'Delete' })).toHaveLength(0) + }) }) describe('on cancel', () => { @@ -360,5 +436,27 @@ describe('View Lab', () => { expect(history.location.pathname).toEqual('/labs') }) }) + + it('should disallow deleting notes', async () => { + const labNote = { + id: 'earth-test-id', + date: new Date().toISOString(), + text: 'A note from the lab!', + deleted: false, + } + + setup( + { + notes: [labNote], + status: 'canceled', + }, + [Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab], + ) + + const notes = await screen.findAllByTestId('note') + expect(notes).toHaveLength(1) + expect(notes[0]).toHaveTextContent(labNote.text) + expect(screen.queryAllByRole('button', { name: 'Delete' })).toHaveLength(0) + }) }) }) diff --git a/src/__tests__/labs/hooks/useUpdateLab.test.ts b/src/__tests__/labs/hooks/useUpdateLab.test.ts index 5ae054a78d..a59d493be3 100644 --- a/src/__tests__/labs/hooks/useUpdateLab.test.ts +++ b/src/__tests__/labs/hooks/useUpdateLab.test.ts @@ -6,7 +6,14 @@ import executeMutation from '../../test-utils/use-mutation.util' describe('Use update lab', () => { const expectedLab = { type: 'some type', - notes: ['some note'], + notes: [ + { + id: 'test-note-id', + date: new Date().toISOString(), + text: 'Hi, this is an example test note', + deleted: false, + }, + ], } as Lab it('should update lab', async () => { diff --git a/src/__tests__/labs/requests/NewLabRequest.test.tsx b/src/__tests__/labs/requests/NewLabRequest.test.tsx index 9cfa2b75a7..011fb1e90a 100644 --- a/src/__tests__/labs/requests/NewLabRequest.test.tsx +++ b/src/__tests__/labs/requests/NewLabRequest.test.tsx @@ -30,7 +30,13 @@ const setup = ( } as any), ) => { const expectedDate = new Date() - const expectedNotes = 'expected notes' + const expectedNotes = { + id: 'test-note-id', + date: new Date().toISOString(), + text: 'Hi, this is an example test note', + deleted: false, + } + const expectedLab = { patient: '1234567', type: 'expected type', @@ -225,7 +231,7 @@ describe('New Lab Request', () => { expect(await screen.findByText(/jim bob/i)).toBeVisible() userEvent.click(screen.getByText(/jim bob/i)) userEvent.type(screen.getByLabelText(/labs\.lab\.type/i), expectedLab.type) - userEvent.type(screen.getByLabelText(/labs\.lab\.notes/i), (expectedLab.notes as string[])[0]) + userEvent.type(screen.getByLabelText(/labs\.lab\.notes/i), expectedLab?.notes?.[0].text) userEvent.click(screen.getByRole('button', { name: /labs\.requests\.new/i })) expect(await screen.findByRole('alert')).toBeInTheDocument() diff --git a/src/__tests__/medications/requests/NewMedicationRequest.test.tsx b/src/__tests__/medications/requests/NewMedicationRequest.test.tsx index 82057c4532..1082f3aa98 100644 --- a/src/__tests__/medications/requests/NewMedicationRequest.test.tsx +++ b/src/__tests__/medications/requests/NewMedicationRequest.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, within } from '@testing-library/react' +import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' @@ -10,6 +10,8 @@ import thunk from 'redux-thunk' import NewMedicationRequest from '../../../medications/requests/NewMedicationRequest' import * as titleUtil from '../../../page-header/title/TitleContext' +import PatientRepository from '../../../shared/db/PatientRepository' +import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' const mockStore = createMockStore([thunk]) @@ -176,6 +178,15 @@ describe('New Medication Request', () => { describe('on save', () => { it('should save the medication request and navigate to "/medications/:id"', async () => { const { history } = setup() + + jest.spyOn(PatientRepository, 'search').mockResolvedValue([ + { + id: 'batman', + fullName: 'Bruce Wayne', + code: 'test code', + } as Patient, + ]) + const patient = screen.getByPlaceholderText(/medications\.medication\.patient/i) const medication = screen.getByLabelText(/medications\.medication\.medication/i) const medicationNotes = screen.getByRole('textbox', { @@ -184,20 +195,35 @@ describe('New Medication Request', () => { const medStatus = within(screen.getByTestId('statusSelect')).getByRole('combobox') const medicationIntent = within(screen.getByTestId('intentSelect')).getByRole('combobox') const medicationPriority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + const quantityValue = screen.getByLabelText(/quantityValue/) + const quantityUnit = screen.getByLabelText(/quantityUnit/) - userEvent.type(patient, 'Bruce Wayne') + userEvent.type(patient, 'Bruce') + await selectEvent.select(patient, /Bruce/) userEvent.type(medication, 'Ibuprofen') userEvent.type(medicationNotes, 'Be warned he is Batman') - selectEvent.create(medStatus, 'active') - selectEvent.create(medicationIntent, 'order') - selectEvent.create(medicationPriority, 'urgent') - + await selectEvent.select(medStatus, /active/) + await selectEvent.select(medicationIntent, /order/) + await selectEvent.select(medicationPriority, /urgent/) + userEvent.type(quantityUnit, '200') + userEvent.type(quantityValue, 'mg') userEvent.click( screen.getByRole('button', { name: /medications\.requests\.new/i, }), ) - expect(history.location.pathname).toEqual('/medications/new') - }) + + await waitFor(() => { + expect(history.location.pathname).toMatch( + /\/medications\/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{11}/, + ) + }) + + expect(medStatus).toHaveValue('medications.status.active') + expect(medicationIntent).toHaveValue('medications.intent.order') + expect(medicationPriority).toHaveValue('medications.priority.urgent') + expect(quantityUnit).toHaveValue('200') + expect(quantityValue).toHaveValue('mg') + }, 20000) }) }) diff --git a/src/__tests__/patients/diagnoses/Diagnoses.test.tsx b/src/__tests__/patients/diagnoses/Diagnoses.test.tsx index d239fade69..5318059b50 100644 --- a/src/__tests__/patients/diagnoses/Diagnoses.test.tsx +++ b/src/__tests__/patients/diagnoses/Diagnoses.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' -import { Router } from 'react-router-dom' +import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -22,20 +22,21 @@ const expectedPatient = { } as Patient const mockStore = createMockStore([thunk]) -const history = createMemoryHistory() -let user: any -let store: any +const setup = async (patient = expectedPatient, permissions = [Permissions.AddDiagnosis]) => { + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + const store = mockStore({ user: { permissions } } as any) + const history = createMemoryHistory() + history.push(`/patients/${patient.id}/diagnoses`) -const setup = (patient = expectedPatient, permissions = [Permissions.AddDiagnosis]) => { - user = { permissions } - store = mockStore({ patient, user } as any) return render( - - - - - , + + + + + + + , ) } @@ -46,31 +47,32 @@ describe('Diagnoses', () => { jest.spyOn(PatientRepository, 'saveOrUpdate') }) - it('should render a add diagnoses button', () => { + it('should render an add diagnoses button', async () => { setup() expect( - screen.getByRole('button', { - name: /patient\.diagnoses\.new/i, + await screen.findByRole('button', { + name: /patient.diagnoses.new/i, }), ).toBeInTheDocument() }) it('should not render a diagnoses button if the user does not have permissions', () => { setup(expectedPatient, []) + expect( screen.queryByRole('button', { - name: /patient\.diagnoses\.new/i, + name: /patient.diagnoses.new/i, }), ).not.toBeInTheDocument() }) - it('should open the Add Diagnosis Modal', () => { + it('should open the Add Diagnosis Modal', async () => { setup() userEvent.click( - screen.getByRole('button', { - name: /patient\.diagnoses\.new/i, + await screen.findByRole('button', { + name: /patient.diagnoses.new/i, }), ) diff --git a/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx b/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx deleted file mode 100644 index 5c4bf8fddc..0000000000 --- a/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react' -import React from 'react' - -import DiagnosesList from '../../../patients/diagnoses/DiagnosesList' -import PatientRepository from '../../../shared/db/PatientRepository' -import Diagnosis from '../../../shared/model/Diagnosis' -import Patient from '../../../shared/model/Patient' - -const expectedDiagnoses = [ - { id: '123', name: 'diagnosis1', diagnosisDate: new Date().toISOString() } as Diagnosis, -] - -describe('Diagnoses list', () => { - const setup = async (diagnoses: Diagnosis[]) => { - jest.resetAllMocks() - const mockPatient = { id: '123', diagnoses } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(mockPatient) - - return render() - } - - it('should list the patients diagnoses', async () => { - const diagnoses = expectedDiagnoses as Diagnosis[] - const { container } = await setup(diagnoses) - await waitFor(() => { - expect(container.querySelector('.list-group')).toBeInTheDocument() - }) - const listItems = container.querySelectorAll('.list-group-item') - expect(listItems).toHaveLength(expectedDiagnoses.length) - expect(listItems[0]).toHaveTextContent(expectedDiagnoses[0].name) - }) - - it('should render a warning message if the patient does not have any diagnoses', async () => { - const { container } = await setup([]) - const alert = await screen.findByRole('alert') - expect(alert).toBeInTheDocument() - expect(container.querySelector('.list-group')).not.toBeInTheDocument() - expect(alert).toHaveClass('alert-warning') - expect(screen.getByText(/patient.diagnoses.warning.noDiagnoses/i)).toBeInTheDocument() - expect(screen.getByText(/patient.diagnoses.addDiagnosisAbove/i)).toBeInTheDocument() - }) -}) diff --git a/src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx b/src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx new file mode 100644 index 0000000000..7b3b59f1c4 --- /dev/null +++ b/src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx @@ -0,0 +1,86 @@ +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { createMemoryHistory } from 'history' +import React from 'react' +import { Route, Router, Switch } from 'react-router-dom' + +import DiagnosisTable from '../../../patients/diagnoses/DiagnosisTable' +import ViewDiagnosis from '../../../patients/diagnoses/ViewDiagnosis' +import PatientRepository from '../../../shared/db/PatientRepository' +import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis' +import Patient from '../../../shared/model/Patient' + +const diagnosis = { + id: '456', + name: 'diagnosis name', + diagnosisDate: new Date(2020, 12, 2).toISOString(), + onsetDate: new Date(2020, 12, 3).toISOString(), + abatementDate: new Date(2020, 12, 4).toISOString(), + status: DiagnosisStatus.Active, + note: 'note', + visit: 'visit', +} as Diagnosis + +const expectedPatient = { + id: '123', + diagnoses: [diagnosis], +} as Patient + +const setup = async (patient = expectedPatient) => { + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + const history = createMemoryHistory() + history.push(`/patients/${patient.id}/diagnoses/${diagnosis.id}`) + + return { + history, + ...render( + + + + + + + + , + ), + } +} + +describe('Diagnosis Table', () => { + it('should render a table', async () => { + setup() + await screen.findByRole('table') + + const columnHeaders = screen.getAllByRole('columnheader') + expect(columnHeaders[0]).toHaveTextContent(/patient.diagnoses.diagnosisName/i) + expect(columnHeaders[1]).toHaveTextContent(/patient.diagnoses.diagnosisDate/i) + expect(columnHeaders[2]).toHaveTextContent(/patient.diagnoses.onsetDate/i) + expect(columnHeaders[3]).toHaveTextContent(/patient.diagnoses.abatementDate/i) + expect(columnHeaders[4]).toHaveTextContent(/patient.diagnoses.status/i) + expect(columnHeaders[5]).toHaveTextContent(/actions.label/i) + + expect(await screen.findByRole('button', { name: /actions.view/i })).toBeInTheDocument() + }) + + it('should navigate to the diagnosis view when the view details button is clicked', async () => { + const { history } = await setup() + await screen.findByRole('table') + + userEvent.click(await screen.findByRole('button', { name: /actions.view/i })) + expect(history.location.pathname).toEqual( + `/patients/${expectedPatient.id}/diagnoses/${diagnosis.id}`, + ) + + const form = await screen.findByRole('form') + expect( + within(form).getByPlaceholderText(/patient.diagnoses.diagnosisName/i), + ).toBeInTheDocument() + }) + + it('should display a warning if there are no diagnoses', async () => { + await setup({ ...expectedPatient, diagnoses: [] }) + + expect(await screen.findByText(/patient.diagnoses.warning.noDiagnoses/i)).toBeInTheDocument() + expect(await screen.findByText(/patient.diagnoses.addDiagnosisAbove/i)).toBeInTheDocument() + }) +}) diff --git a/src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx b/src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx new file mode 100644 index 0000000000..e5487b9923 --- /dev/null +++ b/src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx @@ -0,0 +1,48 @@ +import { render, screen } from '@testing-library/react' +import { createMemoryHistory } from 'history' +import React from 'react' +import { Route, Router } from 'react-router-dom' + +import ViewDiagnoses from '../../../patients/diagnoses/ViewDiagnoses' +import PatientRepository from '../../../shared/db/PatientRepository' +import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis' +import Patient from '../../../shared/model/Patient' + +const diagnosis = { + id: '456', + name: 'diagnosis name', + diagnosisDate: new Date(2020, 12, 2).toISOString(), + onsetDate: new Date(2020, 12, 3).toISOString(), + abatementDate: new Date(2020, 12, 4).toISOString(), + status: DiagnosisStatus.Active, + note: 'note', + visit: 'visit', +} as Diagnosis + +const expectedPatient = { + id: '123', + diagnoses: [diagnosis], +} as Patient + +const setup = async () => { + jest.resetAllMocks() + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + + const history = createMemoryHistory() + history.push(`/patients/${expectedPatient.id}/diagnoses`) + + return render( + + + + + , + ) +} + +describe('View Diagnoses', () => { + it('should render a diagnoses table with the patient id', async () => { + setup() + expect(await screen.findByRole('table')).toBeInTheDocument() + }) +}) diff --git a/src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx b/src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx new file mode 100644 index 0000000000..c1cfd10b28 --- /dev/null +++ b/src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx @@ -0,0 +1,57 @@ +import { render, screen, waitForElementToBeRemoved } from '@testing-library/react' +import { createMemoryHistory } from 'history' +import React from 'react' +import { Route, Router } from 'react-router-dom' + +import ViewDiagnosis from '../../../patients/diagnoses/ViewDiagnosis' +import PatientRepository from '../../../shared/db/PatientRepository' +import Diagnosis from '../../../shared/model/Diagnosis' +import Patient from '../../../shared/model/Patient' + +const diagnosis = { + id: '123', + name: 'some name', + diagnosisDate: new Date().toISOString(), +} as Diagnosis + +const expectedPatient = { + id: 'patientId', + diagnoses: [diagnosis], +} as Patient + +const setup = () => { + jest.resetAllMocks() + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + const history = createMemoryHistory() + history.push(`/patients/${expectedPatient.id}/diagnoses/${diagnosis.id}`) + + return render( + + + + + , + ) +} + +describe('View Diagnosis', () => { + it('should render the loading spinner only while diagnosis data is being fetched', async () => { + const { container } = setup() + + expect(container.querySelector(`[class^='css-']`)).toBeInTheDocument() + await waitForElementToBeRemoved(container.querySelector('.css-0')) + expect(container.querySelector(`[class^='css-']`)).not.toBeInTheDocument() + }) + + it('should render the diagnosis name', async () => { + setup() + + expect(await screen.findByRole('heading', { name: diagnosis.name })).toBeInTheDocument() + }) + + it('should render a diagnosis form', async () => { + setup() + + expect(await screen.findByRole('form')).toBeInTheDocument() + }) +}) diff --git a/src/__tests__/patients/edit/EditPatient.test.tsx b/src/__tests__/patients/edit/EditPatient.test.tsx index cce690b662..75c6a63692 100644 --- a/src/__tests__/patients/edit/EditPatient.test.tsx +++ b/src/__tests__/patients/edit/EditPatient.test.tsx @@ -12,6 +12,7 @@ import * as titleUtil from '../../../page-header/title/TitleContext' import EditPatient from '../../../patients/edit/EditPatient' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' +import Visit from '../../../shared/model/Visit' import { RootState } from '../../../shared/store' const mockStore = createMockStore([thunk]) @@ -33,6 +34,7 @@ const patient = { code: 'P00001', dateOfBirth: subDays(new Date(), 2).toISOString(), index: 'Bruce Banner MDP00001', + visits: [] as Visit[], } as Patient const setup = () => { diff --git a/src/__tests__/patients/hooks/useAddPatientNote.test.ts b/src/__tests__/patients/hooks/useAddPatientNote.test.ts index 1665d8f593..00add99c71 100644 --- a/src/__tests__/patients/hooks/useAddPatientNote.test.ts +++ b/src/__tests__/patients/hooks/useAddPatientNote.test.ts @@ -28,7 +28,12 @@ describe('use add note', () => { }) it('should add the note to the patient', async () => { - const expectedNote = { id: '456', text: 'eome name', date: '1947-09-09T14:48:00.000Z' } + const expectedNote = { + id: '456', + text: 'eome name', + date: '1947-09-09T14:48:00.000Z', + deleted: false, + } const givenPatient = { id: 'patientId', notes: [] as Note[] } as Patient jest.spyOn(uuid, 'uuid').mockReturnValue(expectedNote.id) const expectedPatient = { ...givenPatient, notes: [expectedNote] } diff --git a/src/__tests__/patients/hooks/useDiagnosis.test.tsx b/src/__tests__/patients/hooks/useDiagnosis.test.tsx new file mode 100644 index 0000000000..8527c78342 --- /dev/null +++ b/src/__tests__/patients/hooks/useDiagnosis.test.tsx @@ -0,0 +1,65 @@ +import useDiagnosis from '../../../patients/hooks/useDiagnosis' +import PatientRepository from '../../../shared/db/PatientRepository' +import Diagnosis from '../../../shared/model/Diagnosis' +import Patient from '../../../shared/model/Patient' +import executeQuery from '../../test-utils/use-query.util' + +describe('useDiagnosis', () => { + let errorMock: jest.SpyInstance + + beforeEach(() => { + jest.resetAllMocks() + errorMock = jest.spyOn(console, 'error').mockImplementation() + }) + + afterEach(() => { + errorMock.mockRestore() + }) + + it('should return a diagnosis successfully given a valid patient id and diagnosis id', async () => { + const expectedPatientId = 'patientId' + const expectedDiagnosis = { id: 'diagnosisId', name: 'diagnosis name' } as Diagnosis + const expectedPatient = { id: expectedPatientId, diagnoses: [expectedDiagnosis] } as Patient + jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient) + + const actualDiagnosis = await executeQuery(() => + useDiagnosis(expectedPatientId, expectedDiagnosis.id), + ) + + expect(PatientRepository.find).toHaveBeenCalledTimes(1) + expect(PatientRepository.find).toHaveBeenCalledWith(expectedPatientId) + expect(actualDiagnosis).toEqual(expectedDiagnosis) + }) + + it('should throw an error if patient id is not valid', async () => { + const expectedPatientId = 'patientId' + const expectedDiagnosisId = 'diagnosisId' + jest.spyOn(PatientRepository, 'find').mockRejectedValueOnce(new Error('Patient not found')) + + try { + await executeQuery( + () => useDiagnosis(expectedPatientId, expectedDiagnosisId), + (queryResult) => queryResult.isError, + ) + } catch (e) { + expect(e).toEqual(new Error('Patient not found')) + } + }) + + it('should throw an error if patient id is valid but diagnosis id is not', async () => { + const expectedPatientId = 'patientId' + const expectedDiagnosisId = 'diagnosisId' + const actualDiagnosis = { id: 'actual diagnosisId', name: 'actual diagnosis name' } as Diagnosis + const expectedPatient = { id: expectedPatientId, diagnoses: [actualDiagnosis] } as Patient + jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient) + + try { + await executeQuery( + () => useDiagnosis(expectedPatientId, expectedDiagnosisId), + (queryResult) => queryResult.isError, + ) + } catch (e) { + expect(e).toEqual(new Error('Diagnosis not found')) + } + }) +}) diff --git a/src/__tests__/patients/patient-slice.test.ts b/src/__tests__/patients/patient-slice.test.ts index a27e2afcb1..2dcd11a741 100644 --- a/src/__tests__/patients/patient-slice.test.ts +++ b/src/__tests__/patients/patient-slice.test.ts @@ -121,12 +121,15 @@ describe('patients slice', () => { it('should call the PatientRepository save method with the correct patient', async () => { const store = mockStore() jest.spyOn(PatientRepository, 'save').mockResolvedValue({ id: 'sliceId1' } as Patient) + const expectedPatient = { id: 'sliceId1', givenName: 'some name', fullName: 'some name', } as Patient + expectedPatient.visits = [] + await store.dispatch(createPatient(expectedPatient)) expect(PatientRepository.save).toHaveBeenCalledWith(expectedPatient) @@ -309,7 +312,9 @@ describe('patients slice', () => { id: expectedPatientId, givenName: 'some name', fullName: 'some name', + visits: [] as Visit[], } as Patient + jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(expectedPatient) await store.dispatch(updatePatient(expectedPatient)) diff --git a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx index 815effb7ff..0c319d6818 100644 --- a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx +++ b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx @@ -4,10 +4,11 @@ import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' -import { Router } from 'react-router-dom' +import { Router, Route, Switch } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' +import ViewDiagnosis from '../../../patients/diagnoses/ViewDiagnosis' import ImportantPatientInfo from '../../../patients/view/ImportantPatientInfo' import PatientRepository from '../../../shared/db/PatientRepository' import CarePlan from '../../../shared/model/CarePlan' @@ -23,15 +24,19 @@ describe('Important Patient Info Panel', () => { let user: any let store: any + const diagnosis = { + id: 'diagnosisId', + name: 'diagnosis1', + diagnosisDate: new Date().toISOString(), + } as Diagnosis + 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, - ], + diagnoses: [diagnosis], allergies: [ { id: '1', name: 'allergy1' }, { id: '2', name: 'allergy2' }, @@ -63,6 +68,11 @@ describe('Important Patient Info Panel', () => { + + + + + , ) @@ -182,4 +192,21 @@ describe('Important Patient Info Panel', () => { ).not.toBeInTheDocument() }) }) + + describe('patient diagnosis routing', () => { + it('should render the diagnosis form when the table row is clicked', async () => { + setup(expectedPatient, []) + + const rows = await screen.findAllByRole('row') + userEvent.click(rows[2]) + expect(history.location.pathname).toEqual( + `/patients/${expectedPatient.id}/diagnoses/${diagnosis.id}`, + ) + + const form = await screen.findByRole('form') + expect( + within(form).getByPlaceholderText(/patient.diagnoses.diagnosisName/i), + ).toBeInTheDocument() + }) + }) }) diff --git a/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx b/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx deleted file mode 100644 index 63eacf5ded..0000000000 --- a/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { render, screen } from '@testing-library/react' -import userEvent, { specialChars } from '@testing-library/user-event' -import React from 'react' - -import SelectWithLabelFormGroup from '../../../../shared/components/input/SelectWithLabelFormGroup' - -const { arrowDown, enter } = specialChars - -describe('select with label form group', () => { - describe('layout', () => { - it('should render a label', () => { - const expectedName = 'test' - render( - , - ) - - expect(screen.getByText(expectedName)).toHaveAttribute('for', `${expectedName}Select`) - }) - - it('should render disabled is isDisable disabled is true', () => { - render( - , - ) - - expect(screen.getByRole('combobox')).toBeDisabled() - }) - - it('should render the proper value', () => { - const expectedLabel = 'label' - render( - , - ) - - expect(screen.getByRole('combobox')).toHaveValue(expectedLabel) - }) - }) - - describe('change handler', () => { - it('should call the change handler on change', () => { - const expectedLabel = 'label1' - const expectedValue = 'value1' - const handler = jest.fn() - render( - , - ) - - userEvent.type(screen.getByRole('combobox'), `${expectedLabel}${arrowDown}${enter}`) - - expect(handler).toHaveBeenCalledWith([expectedValue]) - expect(handler).toHaveBeenCalledTimes(1) - }) - }) -}) diff --git a/src/imagings/requests/NewImagingRequest.tsx b/src/imagings/requests/NewImagingRequest.tsx index 28d6d2c69f..f6afe5eeb5 100644 --- a/src/imagings/requests/NewImagingRequest.tsx +++ b/src/imagings/requests/NewImagingRequest.tsx @@ -1,4 +1,4 @@ -import { Typeahead, Label, Button, Alert, Column, Row } from '@hospitalrun/components' +import { Select, Typeahead, Label, Button, Alert, Column, Row } from '@hospitalrun/components' import format from 'date-fns/format' import React, { useState, useEffect } from 'react' import { useSelector } from 'react-redux' @@ -6,9 +6,7 @@ import { useHistory } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' -import SelectWithLabelFormGroup, { - Option, -} from '../../shared/components/input/SelectWithLabelFormGroup' +import { SelectOption } from '../../shared/components/input/SelectOption' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup' import PatientRepository from '../../shared/db/PatientRepository' @@ -29,9 +27,9 @@ const NewImagingRequest = () => { }) const [mutate] = useRequestImaging(user) const [error, setError] = useState() - const [visitOption, setVisitOption] = useState([] as Option[]) + const [visitOption, setVisitOption] = useState([] as SelectOption[]) - const statusOptions: Option[] = [ + const statusOptions: SelectOption[] = [ { label: t('imagings.status.requested'), value: 'requested' }, { label: t('imagings.status.completed'), value: 'completed' }, { label: t('imagings.status.canceled'), value: 'canceled' }, @@ -65,7 +63,7 @@ const NewImagingRequest = () => { const visits = patient.visits?.map((v) => ({ label: `${v.type} at ${format(new Date(v.startDateTime), 'yyyy-MM-dd hh:mm a')}`, value: v.id, - })) as Option[] + })) as SelectOption[] setVisitOption(visits) } else { @@ -154,17 +152,16 @@ const NewImagingRequest = () => {
-
- +
diff --git a/src/incidents/list/ViewIncidents.tsx b/src/incidents/list/ViewIncidents.tsx index f8f0888f0b..a103fde05e 100644 --- a/src/incidents/list/ViewIncidents.tsx +++ b/src/incidents/list/ViewIncidents.tsx @@ -1,12 +1,10 @@ -import { Button, Container, Row, Column } from '@hospitalrun/components' +import { Select, Label, Button, Container, Row, Column } from '@hospitalrun/components' import React, { useEffect, useState } from 'react' import { useHistory } from 'react-router-dom' import { useButtonToolbarSetter } from '../../page-header/button-toolbar/ButtonBarProvider' import { useUpdateTitle } from '../../page-header/title/TitleContext' -import SelectWithLabelFormGroup, { - Option, -} from '../../shared/components/input/SelectWithLabelFormGroup' +import { SelectOption } from '../../shared/components/input/SelectOption' import useTranslator from '../../shared/hooks/useTranslator' import IncidentFilter from '../IncidentFilter' import ViewIncidentsTable from './ViewIncidentsTable' @@ -39,7 +37,7 @@ const ViewIncidents = () => { } }, [setButtonToolBar, t, history]) - const filterOptions: Option[] = Object.values(IncidentFilter).map((filter) => ({ + const filterOptions: SelectOption[] = Object.values(IncidentFilter).map((filter) => ({ label: t(`incidents.status.${filter}`), value: `${filter}`, })) @@ -48,13 +46,13 @@ const ViewIncidents = () => { - + value === searchFilter)} onChange={(values) => setSearchFilter(values[0] as LabFilter)} - isEditable + defaultSelected={filterOptions.filter(({ value }) => value === searchFilter)} + disabled={false} /> diff --git a/src/labs/requests/NewLabRequest.tsx b/src/labs/requests/NewLabRequest.tsx index 5ee4b9107f..2e1c1b0e73 100644 --- a/src/labs/requests/NewLabRequest.tsx +++ b/src/labs/requests/NewLabRequest.tsx @@ -1,4 +1,13 @@ -import { Typeahead, Label, Button, Alert, Toast, Column, Row } from '@hospitalrun/components' +import { + Select, + Typeahead, + Label, + Button, + Alert, + Toast, + Column, + Row, +} from '@hospitalrun/components' import format from 'date-fns/format' import React, { useState, useEffect } from 'react' import { useSelector } from 'react-redux' @@ -6,16 +15,16 @@ import { useHistory } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../../page-header/title/TitleContext' -import SelectWithLabelFormGroup, { - Option, -} from '../../shared/components/input/SelectWithLabelFormGroup' +import { SelectOption } from '../../shared/components/input/SelectOption' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup' import PatientRepository from '../../shared/db/PatientRepository' import useTranslator from '../../shared/hooks/useTranslator' import Lab from '../../shared/model/Lab' +import Note from '../../shared/model/Note' import Patient from '../../shared/model/Patient' import { RootState } from '../../shared/store' +import { uuid } from '../../shared/util/uuid' import useRequestLab from '../hooks/useRequestLab' import { LabError } from '../utils/validate-lab' @@ -24,9 +33,9 @@ const NewLabRequest = () => { const history = useHistory() const { user } = useSelector((state: RootState) => state.user) const [mutate] = useRequestLab() - const [newNote, setNewNote] = useState('') + const [newNoteText, setNewNoteText] = useState('') const [error, setError] = useState(undefined) - const [visitOptions, setVisitOptions] = useState([] as Option[]) + const [visitOptions, setVisitOptions] = useState([] as SelectOption[]) const updateTitle = useUpdateTitle() useEffect(() => { @@ -54,7 +63,7 @@ const NewLabRequest = () => { const visits = patient.visits?.map((v) => ({ label: `${v.type} at ${format(new Date(v.startDateTime), 'yyyy-MM-dd hh:mm a')}`, value: v.id, - })) as Option[] + })) as SelectOption[] setVisitOptions(visits) setNewLabRequest((previousNewLabRequest) => ({ @@ -81,11 +90,19 @@ const NewLabRequest = () => { } const onNoteChange = (event: React.ChangeEvent) => { - const notes = event.currentTarget.value - setNewNote(notes) + const noteText = event.currentTarget.value + setNewNoteText(noteText) + + const newNote: Note = { + id: uuid(), + date: new Date().toISOString(), + text: noteText, + deleted: false, + } + setNewLabRequest((previousNewLabRequest) => ({ ...previousNewLabRequest, - notes: [notes], + notes: [newNote], })) } @@ -145,17 +162,16 @@ const NewLabRequest = () => {
-
- +
diff --git a/src/patients/care-goals/CareGoalForm.tsx b/src/patients/care-goals/CareGoalForm.tsx index 8741b8ffee..573eb85464 100644 --- a/src/patients/care-goals/CareGoalForm.tsx +++ b/src/patients/care-goals/CareGoalForm.tsx @@ -1,10 +1,8 @@ -import { Alert, Row, Column } from '@hospitalrun/components' +import { Select, Label, Alert, Row, Column } from '@hospitalrun/components' import React, { useState } from 'react' import DatePickerWithLabelFormGroup from '../../shared/components/input/DatePickerWithLabelFormGroup' -import SelectWithLabelFormGroup, { - Option, -} from '../../shared/components/input/SelectWithLabelFormGroup' +import { SelectOption } from '../../shared/components/input/SelectOption' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import useTranslator from '../../shared/hooks/useTranslator' import CareGoal, { CareGoalStatus, CareGoalAchievementStatus } from '../../shared/model/CareGoal' @@ -35,19 +33,24 @@ const CareGoalForm = (props: Props) => { const [status, setStatus] = useState(careGoal.status) const [achievementStatus, setAchievementStatus] = useState(careGoal.achievementStatus) - const priorityOptions: Option[] = [ + const priorityOptions: SelectOption[] = [ { label: t('patient.careGoal.priority.low'), value: 'low' }, { label: t('patient.careGoal.priority.medium'), value: 'medium' }, { label: t('patient.careGoal.priority.high'), value: 'high' }, ] - const statusOptions: Option[] = Object.values(CareGoalStatus).map((v) => ({ label: v, value: v })) - - const achievementsStatusOptions: Option[] = Object.values(CareGoalAchievementStatus).map((v) => ({ + const statusOptions: SelectOption[] = Object.values(CareGoalStatus).map((v) => ({ label: v, value: v, })) + const achievementsStatusOptions: SelectOption[] = Object.values(CareGoalAchievementStatus).map( + (v) => ({ + label: v, + value: v, + }), + ) + const onFieldChange = ( name: string, value: string | CareGoalStatus | CareGoalAchievementStatus, @@ -87,50 +90,68 @@ const CareGoalForm = (props: Props) => { - value === priority)} - isEditable={!disabled} - isInvalid={!!careGoalError?.priority} - onChange={onPriorityChange} - /> +
+
- value === achievementStatus, - )} - isEditable={!disabled} - isInvalid={!!careGoalError?.achievementStatus} - onChange={(values) => { - onFieldChange('achievementStatus', values[0]) - setAchievementStatus(values[0] as CareGoalAchievementStatus) - }} - /> +
+
- value === status)} - onChange={(values) => { - onFieldChange('status', values[0]) - setStatus(values[0] as CarePlanStatus) - }} - isEditable={!disabled} - isInvalid={!!carePlanError?.status} - /> +
+
diff --git a/src/patients/diagnoses/Diagnoses.tsx b/src/patients/diagnoses/Diagnoses.tsx index 12ee041896..a7b099ee4b 100644 --- a/src/patients/diagnoses/Diagnoses.tsx +++ b/src/patients/diagnoses/Diagnoses.tsx @@ -1,29 +1,29 @@ import { Button } from '@hospitalrun/components' import React, { useState } from 'react' import { useSelector } from 'react-redux' +import { Route, Switch, useParams } from 'react-router-dom' import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' +import Loading from '../../shared/components/Loading' import useTranslator from '../../shared/hooks/useTranslator' -import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' +import usePatient from '../hooks/usePatient' import AddDiagnosisModal from './AddDiagnosisModal' -import DiagnosesList from './DiagnosesList' +import ViewDiagnoses from './ViewDiagnoses' +import ViewDiagnosis from './ViewDiagnosis' -interface Props { - patient: Patient -} - -const Diagnoses = (props: Props) => { - const { patient } = props +const Diagnoses = () => { + const { id: patientId } = useParams() const { t } = useTranslator() const { permissions } = useSelector((state: RootState) => state.user) + const { data, status } = usePatient(patientId) const [showDiagnosisModal, setShowDiagnosisModal] = useState(false) const breadcrumbs = [ { i18nKey: 'patient.diagnoses.label', - location: `/patients/${patient.id}/diagnoses`, + location: `/patients/${patientId}/diagnoses`, }, ] useAddBreadcrumbs(breadcrumbs) @@ -32,6 +32,10 @@ const Diagnoses = (props: Props) => { setShowDiagnosisModal(false) } + if (data === undefined || status === 'loading') { + return + } + return ( <>
@@ -50,11 +54,18 @@ const Diagnoses = (props: Props) => {

- + + + + + + + + ) diff --git a/src/patients/diagnoses/DiagnosesList.tsx b/src/patients/diagnoses/DiagnosesList.tsx deleted file mode 100644 index 42736e669e..0000000000 --- a/src/patients/diagnoses/DiagnosesList.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Alert, List, ListItem } from '@hospitalrun/components' -import React from 'react' - -import Loading from '../../shared/components/Loading' -import useTranslator from '../../shared/hooks/useTranslator' -import Diagnosis from '../../shared/model/Diagnosis' -import usePatientDiagnoses from '../hooks/usePatientDiagnoses' - -interface Props { - patientId: string -} - -const DiagnosesList = (props: Props) => { - const { patientId } = props - const { t } = useTranslator() - const { data, status } = usePatientDiagnoses(patientId) - - if (data === undefined || status === 'loading') { - return - } - - if (data.length === 0) { - return ( - - ) - } - - return ( - - {data.map((diagnosis: Diagnosis) => ( - {diagnosis.name} - ))} - - ) -} - -export default DiagnosesList diff --git a/src/patients/diagnoses/DiagnosisForm.tsx b/src/patients/diagnoses/DiagnosisForm.tsx index e25c9a6e3b..ca70101a5b 100644 --- a/src/patients/diagnoses/DiagnosisForm.tsx +++ b/src/patients/diagnoses/DiagnosisForm.tsx @@ -1,9 +1,10 @@ -import { Alert, Row, Column } from '@hospitalrun/components' +import { Select, Label, Alert, Row, Column } from '@hospitalrun/components' import format from 'date-fns/format' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import Input, { Option } from '../../shared/components/input' +import Input from '../../shared/components/input' +import { SelectOption } from '../../shared/components/input/SelectOption' import Diagnosis, { DiagnosisStatus } from '../../shared/model/Diagnosis' import Patient from '../../shared/model/Patient' import usePatientVisits from '../hooks/usePatientVisits' @@ -45,7 +46,7 @@ const DiagnosisForm = (props: Props) => { const patientVisits = visits?.map((v) => ({ label: `${v.type} at ${format(new Date(v.startDateTime), 'yyyy-MM-dd, hh:mm a')}`, value: v.id, - })) as Option[] + })) as SelectOption[] const defaultSelectedVisitOption = () => { if (patientVisits !== undefined) { @@ -54,7 +55,7 @@ const DiagnosisForm = (props: Props) => { return [] } - const statusOptions: Option[] = Object.values(DiagnosisStatus).map((v) => ({ + const statusOptions: SelectOption[] = Object.values(DiagnosisStatus).map((v) => ({ label: v, value: v, })) @@ -131,35 +132,42 @@ const DiagnosisForm = (props: Props) => { - { - onFieldChange('visit', values[0]) - }} - isEditable={patient?.visits !== undefined} - /> +
+
diff --git a/src/patients/diagnoses/DiagnosisTable.tsx b/src/patients/diagnoses/DiagnosisTable.tsx new file mode 100644 index 0000000000..13ea52f452 --- /dev/null +++ b/src/patients/diagnoses/DiagnosisTable.tsx @@ -0,0 +1,71 @@ +import { Alert, Table } from '@hospitalrun/components' +import format from 'date-fns/format' +import React from 'react' +import { useHistory } from 'react-router-dom' + +import Loading from '../../shared/components/Loading' +import useTranslator from '../../shared/hooks/useTranslator' +import usePatientDiagnoses from '../hooks/usePatientDiagnoses' + +interface Props { + patientId: string +} + +const DiagnosisTable = (props: Props) => { + const { patientId } = props + const history = useHistory() + const { t } = useTranslator() + const { data, status } = usePatientDiagnoses(patientId) + + if (data === undefined || status === 'loading') { + return + } + + if (data.length === 0) { + return ( + + ) + } + + return ( +
row.id} + data={data} + columns={[ + { label: t('patient.diagnoses.diagnosisName'), key: 'name' }, + { + label: t('patient.diagnoses.diagnosisDate'), + key: 'diagnosisDate', + formatter: (row) => + format(row.diagnosisDate ? new Date(row.diagnosisDate) : new Date(0), 'yyyy-MM-dd'), + }, + { + label: t('patient.diagnoses.onsetDate'), + key: 'onsetDate', + formatter: (row) => + format(row.onsetDate ? new Date(row.onsetDate) : new Date(0), 'yyyy-MM-dd'), + }, + { + label: t('patient.diagnoses.abatementDate'), + key: 'abatementDate', + formatter: (row) => + format(row.abatementDate ? new Date(row.abatementDate) : new Date(0), 'yyyy-MM-dd'), + }, + { label: t('patient.diagnoses.status'), key: 'status' }, + ]} + actionsHeaderText={t('actions.label')} + actions={[ + { + label: t('actions.view'), + action: (row) => history.push(`/patients/${patientId}/diagnoses/${row.id}`), + }, + ]} + /> + ) +} + +export default DiagnosisTable diff --git a/src/patients/diagnoses/ViewDiagnoses.tsx b/src/patients/diagnoses/ViewDiagnoses.tsx new file mode 100644 index 0000000000..8885edcd38 --- /dev/null +++ b/src/patients/diagnoses/ViewDiagnoses.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { useParams } from 'react-router-dom' + +import DiagnosisTable from './DiagnosisTable' + +const ViewDiagnoses = () => { + const { id: patientId } = useParams() + + return +} + +export default ViewDiagnoses diff --git a/src/patients/diagnoses/ViewDiagnosis.tsx b/src/patients/diagnoses/ViewDiagnosis.tsx new file mode 100644 index 0000000000..0d57edadb2 --- /dev/null +++ b/src/patients/diagnoses/ViewDiagnosis.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { useParams } from 'react-router-dom' + +import Loading from '../../shared/components/Loading' +import useDiagnosis from '../hooks/useDiagnosis' +import usePatient from '../hooks/usePatient' +import DiagnosisForm from './DiagnosisForm' + +const ViewDiagnosis = () => { + const { diagnosisId, id: patientId } = useParams() + const { data: patient, status: patientStatus } = usePatient(patientId) + const { data: diagnosis, status: diagnosisStatus } = useDiagnosis(patientId, diagnosisId) + + if ( + patient === undefined || + diagnosis === undefined || + patientStatus === 'loading' || + diagnosisStatus === 'loading' + ) { + return + } + + return ( + <> +

{diagnosis.name}

+ + + ) +} + +export default ViewDiagnosis diff --git a/src/patients/hooks/useDiagnosis.tsx b/src/patients/hooks/useDiagnosis.tsx new file mode 100644 index 0000000000..dd6b856a1a --- /dev/null +++ b/src/patients/hooks/useDiagnosis.tsx @@ -0,0 +1,18 @@ +import { useQuery } from 'react-query' + +import PatientRepository from '../../shared/db/PatientRepository' +import Diagnosis from '../../shared/model/Diagnosis' + +async function getDiagnosis(_: string, patientId: string, diagnosisId: string): Promise { + const patient = await PatientRepository.find(patientId) + const maybeDiagnosis = patient.diagnoses?.find((d) => d.id === diagnosisId) + if (!maybeDiagnosis) { + throw new Error('Diagnosis not found') + } + + return maybeDiagnosis +} + +export default function useDiagnosis(patientId: string, diagnosisId: string) { + return useQuery(['diagnoses', patientId, diagnosisId], getDiagnosis, { retry: false }) +} diff --git a/src/patients/util/set-patient-helper.ts b/src/patients/util/set-patient-helper.ts index 1ee03e9440..a6dcab0d28 100644 --- a/src/patients/util/set-patient-helper.ts +++ b/src/patients/util/set-patient-helper.ts @@ -7,6 +7,7 @@ import { getPatientName } from './patient-util' */ const cleanupPatient = (patient: Patient) => { const newPatient = { ...patient } + newPatient.visits = newPatient.visits ?? [] const { givenName, familyName, suffix } = patient newPatient.fullName = getPatientName(givenName, familyName, suffix) diff --git a/src/patients/view/ImportantPatientInfo.tsx b/src/patients/view/ImportantPatientInfo.tsx index 1aab395a1f..0783be8a4c 100644 --- a/src/patients/view/ImportantPatientInfo.tsx +++ b/src/patients/view/ImportantPatientInfo.tsx @@ -161,7 +161,7 @@ const ImportantPatientInfo = (props: Props) => {
history.push(`/patients/${patient.id}/diagnoses`)} + onRowClick={(row) => history.push(`/patients/${patient.id}/diagnoses/${row.id}`)} getID={(row) => row.id} columns={[ { label: t('patient.diagnoses.diagnosisName'), key: 'name' }, diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx index dd5bd132eb..29acd8d0c8 100644 --- a/src/patients/view/ViewPatient.tsx +++ b/src/patients/view/ViewPatient.tsx @@ -110,7 +110,7 @@ const ViewPatient = () => { onClick={() => history.push(`/patients/${patient.id}/allergies`)} /> history.push(`/patients/${patient.id}/diagnoses`)} /> @@ -163,8 +163,8 @@ const ViewPatient = () => { - - + + diff --git a/src/patients/visits/VisitForm.tsx b/src/patients/visits/VisitForm.tsx index 24869b80f0..46b09c7c1c 100644 --- a/src/patients/visits/VisitForm.tsx +++ b/src/patients/visits/VisitForm.tsx @@ -1,10 +1,8 @@ -import { Alert, Column, Row } from '@hospitalrun/components' +import { Select, Label, Alert, Column, Row } from '@hospitalrun/components' import React, { useState } from 'react' import DateTimePickerWithLabelFormGroup from '../../shared/components/input/DateTimePickerWithLabelFormGroup' -import SelectWithLabelFormGroup, { - Option, -} from '../../shared/components/input/SelectWithLabelFormGroup' +import { SelectOption } from '../../shared/components/input/SelectOption' import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup' import useTranslator from '../../shared/hooks/useTranslator' @@ -43,7 +41,7 @@ const VisitForm = (props: Props) => { } } - const statusOptions: Option[] = + const statusOptions: SelectOption[] = Object.values(VisitStatus).map((v) => ({ label: v, value: v })) || [] return ( @@ -91,17 +89,16 @@ const VisitForm = (props: Props) => { - + value === appointment.type)} + onChange={(values) => onFieldChange && onFieldChange('type', values[0])} + disabled={!isEditable} + /> +
@@ -134,10 +133,8 @@ const AppointmentDetailForm = (props: Props) => { label={t('scheduling.appointment.reason')} value={appointment.reason} isEditable={isEditable} - onChange={ - (event: React.ChangeEvent) => - onFieldChange && onFieldChange('reason', event.currentTarget.value) - // eslint-disable-next-line react/jsx-curly-newline + onChange={(event: React.ChangeEvent) => + onFieldChange && onFieldChange('reason', event.currentTarget.value) } />
diff --git a/src/shared/components/input/LanguageSelector.tsx b/src/shared/components/input/LanguageSelector.tsx index 46d5660717..e5c29e9919 100644 --- a/src/shared/components/input/LanguageSelector.tsx +++ b/src/shared/components/input/LanguageSelector.tsx @@ -1,15 +1,16 @@ +import { Select, Label } from '@hospitalrun/components' import sortBy from 'lodash/sortBy' import React, { useState } from 'react' import i18n, { resources } from '../../config/i18n' import useTranslator from '../../hooks/useTranslator' -import SelectWithLabelFormGroup, { Option } from './SelectWithLabelFormGroup' +import { SelectOption } from './SelectOption' const LanguageSelector = () => { const { t } = useTranslator() const [selected, setSelected] = useState(i18n.language) - let languageOptions: Option[] = Object.keys(resources).map((abbr) => ({ + let languageOptions: SelectOption[] = Object.keys(resources).map((abbr) => ({ label: resources[abbr].name, value: abbr, })) @@ -21,14 +22,15 @@ const LanguageSelector = () => { } return ( - value === selected)} - onChange={(values) => onChange(values[0])} - isEditable - /> + <> +