From 5887859542247573843fc5af980cd081f6cc6f25 Mon Sep 17 00:00:00 2001 From: Jack Meyer Date: Sun, 3 May 2020 20:00:47 -0500 Subject: [PATCH] feat(incidents): add ability to view an incident --- src/__tests__/HospitalRun.test.tsx | 2 + src/__tests__/incidents/Incidents.test.tsx | 25 ++++- .../incidents/incident-slice.test.ts | 71 ++++++++++-- .../incidents/view/ViewIncident.test.tsx | 104 ++++++++++++++++-- src/incidents/incident-slice.ts | 11 ++ src/incidents/view/ViewIncident.tsx | 87 ++++++++++++++- 6 files changed, 276 insertions(+), 24 deletions(-) diff --git a/src/__tests__/HospitalRun.test.tsx b/src/__tests__/HospitalRun.test.tsx index edffdd7dd5..2a0e4c0868 100644 --- a/src/__tests__/HospitalRun.test.tsx +++ b/src/__tests__/HospitalRun.test.tsx @@ -34,6 +34,7 @@ describe('HospitalRun', () => { appointments: { appointments: [] }, breadcrumbs: { breadcrumbs: [] }, components: { sidebarCollapsed: false }, + incidents: { incidents: [] }, }) const wrapper = mount( @@ -265,6 +266,7 @@ describe('HospitalRun', () => { user: { permissions: [Permissions.ViewIncidents] }, breadcrumbs: { breadcrumbs: [] }, components: { sidebarCollapsed: false }, + incidents: { incidents: [] }, }) let wrapper: any diff --git a/src/__tests__/incidents/Incidents.test.tsx b/src/__tests__/incidents/Incidents.test.tsx index e66656549d..51913eafa4 100644 --- a/src/__tests__/incidents/Incidents.test.tsx +++ b/src/__tests__/incidents/Incidents.test.tsx @@ -10,19 +10,28 @@ import Permissions from 'model/Permissions' import ViewIncident from '../../incidents/view/ViewIncident' import Incidents from '../../incidents/Incidents' import ReportIncident from '../../incidents/report/ReportIncident' +import Incident from '../../model/Incident' +import IncidentRepository from '../../clients/db/IncidentRepository' const mockStore = configureMockStore([thunk]) describe('Incidents', () => { describe('routing', () => { describe('/incidents/new', () => { - it('should render the new lab request screen when /incidents/new is accessed', () => { + it('should render the new incident screen when /incidents/new is accessed', () => { + const expectedIncident = { + id: '1234', + code: '1234', + } as Incident + jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident) const store = mockStore({ title: 'test', user: { permissions: [Permissions.ReportIncident] }, breadcrumbs: { breadcrumbs: [] }, components: { sidebarCollapsed: false }, - incident: {}, + incident: { + incident: expectedIncident, + }, }) const wrapper = mount( @@ -36,7 +45,7 @@ describe('Incidents', () => { expect(wrapper.find(ReportIncident)).toHaveLength(1) }) - it('should not navigate to /incidents/new if the user does not have RequestLab permissions', () => { + it('should not navigate to /incidents/new if the user does not have ReportIncident permissions', () => { const store = mockStore({ title: 'test', user: { permissions: [] }, @@ -57,12 +66,20 @@ describe('Incidents', () => { }) describe('/incidents/:id', () => { - it('should render the view lab screen when /incidents/:id is accessed', async () => { + it('should render the view incident screen when /incidents/:id is accessed', async () => { const store = mockStore({ title: 'test', user: { permissions: [Permissions.ViewIncident] }, breadcrumbs: { breadcrumbs: [] }, components: { sidebarCollapsed: false }, + incident: { + incident: { + id: '1234', + code: '1234 ', + date: new Date().toISOString(), + reportedOn: new Date().toISOString(), + }, + }, }) let wrapper: any diff --git a/src/__tests__/incidents/incident-slice.test.ts b/src/__tests__/incidents/incident-slice.test.ts index 4239431596..0e157df0d4 100644 --- a/src/__tests__/incidents/incident-slice.test.ts +++ b/src/__tests__/incidents/incident-slice.test.ts @@ -8,10 +8,15 @@ import incident, { reportIncidentSuccess, reportIncidentError, reportIncident, + fetchIncidentStart, + fetchIncidentSuccess, + fetchIncident, } from '../../incidents/incident-slice' import Incident from '../../model/Incident' import { RootState } from '../../store' import IncidentRepository from '../../clients/db/IncidentRepository' +import Permissions from '../../model/Permissions' +import User from '../../model/User' const mockStore = createMockStore([thunk]) @@ -58,6 +63,22 @@ describe('incident slice', () => { expect(incidentStore.status).toEqual('error') expect(incidentStore.error).toEqual(expectedError) }) + + it('should handle fetch incident start', () => { + const incidentStore = incident(undefined, fetchIncidentStart()) + expect(incidentStore.status).toEqual('loading') + }) + + it('should handle fetch incident success', () => { + const expectedIncident = { + id: '1234', + code: 'some code', + } as Incident + + const incidentStore = incident(undefined, fetchIncidentSuccess(expectedIncident)) + expect(incidentStore.status).toEqual('completed') + expect(incidentStore.incident).toEqual(expectedIncident) + }) }) describe('report incident', () => { @@ -84,11 +105,17 @@ describe('incident slice', () => { code: `I-${expectedShortId}`, reportedOn: expectedDate.toISOString(), reportedBy: 'some user id', - } + status: 'reported', + } as Incident jest.spyOn(IncidentRepository, 'save').mockResolvedValue(expectedIncident) - const store = mockStore({ user: { user: { id: expectedIncident.reportedBy } } }) + const store = mockStore({ + user: { + user: { id: expectedIncident.reportedBy } as User, + permissions: [] as Permissions[], + }, + } as any) await store.dispatch(reportIncident(newIncident, onSuccessSpy)) @@ -110,8 +137,12 @@ describe('incident slice', () => { description: 'incidents.reports.error.descriptionRequired', } - const store = mockStore({ user: { user: { id: 'some id' } } }) - + const store = mockStore({ + user: { + user: { id: 'some id' } as User, + permissions: [] as Permissions[], + }, + } as any) await store.dispatch(reportIncident(newIncident, onSuccessSpy)) expect(store.getActions()[0]).toEqual(reportIncidentStart()) @@ -124,7 +155,7 @@ describe('incident slice', () => { const onSuccessSpy = jest.fn() const newIncident = { description: 'description', - date: addDays(new Date(), 4), + date: addDays(new Date(), 4).toISOString(), department: 'some department', category: 'category', categoryItem: 'categoryItem', @@ -134,8 +165,12 @@ describe('incident slice', () => { date: 'incidents.reports.error.dateMustBeInThePast', } - const store = mockStore({ user: { user: { id: 'some id' } } }) - + const store = mockStore({ + user: { + user: { id: 'some id' } as User, + permissions: [] as Permissions[], + }, + } as any) await store.dispatch(reportIncident(newIncident, onSuccessSpy)) expect(store.getActions()[0]).toEqual(reportIncidentStart()) @@ -144,4 +179,26 @@ describe('incident slice', () => { expect(onSuccessSpy).not.toHaveBeenCalled() }) }) + + describe('fetch incident', () => { + it('should fetch the incident', async () => { + const expectedIncident = { + id: '123', + description: 'description', + date: addDays(new Date(), 4).toISOString(), + department: 'some department', + category: 'category', + categoryItem: 'categoryItem', + } as Incident + jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident) + + const store = mockStore() + + await store.dispatch(fetchIncident(expectedIncident.id)) + + expect(store.getActions()[0]).toEqual(fetchIncidentStart()) + expect(IncidentRepository.find).toHaveBeenCalledWith(expectedIncident.id) + expect(store.getActions()[1]).toEqual(fetchIncidentSuccess(expectedIncident)) + }) + }) }) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index 78daea2f77..0ddb43fc5a 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -12,24 +12,44 @@ import * as titleUtil from '../../../page-header/useTitle' import * as ButtonBarProvider from '../../../page-header/ButtonBarProvider' import * as breadcrumbUtil from '../../../breadcrumbs/useAddBreadcrumbs' import ViewIncident from '../../../incidents/view/ViewIncident' +import Incident from '../../../model/Incident' +import IncidentRepository from '../../../clients/db/IncidentRepository' const mockStore = createMockStore([thunk]) describe('View Incident', () => { + const expectedDate = new Date(2020, 5, 1, 19, 48) let history: any + const expectedIncident = { + id: '1234', + code: 'some code', + department: 'some department', + description: 'some description', + category: 'some category', + categoryItem: 'some category item', + status: 'reported', + reportedBy: 'some user id', + reportedOn: expectedDate.toISOString(), + date: expectedDate.toISOString(), + } as Incident const setup = async (permissions: Permissions[]) => { jest.resetAllMocks() jest.spyOn(breadcrumbUtil, 'default') jest.spyOn(titleUtil, 'default') + jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident) history = createMemoryHistory() history.push(`/incidents/1234`) + const store = mockStore({ title: '', user: { permissions, }, + incident: { + incident: expectedIncident, + }, }) let wrapper: any @@ -50,17 +70,83 @@ describe('View Incident', () => { return wrapper } - it('should set the title', async () => { - await setup([Permissions.ViewIncident]) + describe('layout', () => { + it('should set the title', async () => { + await setup([Permissions.ViewIncident]) - expect(titleUtil.default).toHaveBeenCalledWith('incidents.reports.view') - }) + expect(titleUtil.default).toHaveBeenCalledWith(expectedIncident.code) + }) + + it('should set the breadcrumbs properly', async () => { + await setup([Permissions.ViewIncident]) + + expect(breadcrumbUtil.default).toHaveBeenCalledWith([ + { i18nKey: expectedIncident.code, location: '/incidents/1234' }, + ]) + }) + + it('should render the date of incident', async () => { + const wrapper = await setup([Permissions.ViewIncident]) - it('should set the breadcrumbs properly', async () => { - await setup([Permissions.ViewIncident]) + const dateOfIncidentFormGroup = wrapper.find('.incident-date') + expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.dateOfIncident') + expect(dateOfIncidentFormGroup.find('h5').text()).toEqual('2020-06-01 07:48 PM') + }) + + it('should render the status', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + const dateOfIncidentFormGroup = wrapper.find('.incident-status') + expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.status') + expect(dateOfIncidentFormGroup.find('h5').text()).toEqual(expectedIncident.status) + }) + + it('should render the reported by', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + const dateOfIncidentFormGroup = wrapper.find('.incident-reported-by') + expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.reportedBy') + expect(dateOfIncidentFormGroup.find('h5').text()).toEqual(expectedIncident.reportedBy) + }) - expect(breadcrumbUtil.default).toHaveBeenCalledWith([ - { i18nKey: 'incidents.reports.view', location: '/incidents/1234' }, - ]) + it('should render the reported on', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + const dateOfIncidentFormGroup = wrapper.find('.incident-reported-on') + expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.reportedOn') + expect(dateOfIncidentFormGroup.find('h5').text()).toEqual('2020-06-01 07:48 PM') + }) + + it('should render the department', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + const departmentInput = wrapper.findWhere((w: any) => w.prop('name') === 'department') + expect(departmentInput.prop('label')).toEqual('incidents.reports.department') + expect(departmentInput.prop('value')).toEqual(expectedIncident.department) + }) + + it('should render the category', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + const categoryInput = wrapper.findWhere((w: any) => w.prop('name') === 'category') + expect(categoryInput.prop('label')).toEqual('incidents.reports.category') + expect(categoryInput.prop('value')).toEqual(expectedIncident.category) + }) + + it('should render the category item', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + const categoryItemInput = wrapper.findWhere((w: any) => w.prop('name') === 'categoryItem') + expect(categoryItemInput.prop('label')).toEqual('incidents.reports.categoryItem') + expect(categoryItemInput.prop('value')).toEqual(expectedIncident.categoryItem) + }) + + it('should render the description', async () => { + const wrapper = await setup([Permissions.ViewIncident]) + + 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) + }) }) }) diff --git a/src/incidents/incident-slice.ts b/src/incidents/incident-slice.ts index 4d005cfa7f..dde9e43a18 100644 --- a/src/incidents/incident-slice.ts +++ b/src/incidents/incident-slice.ts @@ -45,6 +45,8 @@ const incidentSlice = createSlice({ name: 'incident', initialState, reducers: { + fetchIncidentStart: start, + fetchIncidentSuccess: finish, reportIncidentStart: start, reportIncidentSuccess: finish, reportIncidentError: error, @@ -52,11 +54,19 @@ const incidentSlice = createSlice({ }) export const { + fetchIncidentStart, + fetchIncidentSuccess, reportIncidentStart, reportIncidentSuccess, reportIncidentError, } = incidentSlice.actions +export const fetchIncident = (id: string): AppThunk => async (dispatch) => { + dispatch(fetchIncidentStart()) + const incident = await IncidentRepository.find(id) + dispatch(fetchIncidentSuccess(incident)) +} + function validateIncident(incident: Incident): Error { const newError: Error = {} @@ -98,6 +108,7 @@ export const reportIncident = ( incident.reportedOn = new Date(Date.now()).toISOString() incident.code = getIncidentCode() incident.reportedBy = getState().user.user.id + incident.status = 'reported' const newIncident = await IncidentRepository.save(incident) await dispatch(reportIncidentSuccess(newIncident)) if (onSuccess) { diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index 17b4c87a68..87484dc841 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -1,22 +1,101 @@ -import React from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router' +import { useDispatch, useSelector } from 'react-redux' +import { Column, Row } from '@hospitalrun/components' +import format from 'date-fns/format' import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs' import useTitle from '../../page-header/useTitle' +import { RootState } from '../../store' +import { fetchIncident } from '../incident-slice' +import TextInputWithLabelFormGroup from '../../components/input/TextInputWithLabelFormGroup' +import TextFieldWithLabelFormGroup from '../../components/input/TextFieldWithLabelFormGroup' const ViewIncident = () => { + const dispatch = useDispatch() const { t } = useTranslation() const { id } = useParams() - useTitle(t('incidents.reports.view')) + const { incident } = useSelector((state: RootState) => state.incident) + useTitle(incident ? incident.code : '') const breadcrumbs = [ { - i18nKey: 'incidents.reports.view', + i18nKey: incident ? incident.code : '', location: `/incidents/${id}`, }, ] useAddBreadcrumbs(breadcrumbs) - return

View Incident

+ useEffect(() => { + if (id) { + dispatch(fetchIncident(id)) + } + }, [dispatch, id]) + + return ( + <> + + +
+

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

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

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

+
{incident?.status}
+
+
+ +
+

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

+
{incident?.reportedBy}
+
+
+ +
+

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

+
{format(new Date(incident?.reportedOn || ''), 'yyyy-MM-dd hh:mm a')}
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + ) } export default ViewIncident