diff --git a/src/__tests__/HospitalRun.test.tsx b/src/__tests__/HospitalRun.test.tsx index cc011ad27c..9d66e3a2e1 100644 --- a/src/__tests__/HospitalRun.test.tsx +++ b/src/__tests__/HospitalRun.test.tsx @@ -114,6 +114,7 @@ describe('HospitalRun', () => { store={mockStore({ title: 'test', user: { permissions: [Permissions.ReadAppointments] }, + appointments: { appointments: [] }, })} > @@ -131,6 +132,7 @@ describe('HospitalRun', () => { store={mockStore({ title: 'test', user: { permissions: [] }, + appointments: { appointments: [] }, })} > diff --git a/src/__tests__/scheduling/appointments-slice.test.ts b/src/__tests__/scheduling/appointments-slice.test.ts index 664863d016..75c8cd809c 100644 --- a/src/__tests__/scheduling/appointments-slice.test.ts +++ b/src/__tests__/scheduling/appointments-slice.test.ts @@ -1,11 +1,15 @@ import { AnyAction } from 'redux' +import { mocked } from 'ts-jest/utils' import Appointment from 'model/Appointment' import { createMemoryHistory } from 'history' import AppointmentRepository from 'clients/db/AppointmentsRepository' -import { mocked } from 'ts-jest/utils' + import appointments, { createAppointmentStart, createAppointment, + getAppointmentsStart, + getAppointmentsSuccess, + fetchAppointments, } from '../../scheduling/appointments/appointments-slice' describe('appointments slice', () => { @@ -22,6 +26,81 @@ describe('appointments slice', () => { expect(appointmentsStore.isLoading).toBeTruthy() }) + + it('should handle the GET_APPOINTMENTS_START action', () => { + const appointmentsStore = appointments(undefined, { + type: getAppointmentsStart.type, + }) + + expect(appointmentsStore.isLoading).toBeTruthy() + }) + + it('should handle the GET_APPOINTMENTS_SUCCESS action', () => { + const expectedAppointments = [ + { + patientId: '1234', + startDateTime: new Date().toISOString(), + endDateTime: new Date().toISOString(), + }, + ] + const appointmentsStore = appointments(undefined, { + type: getAppointmentsSuccess.type, + payload: expectedAppointments, + }) + + expect(appointmentsStore.isLoading).toBeFalsy() + expect(appointmentsStore.appointments).toEqual(expectedAppointments) + }) + }) + + describe('fetchAppointments()', () => { + let findAllSpy = jest.spyOn(AppointmentRepository, 'findAll') + const expectedAppointments: Appointment[] = [ + { + id: '1', + rev: '1', + patientId: '123', + startDateTime: new Date().toISOString(), + endDateTime: new Date().toISOString(), + location: 'location', + type: 'type', + reason: 'reason', + }, + ] + + beforeEach(() => { + findAllSpy = jest.spyOn(AppointmentRepository, 'findAll') + mocked(AppointmentRepository, true).findAll.mockResolvedValue( + expectedAppointments as Appointment[], + ) + }) + + it('should dispatch the GET_APPOINTMENTS_START event', async () => { + const dispatch = jest.fn() + const getState = jest.fn() + await fetchAppointments()(dispatch, getState, null) + + expect(dispatch).toHaveBeenCalledWith({ type: getAppointmentsStart.type }) + }) + + it('should call the AppointmentsRepository findAll() function', async () => { + const dispatch = jest.fn() + const getState = jest.fn() + await fetchAppointments()(dispatch, getState, null) + + expect(findAllSpy).toHaveBeenCalled() + }) + + it('should dispatch the GET_APPOINTMENTS_SUCCESS event', async () => { + const dispatch = jest.fn() + const getState = jest.fn() + await fetchAppointments()(dispatch, getState, null) + + expect(dispatch).toHaveBeenCalledWith({ + type: getAppointmentsSuccess.type, + payload: expectedAppointments, + }) + }) }) describe('createAppointments()', () => { diff --git a/src/__tests__/scheduling/appointments/Appointments.test.tsx b/src/__tests__/scheduling/appointments/Appointments.test.tsx index 708a4f36d7..5fe3281274 100644 --- a/src/__tests__/scheduling/appointments/Appointments.test.tsx +++ b/src/__tests__/scheduling/appointments/Appointments.test.tsx @@ -7,13 +7,26 @@ import Appointments from 'scheduling/appointments/Appointments' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import { Calendar } from '@hospitalrun/components' +import { act } from '@testing-library/react' import * as titleUtil from '../../../page-header/useTitle' describe('Appointments', () => { - const setup = () => { + const expectedAppointments = [ + { + id: '123', + rev: '1', + patientId: '1234', + startDateTime: new Date().toISOString(), + endDateTime: new Date().toISOString(), + location: 'location', + reason: 'reason', + }, + ] + + const setup = async () => { const mockStore = createMockStore([thunk]) return mount( - + @@ -27,8 +40,25 @@ describe('Appointments', () => { expect(titleUtil.default).toHaveBeenCalledWith('scheduling.appointments.label') }) - it('should render a calendar', () => { - const wrapper = setup() - expect(wrapper.find(Calendar)).toHaveLength(1) + it('should render a calendar with the proper events', async () => { + let wrapper: any + await act(async () => { + wrapper = await setup() + }) + wrapper.update() + + const expectedEvents = [ + { + id: expectedAppointments[0].id, + start: new Date(expectedAppointments[0].startDateTime), + end: new Date(expectedAppointments[0].endDateTime), + title: expectedAppointments[0].patientId, + allDay: false, + }, + ] + + const calendar = wrapper.find(Calendar) + expect(calendar).toHaveLength(1) + expect(calendar.prop('events')).toEqual(expectedEvents) }) }) diff --git a/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx b/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx new file mode 100644 index 0000000000..113e79fd6f --- /dev/null +++ b/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx @@ -0,0 +1,45 @@ +import '../../../../__mocks__/matchMediaMock' +import React from 'react' +import { mount } from 'enzyme' +import { Provider } from 'react-redux' +import createMockStore from 'redux-mock-store' +import thunk from 'redux-thunk' +import Appointment from 'model/Appointment' +import ViewAppointment from 'scheduling/appointments/view/ViewAppointment' +import * as titleUtil from '../../../../page-header/useTitle' +import { Router, Route } from 'react-router' +import { createMemoryHistory } from 'history' + +describe('View Appointment', () => { + describe('header', () => { + it('should use the correct title', () => { + jest.spyOn(titleUtil, 'default') + const history = createMemoryHistory() + const mockStore = createMockStore([thunk]) + const store = mockStore({ + appointment: { + appointment: { + id: '123', + patientId: 'patient', + } as Appointment, + isLoading: false, + }, + }) + mount( + + + + + + + , + ) + + expect(titleUtil.default).toHaveBeenCalledWith('scheduling.appointments.view.label') + }) + }) + + it('should render a loading spinner', () => {}) + + it('should render a AppointmentDetailForm with the correct data', () => {}) +}) diff --git a/src/scheduling/appointments/AppointmentDetailForm.tsx b/src/scheduling/appointments/AppointmentDetailForm.tsx new file mode 100644 index 0000000000..adec28a49c --- /dev/null +++ b/src/scheduling/appointments/AppointmentDetailForm.tsx @@ -0,0 +1,115 @@ +import React from 'react' +import Appointment from 'model/Appointment' +import DateTimePickerWithLabelFormGroup from 'components/input/DateTimePickerWithLabelFormGroup' +import { Typeahead, Label } from '@hospitalrun/components' +import Patient from 'model/Patient' +import PatientRepository from 'clients/db/PatientRepository' +import TextInputWithLabelFormGroup from 'components/input/TextInputWithLabelFormGroup' +import TextFieldWithLabelFormGroup from 'components/input/TextFieldWithLabelFormGroup' +import SelectWithLabelFormGroup from 'components/input/SelectWithLableFormGroup' +import { useTranslation } from 'react-i18next' +interface Props { + appointment: Appointment + onAppointmentChange: (appointment: Appointment) => void +} + +const AppointmentDetailForm = (props: Props) => { + const { onAppointmentChange, appointment } = props + const { t } = useTranslation() + return ( + <> +
+
+
+
+
+
+
+
+ { + onAppointmentChange({ ...appointment, startDateTime: date.toISOString() }) + }} + /> +
+
+ { + onAppointmentChange({ ...appointment, endDateTime: date.toISOString() }) + }} + /> +
+
+
+
+ { + onAppointmentChange({ ...appointment, location: event?.target.value }) + }} + /> +
+
+
+
+ ) => { + onAppointmentChange({ ...appointment, type: event.currentTarget.value }) + }} + /> +
+
+
+
+
+ { + onAppointmentChange({ ...appointment, reason: event?.target.value }) + }} + /> +
+
+
+ + ) +} + +export default AppointmentDetailForm diff --git a/src/scheduling/appointments/Appointments.tsx b/src/scheduling/appointments/Appointments.tsx index a9e8c1d431..44d07dc029 100644 --- a/src/scheduling/appointments/Appointments.tsx +++ b/src/scheduling/appointments/Appointments.tsx @@ -1,12 +1,61 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { Calendar } from '@hospitalrun/components' import useTitle from 'page-header/useTitle' import { useTranslation } from 'react-i18next' +import { useSelector, useDispatch } from 'react-redux' +import { RootState } from 'store' +import { useHistory } from 'react-router' +import { fetchAppointments } from './appointments-slice' + +interface Event { + id: string + start: Date + end: Date + title: string + allDay: boolean +} const Appointments = () => { const { t } = useTranslation() + const history = useHistory() useTitle(t('scheduling.appointments.label')) - return + const dispatch = useDispatch() + const { appointments } = useSelector((state: RootState) => state.appointments) + const [events, setEvents] = useState([]) + + useEffect(() => { + dispatch(fetchAppointments()) + }, [dispatch]) + + useEffect(() => { + if (appointments) { + const newEvents: Event[] = [] + appointments.forEach((a) => { + const event = { + id: a.id, + start: new Date(a.startDateTime), + end: new Date(a.endDateTime), + title: a.patientId, + allDay: false, + } + + newEvents.push(event) + }) + + setEvents(newEvents) + } + }, [appointments]) + + return ( +
+ { + history.push(`/appointments/${event.id}`) + }} + /> +
+ ) } export default Appointments diff --git a/src/scheduling/appointments/appointments-slice.ts b/src/scheduling/appointments/appointments-slice.ts index a7a12e8125..5152e75057 100644 --- a/src/scheduling/appointments/appointments-slice.ts +++ b/src/scheduling/appointments/appointments-slice.ts @@ -1,14 +1,16 @@ -import { createSlice } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' import Appointment from 'model/Appointment' import { AppThunk } from 'store' import AppointmentRepository from 'clients/db/AppointmentsRepository' interface AppointmentsState { isLoading: boolean + appointments: Appointment[] } const initialState: AppointmentsState = { isLoading: false, + appointments: [], } function startLoading(state: AppointmentsState) { @@ -20,10 +22,25 @@ const appointmentsSlice = createSlice({ initialState, reducers: { createAppointmentStart: startLoading, + getAppointmentsStart: startLoading, + getAppointmentsSuccess: (state, { payload }: PayloadAction) => { + state.isLoading = false + state.appointments = payload + }, }, }) -export const { createAppointmentStart } = appointmentsSlice.actions +export const { + createAppointmentStart, + getAppointmentsStart, + getAppointmentsSuccess, +} = appointmentsSlice.actions + +export const fetchAppointments = (): AppThunk => async (dispatch) => { + dispatch(getAppointmentsStart()) + const appointments = await AppointmentRepository.findAll() + dispatch(getAppointmentsSuccess(appointments)) +} export const createAppointment = (appointment: Appointment, history: any): AppThunk => async ( dispatch, diff --git a/src/store/index.ts b/src/store/index.ts index f77ebfb145..58a258e33d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,6 +2,7 @@ import { configureStore, combineReducers, Action } from '@reduxjs/toolkit' import ReduxThunk, { ThunkAction } from 'redux-thunk' import patient from '../patients/patient-slice' import patients from '../patients/patients-slice' +import appointments from '../scheduling/appointments/appointments-slice' import title from '../page-header/title-slice' import user from '../user/user-slice' @@ -10,6 +11,7 @@ const reducer = combineReducers({ patients, title, user, + appointments, }) const store = configureStore({