diff --git a/src/HospitalRun.tsx b/src/HospitalRun.tsx
index 3af081530d..31ab6b9990 100644
--- a/src/HospitalRun.tsx
+++ b/src/HospitalRun.tsx
@@ -5,6 +5,7 @@ import { Toaster } from '@hospitalrun/components'
import Appointments from 'scheduling/appointments/Appointments'
import NewAppointment from 'scheduling/appointments/new/NewAppointment'
import ViewAppointment from 'scheduling/appointments/view/ViewAppointment'
+import Breadcrumbs from 'breadcrumbs/Breadcrumbs'
import { ButtonBarProvider } from 'page-header/ButtonBarProvider'
import ButtonToolBar from 'page-header/ButtonToolBar'
import Sidebar from './components/Sidebar'
@@ -34,6 +35,7 @@ const HospitalRun = () => {
{title}
+
diff --git a/src/__tests__/HospitalRun.test.tsx b/src/__tests__/HospitalRun.test.tsx
index 569e241c02..d3cc0e40f7 100644
--- a/src/__tests__/HospitalRun.test.tsx
+++ b/src/__tests__/HospitalRun.test.tsx
@@ -7,10 +7,12 @@ import { mocked } from 'ts-jest/utils'
import thunk from 'redux-thunk'
import configureMockStore from 'redux-mock-store'
import { Toaster } from '@hospitalrun/components'
+
import { act } from 'react-dom/test-utils'
import Dashboard from 'dashboard/Dashboard'
import Appointments from 'scheduling/appointments/Appointments'
import NewAppointment from 'scheduling/appointments/new/NewAppointment'
+import { addBreadcrumbs } from 'breadcrumbs/breadcrumbs-slice'
import NewPatient from '../patients/new/NewPatient'
import EditPatient from '../patients/edit/EditPatient'
import ViewPatient from '../patients/view/ViewPatient'
@@ -25,13 +27,14 @@ describe('HospitalRun', () => {
describe('routing', () => {
describe('/patients/new', () => {
it('should render the new patient screen when /patients/new is accessed', () => {
+ const store = mockStore({
+ title: 'test',
+ user: { permissions: [Permissions.WritePatients] },
+ breadcrumbs: { breadcrumbs: [] },
+ })
+
const wrapper = mount(
-
+
@@ -39,6 +42,14 @@ describe('HospitalRun', () => {
)
expect(wrapper.find(NewPatient)).toHaveLength(1)
+
+ expect(store.getActions()).toContainEqual(
+ addBreadcrumbs([
+ { i18nKey: 'patients.label', location: '/patients' },
+ { i18nKey: 'patients.newPatient', location: '/patients/new' },
+ { i18nKey: 'dashboard.label', location: '/' },
+ ]),
+ )
})
it('should render the Dashboard if the user does not have write patient privileges', () => {
@@ -47,6 +58,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
@@ -74,14 +86,15 @@ describe('HospitalRun', () => {
mockedPatientRepository.find.mockResolvedValue(patient)
+ const store = mockStore({
+ title: 'test',
+ user: { permissions: [Permissions.WritePatients, Permissions.ReadPatients] },
+ patient: { patient },
+ breadcrumbs: { breadcrumbs: [] },
+ })
+
const wrapper = mount(
-
+
@@ -89,6 +102,15 @@ describe('HospitalRun', () => {
)
expect(wrapper.find(EditPatient)).toHaveLength(1)
+
+ expect(store.getActions()).toContainEqual(
+ addBreadcrumbs([
+ { i18nKey: 'patients.label', location: '/patients' },
+ { text: 'test test test', location: `/patients/${patient.id}` },
+ { i18nKey: 'patients.editPatient', location: `/patients/${patient.id}/edit` },
+ { i18nKey: 'dashboard.label', location: '/' },
+ ]),
+ )
})
it('should render the Dashboard when the user does not have read patient privileges', () => {
@@ -97,6 +119,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [Permissions.WritePatients] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
@@ -114,6 +137,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [Permissions.ReadPatients] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
@@ -141,14 +165,15 @@ describe('HospitalRun', () => {
mockedPatientRepository.find.mockResolvedValue(patient)
+ const store = mockStore({
+ title: 'test',
+ user: { permissions: [Permissions.ReadPatients] },
+ patient: { patient },
+ breadcrumbs: { breadcrumbs: [] },
+ })
+
const wrapper = mount(
-
+
@@ -156,6 +181,14 @@ describe('HospitalRun', () => {
)
expect(wrapper.find(ViewPatient)).toHaveLength(1)
+
+ expect(store.getActions()).toContainEqual(
+ addBreadcrumbs([
+ { i18nKey: 'patients.label', location: '/patients' },
+ { text: 'test test test', location: `/patients/${patient.id}` },
+ { i18nKey: 'dashboard.label', location: '/' },
+ ]),
+ )
})
it('should render the Dashboard when the user does not have read patient privileges', () => {
@@ -164,6 +197,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
@@ -178,14 +212,15 @@ describe('HospitalRun', () => {
describe('/appointments', () => {
it('should render the appointments screen when /appointments is accessed', async () => {
+ const store = mockStore({
+ title: 'test',
+ user: { permissions: [Permissions.ReadAppointments] },
+ appointments: { appointments: [] },
+ breadcrumbs: { breadcrumbs: [] },
+ })
+
const wrapper = mount(
-
+
@@ -197,6 +232,13 @@ describe('HospitalRun', () => {
})
expect(wrapper.find(Appointments)).toHaveLength(1)
+
+ expect(store.getActions()).toContainEqual(
+ addBreadcrumbs([
+ { i18nKey: 'scheduling.appointments.label', location: '/appointments' },
+ { i18nKey: 'dashboard.label', location: '/' },
+ ]),
+ )
})
it('should render the Dashboard when the user does not have read appointment privileges', () => {
@@ -205,7 +247,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [] },
- appointments: { appointments: [] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
@@ -221,13 +263,14 @@ describe('HospitalRun', () => {
describe('/appointments/new', () => {
it('should render the new appointment screen when /appointments/new is accessed', async () => {
+ const store = mockStore({
+ title: 'test',
+ user: { permissions: [Permissions.WriteAppointments] },
+ breadcrumbs: { breadcrumbs: [] },
+ })
+
const wrapper = mount(
-
+
@@ -235,6 +278,14 @@ describe('HospitalRun', () => {
)
expect(wrapper.find(NewAppointment)).toHaveLength(1)
+
+ expect(store.getActions()).toContainEqual(
+ addBreadcrumbs([
+ { i18nKey: 'scheduling.appointments.label', location: '/appointments' },
+ { i18nKey: 'scheduling.appointments.new', location: '/appointments/new' },
+ { i18nKey: 'dashboard.label', location: '/' },
+ ]),
+ )
})
it('should render the Dashboard when the user does not have read appointment privileges', () => {
@@ -243,6 +294,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
@@ -262,6 +314,7 @@ describe('HospitalRun', () => {
store={mockStore({
title: 'test',
user: { permissions: [Permissions.WritePatients] },
+ breadcrumbs: { breadcrumbs: [] },
})}
>
diff --git a/src/__tests__/breadcrumbs/Breadcrumbs.test.tsx b/src/__tests__/breadcrumbs/Breadcrumbs.test.tsx
new file mode 100644
index 0000000000..f14bc46b9f
--- /dev/null
+++ b/src/__tests__/breadcrumbs/Breadcrumbs.test.tsx
@@ -0,0 +1,58 @@
+import '../../__mocks__/matchMediaMock'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { mount } from 'enzyme'
+import { createMemoryHistory } from 'history'
+import { Router } from 'react-router-dom'
+import configureMockStore from 'redux-mock-store'
+import {
+ Breadcrumb as HRBreadcrumb,
+ BreadcrumbItem as HRBreadcrumbItem,
+} from '@hospitalrun/components'
+
+import Breadcrumbs from 'breadcrumbs/Breadcrumbs'
+import Breadcrumb from 'model/Breadcrumb'
+
+const mockStore = configureMockStore()
+
+describe('Breadcrumbs', () => {
+ const setup = (breadcrumbs: Breadcrumb[]) => {
+ const history = createMemoryHistory()
+ const store = mockStore({
+ breadcrumbs: { breadcrumbs },
+ })
+
+ const wrapper = mount(
+
+
+
+
+ ,
+ )
+
+ return wrapper
+ }
+
+ it('should not render the breadcrumb when there are no items in the store', () => {
+ const wrapper = setup([])
+
+ expect(wrapper.find(HRBreadcrumb)).toHaveLength(0)
+ expect(wrapper.find(HRBreadcrumbItem)).toHaveLength(0)
+ })
+
+ it('should render breadcrumbs items', () => {
+ const breadcrumbs = [
+ { i18nKey: 'patient.label', location: '/patient' },
+ { text: 'Bob', location: '/patient/1' },
+ { text: 'Edit Patient', location: '/patient/1/edit' },
+ ]
+ const wrapper = setup(breadcrumbs)
+
+ const items = wrapper.find(HRBreadcrumbItem)
+
+ expect(items).toHaveLength(3)
+ expect(items.at(0).text()).toEqual('patient.label')
+ expect(items.at(1).text()).toEqual('Bob')
+ expect(items.at(2).text()).toEqual('Edit Patient')
+ })
+})
diff --git a/src/__tests__/breadcrumbs/breadcrumbs-slice.test.ts b/src/__tests__/breadcrumbs/breadcrumbs-slice.test.ts
new file mode 100644
index 0000000000..10f73f19ff
--- /dev/null
+++ b/src/__tests__/breadcrumbs/breadcrumbs-slice.test.ts
@@ -0,0 +1,82 @@
+import '../../__mocks__/matchMediaMock'
+import { AnyAction } from 'redux'
+import breadcrumbs, { addBreadcrumbs, removeBreadcrumbs } from '../../breadcrumbs/breadcrumbs-slice'
+
+describe('breadcrumbs slice', () => {
+ describe('breadcrumbs reducer', () => {
+ it('should create the proper initial state with empty patients array', () => {
+ const breadcrumbsStore = breadcrumbs(undefined, {} as AnyAction)
+
+ expect(breadcrumbsStore.breadcrumbs).toEqual([])
+ })
+
+ it('should handle the ADD_BREADCRUMBS action', () => {
+ const breadcrumbsToAdd = [
+ { text: 'user', location: '/user' },
+ { text: 'Bob', location: '/user/1' },
+ ]
+
+ const breadcrumbsStore = breadcrumbs(undefined, {
+ type: addBreadcrumbs.type,
+ payload: breadcrumbsToAdd,
+ })
+
+ expect(breadcrumbsStore.breadcrumbs).toEqual(breadcrumbsToAdd)
+ })
+
+ it('should handle the ADD_BREADCRUMBS action with existing breadcrumbs', () => {
+ const breadcrumbsToAdd = [{ text: 'Bob', location: '/user/1' }]
+
+ const state = {
+ breadcrumbs: [{ text: 'user', location: '/user' }],
+ }
+
+ const breadcrumbsStore = breadcrumbs(state, {
+ type: addBreadcrumbs.type,
+ payload: breadcrumbsToAdd,
+ })
+
+ expect(breadcrumbsStore.breadcrumbs).toEqual([...state.breadcrumbs, ...breadcrumbsToAdd])
+ })
+
+ it('should handle the ADD_BREADCRUMBS action and sort the breadcrumbs by their location', () => {
+ const breadcrumbsToAdd = [{ text: 'Bob', location: '/user/1/' }]
+
+ const state = {
+ breadcrumbs: [
+ { text: 'user', location: '/user' },
+ { text: 'edit user', location: '/user/1/edit' },
+ ],
+ }
+
+ const breadcrumbsStore = breadcrumbs(state, {
+ type: addBreadcrumbs.type,
+ payload: breadcrumbsToAdd,
+ })
+
+ expect(breadcrumbsStore.breadcrumbs).toEqual([
+ { text: 'user', location: '/user' },
+ { text: 'Bob', location: '/user/1/' },
+ { text: 'edit user', location: '/user/1/edit' },
+ ])
+ })
+
+ it('should handle the REMOVE_BREADCRUMBS action', () => {
+ const breadcrumbsToRemove = [{ text: 'Bob', location: '/user/1' }]
+
+ const state = {
+ breadcrumbs: [
+ { text: 'user', location: '/user' },
+ { text: 'Bob', location: '/user/1' },
+ ],
+ }
+
+ const breadcrumbsStore = breadcrumbs(state, {
+ type: removeBreadcrumbs.type,
+ payload: breadcrumbsToRemove,
+ })
+
+ expect(breadcrumbsStore.breadcrumbs).toEqual([{ text: 'user', location: '/user' }])
+ })
+ })
+})
diff --git a/src/__tests__/breadcrumbs/useAddBreadcrumbs.test.tsx b/src/__tests__/breadcrumbs/useAddBreadcrumbs.test.tsx
new file mode 100644
index 0000000000..452f76ebdc
--- /dev/null
+++ b/src/__tests__/breadcrumbs/useAddBreadcrumbs.test.tsx
@@ -0,0 +1,65 @@
+import React from 'react'
+import { renderHook } from '@testing-library/react-hooks'
+import configureMockStore from 'redux-mock-store'
+import { Provider } from 'react-redux'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
+import * as breadcrumbsSlice from '../../breadcrumbs/breadcrumbs-slice'
+
+const store = configureMockStore()
+
+describe('useAddBreadcrumbs', () => {
+ beforeEach(() => jest.clearAllMocks())
+
+ it('should call addBreadcrumbs with the correct data', () => {
+ const wrapper = ({ children }: any) => {children}
+
+ jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs')
+ const breadcrumbs = [
+ {
+ text: 'Patients',
+ location: '/patients',
+ },
+ ]
+
+ renderHook(() => useAddBreadcrumbs(breadcrumbs), { wrapper } as any)
+ expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledTimes(1)
+ expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledWith(breadcrumbs)
+ })
+
+ it('should call addBreadcrumbs with an additional dashboard breadcrumb', () => {
+ const wrapper = ({ children }: any) => {children}
+
+ jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs')
+ const breadcrumbs = [
+ {
+ text: 'Patients',
+ location: '/patients',
+ },
+ ]
+
+ renderHook(() => useAddBreadcrumbs(breadcrumbs, true), { wrapper } as any)
+ expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledTimes(1)
+ expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledWith([
+ ...breadcrumbs,
+ { i18nKey: 'dashboard.label', location: '/' },
+ ])
+ })
+
+ it('should call removeBreadcrumbs with the correct data after unmount', () => {
+ const wrapper = ({ children }: any) => {children}
+
+ jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs')
+ jest.spyOn(breadcrumbsSlice, 'removeBreadcrumbs')
+ const breadcrumbs = [
+ {
+ text: 'Patients',
+ location: '/patients',
+ },
+ ]
+
+ const { unmount } = renderHook(() => useAddBreadcrumbs(breadcrumbs), { wrapper } as any)
+ unmount()
+ expect(breadcrumbsSlice.removeBreadcrumbs).toHaveBeenCalledTimes(1)
+ expect(breadcrumbsSlice.removeBreadcrumbs).toHaveBeenCalledWith(breadcrumbs)
+ })
+})
diff --git a/src/breadcrumbs/Breadcrumbs.tsx b/src/breadcrumbs/Breadcrumbs.tsx
new file mode 100644
index 0000000000..263f8e9476
--- /dev/null
+++ b/src/breadcrumbs/Breadcrumbs.tsx
@@ -0,0 +1,33 @@
+import React from 'react'
+import { useHistory } from 'react-router'
+import { useSelector } from 'react-redux'
+import { useTranslation } from 'react-i18next'
+import { Breadcrumb, BreadcrumbItem } from '@hospitalrun/components'
+import { RootState } from '../store'
+
+const Breadcrumbs = () => {
+ const history = useHistory()
+ const { t } = useTranslation()
+ const { breadcrumbs } = useSelector((state: RootState) => state.breadcrumbs)
+
+ if (breadcrumbs.length === 0) {
+ return null
+ }
+
+ return (
+
+ {breadcrumbs.map(({ i18nKey, text, location }, index) => {
+ const isLast = index === breadcrumbs.length - 1
+ const onClick = !isLast ? () => history.push(location) : undefined
+
+ return (
+
+ {i18nKey ? t(i18nKey) : text}
+
+ )
+ })}
+
+ )
+}
+
+export default Breadcrumbs
diff --git a/src/breadcrumbs/breadcrumbs-slice.ts b/src/breadcrumbs/breadcrumbs-slice.ts
new file mode 100644
index 0000000000..86a689e0d8
--- /dev/null
+++ b/src/breadcrumbs/breadcrumbs-slice.ts
@@ -0,0 +1,32 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit'
+import Breadcrumb from 'model/Breadcrumb'
+
+interface BreadcrumbsState {
+ breadcrumbs: Breadcrumb[]
+}
+
+const initialState: BreadcrumbsState = {
+ breadcrumbs: [],
+}
+
+const breadcrumbsSlice = createSlice({
+ name: 'breadcrumbs',
+ initialState,
+ reducers: {
+ addBreadcrumbs(state, { payload }: PayloadAction) {
+ state.breadcrumbs = [...state.breadcrumbs, ...payload].sort(
+ (b1, b2) => b1.location.length - b2.location.length,
+ )
+ },
+ removeBreadcrumbs(state, { payload }: PayloadAction) {
+ const locations = payload.map((b) => b.location)
+ state.breadcrumbs = state.breadcrumbs.filter(
+ (breadcrumb) => !locations.includes(breadcrumb.location),
+ )
+ },
+ },
+})
+
+export const { addBreadcrumbs, removeBreadcrumbs } = breadcrumbsSlice.actions
+
+export default breadcrumbsSlice.reducer
diff --git a/src/breadcrumbs/useAddBreadcrumbs.ts b/src/breadcrumbs/useAddBreadcrumbs.ts
new file mode 100644
index 0000000000..76d68e80ce
--- /dev/null
+++ b/src/breadcrumbs/useAddBreadcrumbs.ts
@@ -0,0 +1,21 @@
+import { useEffect } from 'react'
+import { useDispatch } from 'react-redux'
+import Breadcrumb from 'model/Breadcrumb'
+import { addBreadcrumbs, removeBreadcrumbs } from './breadcrumbs-slice'
+
+export default function useAddBreadcrumbs(breadcrumbs: Breadcrumb[], withDashboard = false): void {
+ const dispatch = useDispatch()
+
+ const breadcrumbsStringified = withDashboard
+ ? JSON.stringify([...breadcrumbs, { i18nKey: 'dashboard.label', location: '/' }])
+ : JSON.stringify(breadcrumbs)
+
+ useEffect(() => {
+ const breadcrumbsParsed: Breadcrumb[] = JSON.parse(breadcrumbsStringified)
+ dispatch(addBreadcrumbs(breadcrumbsParsed))
+
+ return () => {
+ dispatch(removeBreadcrumbs(breadcrumbsParsed))
+ }
+ }, [breadcrumbsStringified, dispatch])
+}
diff --git a/src/index.css b/src/index.css
index 49f3c5114d..417dbe2316 100644
--- a/src/index.css
+++ b/src/index.css
@@ -88,3 +88,8 @@ code {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}
+
+.breadcrumb {
+ padding: 0;
+ background-color: white;
+}
diff --git a/src/model/Breadcrumb.ts b/src/model/Breadcrumb.ts
new file mode 100644
index 0000000000..b93f41ed41
--- /dev/null
+++ b/src/model/Breadcrumb.ts
@@ -0,0 +1,5 @@
+export default interface Breadcrumb {
+ i18nKey?: string
+ text?: string
+ location: string
+}
diff --git a/src/patients/appointments/AppointmentsList.tsx b/src/patients/appointments/AppointmentsList.tsx
index feb0f9189c..7224bce5f8 100644
--- a/src/patients/appointments/AppointmentsList.tsx
+++ b/src/patients/appointments/AppointmentsList.tsx
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import { TextInput, Button, List, ListItem, Container, Row } from '@hospitalrun/components'
import { RootState } from '../../store'
import { fetchPatientAppointments } from '../../scheduling/appointments/appointments-slice'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
interface Props {
patientId: string
@@ -19,6 +20,14 @@ const AppointmentsList = (props: Props) => {
const { appointments } = useSelector((state: RootState) => state.appointments)
const [searchText, setSearchText] = useState('')
+ const breadcrumbs = [
+ {
+ i18nKey: 'scheduling.appointments.label',
+ location: `/patients/${patientId}/appointments`,
+ },
+ ]
+ useAddBreadcrumbs(breadcrumbs)
+
useEffect(() => {
dispatch(fetchPatientAppointments(patientId))
}, [dispatch, patientId])
diff --git a/src/patients/edit/EditPatient.tsx b/src/patients/edit/EditPatient.tsx
index b47de7491c..2e3db2da99 100644
--- a/src/patients/edit/EditPatient.tsx
+++ b/src/patients/edit/EditPatient.tsx
@@ -10,6 +10,7 @@ import Patient from '../../model/Patient'
import { updatePatient, fetchPatient } from '../patient-slice'
import { RootState } from '../../store'
import { getPatientFullName, getPatientName } from '../util/patient-name-util'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
const getFriendlyId = (p: Patient): string => {
if (p) {
@@ -34,6 +35,13 @@ const EditPatient = () => {
)})`,
)
+ const breadcrumbs = [
+ { i18nKey: 'patients.label', location: '/patients' },
+ { text: getPatientFullName(reduxPatient), location: `/patients/${reduxPatient.id}` },
+ { i18nKey: 'patients.editPatient', location: `/patients/${reduxPatient.id}/edit` },
+ ]
+ useAddBreadcrumbs(breadcrumbs, true)
+
useEffect(() => {
setPatient(reduxPatient)
}, [reduxPatient])
diff --git a/src/patients/list/Patients.tsx b/src/patients/list/Patients.tsx
index 1a8373eee4..2a428793ee 100644
--- a/src/patients/list/Patients.tsx
+++ b/src/patients/list/Patients.tsx
@@ -7,11 +7,15 @@ import { useButtonToolbarSetter } from 'page-header/ButtonBarProvider'
import { RootState } from '../../store'
import { fetchPatients, searchPatients } from '../patients-slice'
import useTitle from '../../page-header/useTitle'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
+
+const breadcrumbs = [{ i18nKey: 'patients.label', location: '/patients' }]
const Patients = () => {
const { t } = useTranslation()
const history = useHistory()
useTitle(t('patients.label'))
+ useAddBreadcrumbs(breadcrumbs, true)
const dispatch = useDispatch()
const { patients, isLoading } = useSelector((state: RootState) => state.patients)
diff --git a/src/patients/new/NewPatient.tsx b/src/patients/new/NewPatient.tsx
index 070470b7eb..5c80cdc0b6 100644
--- a/src/patients/new/NewPatient.tsx
+++ b/src/patients/new/NewPatient.tsx
@@ -9,6 +9,12 @@ import useTitle from '../../page-header/useTitle'
import Patient from '../../model/Patient'
import { createPatient } from '../patient-slice'
import { getPatientName } from '../util/patient-name-util'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
+
+const breadcrumbs = [
+ { i18nKey: 'patients.label', location: '/patients' },
+ { i18nKey: 'patients.newPatient', location: '/patients/new' },
+]
const NewPatient = () => {
const { t } = useTranslation()
@@ -19,6 +25,7 @@ const NewPatient = () => {
const [errorMessage, setErrorMessage] = useState('')
useTitle(t('patients.newPatient'))
+ useAddBreadcrumbs(breadcrumbs, true)
const onCancel = () => {
history.push('/patients')
diff --git a/src/patients/related-persons/RelatedPersonTab.tsx b/src/patients/related-persons/RelatedPersonTab.tsx
index 8eecc152da..f3451dae19 100644
--- a/src/patients/related-persons/RelatedPersonTab.tsx
+++ b/src/patients/related-persons/RelatedPersonTab.tsx
@@ -10,6 +10,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'store'
import Permissions from 'model/Permissions'
import PatientRepository from 'clients/db/PatientRepository'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
interface Props {
patient: Patient
@@ -28,6 +29,14 @@ const RelatedPersonTab = (props: Props) => {
const [showNewRelatedPersonModal, setShowRelatedPersonModal] = useState(false)
const [relatedPersons, setRelatedPersons] = useState(undefined)
+ const breadcrumbs = [
+ {
+ i18nKey: 'patient.relatedPersons.label',
+ location: `/patients/${patient.id}/relatedpersons`,
+ },
+ ]
+ useAddBreadcrumbs(breadcrumbs)
+
useEffect(() => {
const fetchRelatedPersons = async () => {
const fetchedRelatedPersons: Patient[] = []
diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx
index 1d221e1ce0..80358d8562 100644
--- a/src/patients/view/ViewPatient.tsx
+++ b/src/patients/view/ViewPatient.tsx
@@ -12,6 +12,7 @@ import { getPatientFullName } from '../util/patient-name-util'
import Patient from '../../model/Patient'
import GeneralInformation from '../GeneralInformation'
import RelatedPerson from '../related-persons/RelatedPersonTab'
+import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs'
import AppointmentsList from '../appointments/AppointmentsList'
const getFriendlyId = (p: Patient): string => {
@@ -47,6 +48,12 @@ const ViewPatient = () => {
,
])
+ const breadcrumbs = [
+ { i18nKey: 'patients.label', location: '/patients' },
+ { text: getPatientFullName(patient), location: `/patients/${patient.id}` },
+ ]
+ useAddBreadcrumbs(breadcrumbs, true)
+
const { id } = useParams()
useEffect(() => {
if (id) {
diff --git a/src/scheduling/appointments/Appointments.tsx b/src/scheduling/appointments/Appointments.tsx
index 55d618d98a..f152f49507 100644
--- a/src/scheduling/appointments/Appointments.tsx
+++ b/src/scheduling/appointments/Appointments.tsx
@@ -6,6 +6,7 @@ import { useSelector, useDispatch } from 'react-redux'
import { RootState } from 'store'
import { useHistory } from 'react-router'
import PatientRepository from 'clients/db/PatientRepository'
+import useAddBreadcrumbs from 'breadcrumbs/useAddBreadcrumbs'
import { useButtonToolbarSetter } from 'page-header/ButtonBarProvider'
import { fetchAppointments } from './appointments-slice'
@@ -17,6 +18,8 @@ interface Event {
allDay: boolean
}
+const breadcrumbs = [{ i18nKey: 'scheduling.appointments.label', location: '/appointments' }]
+
const Appointments = () => {
const { t } = useTranslation()
const history = useHistory()
@@ -36,6 +39,7 @@ const Appointments = () => {
{t('scheduling.appointments.new')}
,
])
+ useAddBreadcrumbs(breadcrumbs, true)
useEffect(() => {
dispatch(fetchAppointments())
diff --git a/src/scheduling/appointments/new/NewAppointment.tsx b/src/scheduling/appointments/new/NewAppointment.tsx
index 538ae33f98..487948f5eb 100644
--- a/src/scheduling/appointments/new/NewAppointment.tsx
+++ b/src/scheduling/appointments/new/NewAppointment.tsx
@@ -1,7 +1,6 @@
import React, { useState } from 'react'
import useTitle from 'page-header/useTitle'
import { useTranslation } from 'react-i18next'
-
import roundToNearestMinutes from 'date-fns/roundToNearestMinutes'
import { useHistory } from 'react-router'
import { useDispatch } from 'react-redux'
@@ -9,14 +8,21 @@ import Appointment from 'model/Appointment'
import addMinutes from 'date-fns/addMinutes'
import { isBefore } from 'date-fns'
import { Button, Alert } from '@hospitalrun/components'
+import useAddBreadcrumbs from '../../../breadcrumbs/useAddBreadcrumbs'
import { createAppointment } from '../appointments-slice'
import AppointmentDetailForm from '../AppointmentDetailForm'
+const breadcrumbs = [
+ { i18nKey: 'scheduling.appointments.label', location: '/appointments' },
+ { i18nKey: 'scheduling.appointments.new', location: '/appointments/new' },
+]
+
const NewAppointment = () => {
const { t } = useTranslation()
const history = useHistory()
const dispatch = useDispatch()
useTitle(t('scheduling.appointments.new'))
+ useAddBreadcrumbs(breadcrumbs, true)
const startDateTime = roundToNearestMinutes(new Date(), { nearestTo: 15 })
const endDateTime = addMinutes(startDateTime, 60)
diff --git a/src/scheduling/appointments/view/ViewAppointment.tsx b/src/scheduling/appointments/view/ViewAppointment.tsx
index c2e78947ac..5744041875 100644
--- a/src/scheduling/appointments/view/ViewAppointment.tsx
+++ b/src/scheduling/appointments/view/ViewAppointment.tsx
@@ -5,8 +5,18 @@ import { RootState } from 'store'
import { useParams } from 'react-router'
import { Spinner } from '@hospitalrun/components'
import { useTranslation } from 'react-i18next'
+import Appointment from 'model/Appointment'
import { fetchAppointment } from '../appointment-slice'
import AppointmentDetailForm from '../AppointmentDetailForm'
+import useAddBreadcrumbs from '../../../breadcrumbs/useAddBreadcrumbs'
+
+function getAppointmentLabel(appointment: Appointment) {
+ const { id, startDateTime, endDateTime } = appointment
+
+ return startDateTime && endDateTime
+ ? `${new Date(startDateTime).toLocaleString()} - ${new Date(endDateTime).toLocaleString()}`
+ : id
+}
const ViewAppointment = () => {
const { t } = useTranslation()
@@ -15,6 +25,12 @@ const ViewAppointment = () => {
const { id } = useParams()
const { appointment, patient, isLoading } = useSelector((state: RootState) => state.appointment)
+ const breadcrumbs = [
+ { i18nKey: 'scheduling.appointments.label', location: '/appointments' },
+ { text: getAppointmentLabel(appointment), location: `/patients/${appointment.id}` },
+ ]
+ useAddBreadcrumbs(breadcrumbs, true)
+
useEffect(() => {
if (id) {
dispatch(fetchAppointment(id))
diff --git a/src/store/index.ts b/src/store/index.ts
index 60176dfc5c..b226815116 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -6,6 +6,7 @@ import appointment from '../scheduling/appointments/appointment-slice'
import appointments from '../scheduling/appointments/appointments-slice'
import title from '../page-header/title-slice'
import user from '../user/user-slice'
+import breadcrumbs from '../breadcrumbs/breadcrumbs-slice'
const reducer = combineReducers({
patient,
@@ -14,6 +15,7 @@ const reducer = combineReducers({
user,
appointment,
appointments,
+ breadcrumbs,
})
const store = configureStore({