-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ac24702
commit f4e486f
Showing
13 changed files
with
560 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/esm-ward-app/src/ward-patient-card/ward-patient-card.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 0 additions & 13 deletions
13
packages/esm-ward-app/src/ward-workspace/ward-patient-notes/form/notes-form.resource.ts
This file was deleted.
Oops, something went wrong.
51 changes: 51 additions & 0 deletions
51
packages/esm-ward-app/src/ward-workspace/ward-patient-notes/history/note.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import { type PatientNote } from '../types'; | ||
import { SkeletonText, Tag, Tile } from '@carbon/react'; | ||
import dayjs from 'dayjs'; | ||
import styles from './styles.scss'; | ||
|
||
export const InPatientNoteSkeleton: React.FC = () => { | ||
return ( | ||
<Tile className={styles.noteTile}> | ||
<div className={styles.noteHeader}> | ||
<SkeletonText heading width="30%" /> | ||
<SkeletonText width="20%" /> | ||
</div> | ||
<SkeletonText width="15%" /> | ||
<SkeletonText width="100%" /> | ||
<SkeletonText width="80%" /> | ||
</Tile> | ||
); | ||
}; | ||
|
||
interface InPatientNoteProps { | ||
note: PatientNote; | ||
} | ||
|
||
const InPatientNote: React.FC<InPatientNoteProps> = ({ note }) => { | ||
const formattedDate = note.encounterNoteRecordedAt | ||
? dayjs(note.encounterNoteRecordedAt).format('dddd, D MMM YYYY') | ||
: ''; | ||
const formattedTime = note.encounterNoteRecordedAt ? dayjs(note.encounterNoteRecordedAt).format('HH:mm') : ''; | ||
|
||
return ( | ||
<Tile className={styles.noteTile}> | ||
<div className={styles.noteHeader}> | ||
<span className={styles.noteProviderRole}>Nurse’s note</span> | ||
<span className={styles.noteDateAndTime}> | ||
{formattedDate}, {formattedTime} | ||
</span> | ||
</div> | ||
{note.diagnoses && | ||
note.diagnoses.split(',').map((diagnosis, index) => ( | ||
<Tag key={index} type="red"> | ||
{diagnosis.trim()} | ||
</Tag> | ||
))} | ||
<div className={styles.noteBody}>{note.encounterNote}</div> | ||
<div className={styles.noteProviderName}>{note.encounterProvider}</div> | ||
</Tile> | ||
); | ||
}; | ||
|
||
export default InPatientNote; |
60 changes: 60 additions & 0 deletions
60
.../esm-ward-app/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React, { useState } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { isDesktop, type PatientUuid, useLayoutType } from '@openmrs/esm-framework'; | ||
import { usePatientNotes } from '../notes.resource'; | ||
import InPatientNote, { InPatientNoteSkeleton } from './note.component'; | ||
import { type EmrApiConfigurationResponse } from '../../../types'; | ||
import styles from './styles.scss'; | ||
import { Dropdown } from '@carbon/react'; | ||
|
||
interface PatientNotesHistoryProps { | ||
patientUuid: PatientUuid; | ||
emrConfiguration: EmrApiConfigurationResponse; | ||
isLoadingEmrConfiguration: boolean; | ||
} | ||
|
||
const PatientNotesHistory: React.FC<PatientNotesHistoryProps> = ({ | ||
patientUuid, | ||
emrConfiguration, | ||
isLoadingEmrConfiguration, | ||
}) => { | ||
const { t } = useTranslation(); | ||
const desktopLayout = isDesktop(useLayoutType()); | ||
const [filter, setFilter] = useState(''); | ||
const { patientNotes, isLoadingPatientNotes } = usePatientNotes( | ||
patientUuid, | ||
emrConfiguration?.visitNoteEncounterType?.uuid, | ||
emrConfiguration?.consultFreeTextCommentsConcept.uuid, | ||
); | ||
|
||
const handleEncounterTypeChange = ({ selectedItem }) => setFilter(selectedItem); | ||
|
||
const isLoading = isLoadingPatientNotes || isLoadingEmrConfiguration; | ||
|
||
return ( | ||
<div className={styles.notesContainer}> | ||
<div className={styles.notesContainerHeader}> | ||
<div className={styles.notesContainerTitle}>History</div> | ||
<div> | ||
<Dropdown | ||
autoAlign | ||
id="providerFilter" | ||
initialSelectedItem={t('all', 'All')} | ||
label="" | ||
titleText={t('show', 'Show')} | ||
type="inline" | ||
items={[t('all', 'All')]} | ||
onChange={handleEncounterTypeChange} | ||
size={desktopLayout ? 'sm' : 'lg'} | ||
/> | ||
</div> | ||
</div> | ||
{isLoading ? [1, 2, 3, 4].map((item, index) => <InPatientNoteSkeleton key={index} />) : null} | ||
{patientNotes.map((patientNote) => ( | ||
<InPatientNote key={patientNote.id} note={patientNote} /> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default PatientNotesHistory; |
110 changes: 110 additions & 0 deletions
110
packages/esm-ward-app/src/ward-workspace/ward-patient-notes/history/notes-container.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import React from 'react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { render, screen } from '@testing-library/react'; | ||
import { createErrorHandler, ResponsiveWrapper, showSnackbar, translateFrom, useSession } from '@openmrs/esm-framework'; | ||
import { savePatientNote } from './notes-form.resource'; | ||
import PatientNotesForm from './notes-form.component'; | ||
import { emrConfigurationMock, mockPatient, mockSession } from '__mocks__'; | ||
import useEmrConfiguration from '../../../hooks/useEmrConfiguration'; | ||
|
||
const testProps = { | ||
patientUuid: mockPatient.uuid, | ||
closeWorkspace: jest.fn(), | ||
closeWorkspaceWithSavedChanges: jest.fn(), | ||
promptBeforeClosing: jest.fn(), | ||
setTitle: jest.fn(), | ||
onWorkspaceClose: jest.fn(), | ||
setOnCloseCallback: jest.fn(), | ||
}; | ||
|
||
const mockSavePatientNote = savePatientNote as jest.Mock; | ||
const mockedShowSnackbar = jest.mocked(showSnackbar); | ||
const mockedCreateErrorHandler = jest.mocked(createErrorHandler); | ||
const mockedTranslateFrom = jest.mocked(translateFrom); | ||
const mockedResponsiveWrapper = jest.mocked(ResponsiveWrapper); | ||
const mockedUseSession = jest.mocked(useSession); | ||
|
||
jest.mock('./notes-form.resource', () => ({ | ||
savePatientNote: jest.fn(), | ||
})); | ||
|
||
jest.mock('../../../hooks/useEmrConfiguration', () => jest.fn()); | ||
|
||
const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration); | ||
|
||
mockedUseEmrConfiguration.mockReturnValue({ | ||
emrConfiguration: emrConfigurationMock, | ||
mutateEmrConfiguration: jest.fn(), | ||
isLoadingEmrConfiguration: false, | ||
errorFetchingEmrConfiguration: null, | ||
}); | ||
|
||
test('renders the visit notes form with all the relevant fields and values', () => { | ||
renderWardPatientNotesForm(); | ||
|
||
expect(screen.getByRole('textbox', { name: /Write your notes/i })).toBeInTheDocument(); | ||
expect(screen.getByRole('button', { name: /Save/i })).toBeInTheDocument(); | ||
}); | ||
|
||
test('renders a success snackbar upon successfully recording a visit note', async () => { | ||
const successPayload = { | ||
encounterType: emrConfigurationMock.visitNoteEncounterType.uuid, | ||
location: undefined, | ||
obs: expect.arrayContaining([ | ||
{ | ||
concept: { display: '', uuid: '162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, | ||
value: 'Sample clinical note', | ||
}, | ||
]), | ||
patient: mockPatient.uuid, | ||
}; | ||
|
||
mockSavePatientNote.mockResolvedValue({ status: 201, body: 'Condition created' }); | ||
|
||
renderWardPatientNotesForm(); | ||
|
||
const note = screen.getByRole('textbox', { name: /Write your notes/i }); | ||
await userEvent.clear(note); | ||
await userEvent.type(note, 'Sample clinical note'); | ||
expect(note).toHaveValue('Sample clinical note'); | ||
|
||
const submitButton = screen.getByRole('button', { name: /Save/i }); | ||
await userEvent.click(submitButton); | ||
|
||
expect(mockSavePatientNote).toHaveBeenCalledTimes(1); | ||
expect(mockSavePatientNote).toHaveBeenCalledWith(expect.objectContaining(successPayload), new AbortController()); | ||
}); | ||
|
||
test('renders an error snackbar if there was a problem recording a visit note', async () => { | ||
const error = { | ||
message: 'Internal Server Error', | ||
response: { | ||
status: 500, | ||
statusText: 'Internal Server Error', | ||
}, | ||
}; | ||
|
||
mockSavePatientNote.mockRejectedValueOnce(error); | ||
renderWardPatientNotesForm(); | ||
|
||
const note = screen.getByRole('textbox', { name: /Write your notes/i }); | ||
await userEvent.clear(note); | ||
await userEvent.type(note, 'Sample clinical note'); | ||
expect(note).toHaveValue('Sample clinical note'); | ||
|
||
const submitButton = screen.getByRole('button', { name: /Save/i }); | ||
|
||
await userEvent.click(submitButton); | ||
|
||
expect(mockedShowSnackbar).toHaveBeenCalledWith({ | ||
isLowContrast: false, | ||
kind: 'error', | ||
subtitle: 'Internal Server Error', | ||
title: 'Error saving patient note', | ||
}); | ||
}); | ||
|
||
function renderWardPatientNotesForm() { | ||
mockedUseSession.mockReturnValue(mockSession); | ||
render(<PatientNotesForm {...testProps} />); | ||
} |
Oops, something went wrong.