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

feat(imaging): link image with visit #2309

Merged
merged 15 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/__tests__/imagings/requests/NewImagingRequest.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ describe('New Imaging Request', () => {
expect(typeahead.prop('searchAccessor')).toEqual('fullName')
})

it('should render a dropdown list of visits', async () => {
const wrapper = await setup('loading', {})
const visitsTypeSelect = wrapper.find('.visits').find(SelectWithLabelFormGroup)
expect(visitsTypeSelect).toBeDefined()
expect(visitsTypeSelect.prop('label')).toEqual('patient.visits.type')
expect(visitsTypeSelect.prop('isRequired')).toBeTruthy()
})

it('should render a type input box', async () => {
const wrapper = await setup('loading', {})
const typeInputBox = wrapper.find(TextInputWithLabelFormGroup)
Expand All @@ -98,7 +106,7 @@ describe('New Imaging Request', () => {

it('should render a status types select', async () => {
const wrapper = await setup('loading', {})
const statusTypesSelect = wrapper.find(SelectWithLabelFormGroup)
const statusTypesSelect = wrapper.find('.imaging-status').find(SelectWithLabelFormGroup)

expect(statusTypesSelect).toBeDefined()
expect(statusTypesSelect.prop('label')).toEqual('imagings.imaging.status')
Expand Down Expand Up @@ -184,6 +192,7 @@ describe('New Imaging Request', () => {
patient: 'patient',
type: 'expected type',
status: 'requested',
visitId: 'expected visitId',
notes: 'expected notes',
id: '1234',
requestedOn: expectedDate.toISOString(),
Expand All @@ -204,12 +213,18 @@ describe('New Imaging Request', () => {
onChange({ currentTarget: { value: expectedImaging.type } })
})

const statusSelect = wrapper.find(SelectWithLabelFormGroup)
const statusSelect = wrapper.find('.imaging-status').find(SelectWithLabelFormGroup)
blestab marked this conversation as resolved.
Show resolved Hide resolved
act(() => {
const onChange = statusSelect.prop('onChange') as any
onChange({ currentTarget: { value: expectedImaging.status } })
})

const visitsSelect = wrapper.find('.visits').find(SelectWithLabelFormGroup)
act(() => {
const onChange = visitsSelect.prop('onChange') as any
onChange({ currentTarget: { value: expectedImaging.visitId } })
})

const notesTextField = wrapper.find(TextFieldWithLabelFormGroup)
act(() => {
const onChange = notesTextField.prop('onChange') as any
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/labs/requests/NewLabRequest.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe('New Lab Request', () => {
it('should render a save button', () => {
const saveButton = wrapper.find(Button).at(0)
expect(saveButton).toBeDefined()
expect(saveButton.text().trim()).toEqual('actions.save')
expect(saveButton.text().trim()).toEqual('labs.requests.save')
})

it('should render a cancel button', () => {
Expand Down
1 change: 1 addition & 0 deletions src/imagings/imaging-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface Error {
type?: string
status?: string
message?: string
visitId?: string
}

interface ImagingState {
Expand Down
115 changes: 88 additions & 27 deletions src/imagings/requests/NewImagingRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Typeahead, Label, Button, Alert } from '@hospitalrun/components'
import { Typeahead, Label, Button, Alert, Column, Row } from '@hospitalrun/components'
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useHistory } from 'react-router-dom'
Expand All @@ -23,6 +23,7 @@ const NewImagingRequest = () => {
const history = useHistory()
useTitle(t('imagings.requests.new'))
const { status, error } = useSelector((state: RootState) => state.imaging)
const [visitOption, setVisitOption] = useState([] as Option[])

const statusOptions: Option[] = [
{ label: t('imagings.status.requested'), value: 'requested' },
Expand All @@ -35,6 +36,7 @@ const NewImagingRequest = () => {
type: '',
notes: '',
status: '',
visitId: '',
})

const breadcrumbs = [
Expand All @@ -46,10 +48,21 @@ const NewImagingRequest = () => {
useAddBreadcrumbs(breadcrumbs)

const onPatientChange = (patient: Patient) => {
setNewImagingRequest((previousNewImagingRequest) => ({
...previousNewImagingRequest,
patient: patient.fullName as string,
}))
if (patient) {
setNewImagingRequest((previousNewImagingRequest) => ({
...previousNewImagingRequest,
patient: patient.fullName as string,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should store patient id rather than the patient full name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this store as patient id, the patient field on the table in requested imagings page would display its patient id instead of full name
image

}))

const visits = patient.visits?.map((v) => ({ label: v.type, value: v.id }))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the label should be something like ${visitType} at ${startDateTime}or${visitType} (${startDateTime)}`

setVisitOption(visits)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the visits need to be a state variable? seems like we could just reference patient.visits wherever we use the visits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we need the visits to be a state variable. Because we can only fetch the visits data inside the onPatientChange method using the patient parameter (as we fetch the patient using this line onSearch={async (query: string) => PatientRepository.search(query)}). Using state variable ensures that the visits data will be re-rendered whenever the patient field value is changed.

} else {
setNewImagingRequest((previousNewImagingRequest) => ({
...previousNewImagingRequest,
patient: '',
visitId: '',
}))
}
}

const onImagingTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -67,6 +80,13 @@ const NewImagingRequest = () => {
}))
}

const onVisitChange = (value: string) => {
setNewImagingRequest((previousNewImagingRequest) => ({
...previousNewImagingRequest,
visitId: value,
}))
}

const onNoteChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const notes = event.currentTarget.value
setNewImagingRequest((previousNewImagingRequest) => ({
Expand Down Expand Up @@ -94,19 +114,56 @@ const NewImagingRequest = () => {
<Alert color="danger" title={t('states.error')} message={t(error.message || '')} />
)}
<form>
<div className="form-group patient-typeahead">
<Label htmlFor="patientTypeahead" isRequired text={t('imagings.imaging.patient')} />
<Typeahead
id="patientTypeahead"
placeholder={t('imagings.imaging.patient')}
onChange={(p: Patient[]) => onPatientChange(p[0])}
onSearch={async (query: string) => PatientRepository.search(query)}
searchAccessor="fullName"
renderMenuItemChildren={(p: Patient) => <div>{`${p.fullName} (${p.code})`}</div>}
isInvalid={!!error.patient}
feedback={t(error.patient as string)}
/>
</div>
<Row>
<Column>
<div className="form-group patient-typeahead">
<Label htmlFor="patientTypeahead" isRequired text={t('imagings.imaging.patient')} />
<Typeahead
id="patientTypeahead"
placeholder={t('imagings.imaging.patient')}
onChange={(p: Patient[]) => {
onPatientChange(p[0])
}}
onSearch={async (query: string) => PatientRepository.search(query)}
searchAccessor="fullName"
renderMenuItemChildren={(p: Patient) => <div>{`${p.fullName} (${p.code})`}</div>}
isInvalid={!!error.patient}
feedback={t(error.patient as string)}
/>
</div>
</Column>
<Column>
<div className="visits">
{newImagingRequest.patient && visitOption ? (
<SelectWithLabelFormGroup
name="visit"
label={t('patient.visits.type')}
isRequired
isEditable
options={visitOption}
defaultSelected={visitOption.filter(
({ value }) => value === newImagingRequest.visitId,
)}
onChange={(values) => {
onVisitChange(values[0])
}}
/>
) : (
<SelectWithLabelFormGroup
name="visit"
label={t('patient.visits.type')}
isRequired
isEditable={false}
options={[]}
onChange={(values) => {
onVisitChange(values[0])
}}
/>
)}
</div>
</Column>
</Row>

<TextInputWithLabelFormGroup
name="imagingType"
label={t('imagings.imaging.type')}
Expand All @@ -117,15 +174,19 @@ const NewImagingRequest = () => {
value={newImagingRequest.type}
onChange={onImagingTypeChange}
/>
<SelectWithLabelFormGroup
name="status"
label={t('imagings.imaging.status')}
options={statusOptions}
isRequired
isEditable
defaultSelected={statusOptions.filter(({ value }) => value === newImagingRequest.status)}
onChange={(values) => onStatusChange(values[0])}
/>
<div className="imaging-status">
<SelectWithLabelFormGroup
name="status"
label={t('imagings.imaging.status')}
options={statusOptions}
isRequired
isEditable
defaultSelected={statusOptions.filter(
({ value }) => value === newImagingRequest.status,
)}
onChange={(values) => onStatusChange(values[0])}
/>
</div>
<div className="form-group">
<TextFieldWithLabelFormGroup
name="ImagingNotes"
Expand Down
2 changes: 1 addition & 1 deletion src/labs/requests/NewLabRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const NewLabRequest = () => {
<div className="row float-right">
<div className="btn-group btn-group-lg mt-3">
<Button className="mr-2" color="success" onClick={onSave}>
{t('actions.save')}
{t('labs.requests.save')}
blestab marked this conversation as resolved.
Show resolved Hide resolved
</Button>

<Button color="danger" onClick={onCancel}>
Expand Down
3 changes: 2 additions & 1 deletion src/shared/locales/enUs/translations/labs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export default {
},
requests: {
label: 'Lab Requests',
new: 'New Lab Request',
new: 'Request Lab',
blestab marked this conversation as resolved.
Show resolved Hide resolved
save: 'Request',
view: 'View Lab',
cancel: 'Cancel Lab',
complete: 'Complete Lab',
Expand Down
2 changes: 1 addition & 1 deletion src/shared/locales/enUs/translations/patient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export default {
label: 'Visits',
startDateTime: 'Start Date',
endDateTime: 'End Date',
type: 'Type',
type: 'Visit Type',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why we decide to add Visit here? Aren't we already looking at Visit* fields? if we have to do it for one should we maybe not also do it for all of them? i.e. Visit Start Date, Visit End Date etc for consistency? Also not sure if this is related to the issue that this PR is meant to be addressing? I stand corrected should it be.

Copy link
Contributor Author

@AlexTan331 AlexTan331 Aug 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I was originally thinking is to create a translation key for the visit type so that it can be used in the new request imaging page
<div className="visits"> {newImagingRequest.patient && visitOption ? ( <SelectWithLabelFormGroup name="visit" label={t('patient.visits.type')}
image

Maybe it will make more sense to create a new translation key in the imaging locale index.ts file to prevent some confusion. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can keep the translation key in patients since visits are part of the patients module. Maybe just call it Visit rather than Visit Type because visit type makes me think (outpatient/inpatient/emergency etc) where here we are giving the user a list of all visits for this patient regardless of the type of visit it is, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it

status: 'Status',
reason: 'Reason',
location: 'Location',
Expand Down
1 change: 1 addition & 0 deletions src/shared/model/Imaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default interface Imaging extends AbstractDBModel {
patient: string
type: string
status: 'requested' | 'completed' | 'canceled'
visitId: string
requestedOn: string
requestedBy: string // will be the currently logged in user's id
completedOn?: string
Expand Down