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

feat(AppointmentsList): add an appointments tab to the patient view #1823

Merged
merged 4 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": false,
"license": "MIT",
"dependencies": {
"@hospitalrun/components": "^0.33.0",
"@hospitalrun/components": "^0.33.1",
"@reduxjs/toolkit": "~1.2.1",
"@types/pouchdb-find": "~6.3.4",
"bootstrap": "~4.4.1",
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/patients/view/ViewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('ViewPatient', () => {

wrapper.update()

const editButton = wrapper.find(Button).at(2)
const editButton = wrapper.find(Button).at(3)
const onClick = editButton.prop('onClick') as any
expect(editButton.text().trim()).toEqual('actions.edit')

Expand Down Expand Up @@ -120,9 +120,10 @@ describe('ViewPatient', () => {
const tabs = tabsHeader.find(Tab)
expect(tabsHeader).toHaveLength(1)

expect(tabs).toHaveLength(2)
expect(tabs).toHaveLength(3)
expect(tabs.at(0).prop('label')).toEqual('patient.generalInformation')
expect(tabs.at(1).prop('label')).toEqual('patient.relatedPersons.label')
expect(tabs.at(2).prop('label')).toEqual('scheduling.appointments.label')
})

it('should mark the general information tab as active and render the general information component when route is /patients/:id', async () => {
Expand Down
32 changes: 32 additions & 0 deletions src/clients/db/AppointmentsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,38 @@ export class AppointmentRepository extends Repository<Appointment> {
constructor() {
super(appointments)
}

// Fuzzy search for patient appointments. Used for patient appointment search bar
async searchPatientAppointments(patientId: string, text: string): Promise<Appointment[]> {
Copy link
Member

Choose a reason for hiding this comment

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

Searching this list is not something that I had previously considered. I think that this is a really neat idea!

@fox1t @tehkapa @lauggh @StefanoMiC,

What does everyone thing the requirements should be here? I see a really cool opportunity for having filter buttons for the different appointment types.

Copy link
Member

Choose a reason for hiding this comment

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

We can leave it like this for now. We need to open a new feature issue to search about this. We really want a multi-field fuzzy search on almost every entity.

return super.search({
selector: {
$and: [
{
patientId,
},
{
$or: [
{
location: {
$regex: RegExp(text, 'i'),
},
},
{
reason: {
$regex: RegExp(text, 'i'),
},
},
{
type: {
$regex: RegExp(text, 'i'),
},
},
],
},
],
},
})
}
}

export default new AppointmentRepository()
7 changes: 4 additions & 3 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Sidebar = () => {
const { t } = useTranslation()
const path = useLocation()
const history = useHistory()
const { pathname } = path

const navigateTo = (location: string) => {
history.push(location)
Expand All @@ -21,23 +22,23 @@ const Sidebar = () => {
<div className="sidebar-sticky">
<List layout="flush" className="nav flex-column">
<ListItem
active={path.pathname === '/'}
active={pathname === '/'}
onClick={() => navigateTo('/')}
className="nav-item"
style={listItemStyle}
>
<Icon icon="dashboard" /> {t('dashboard.label')}
</ListItem>
<ListItem
active={path.pathname.includes('patient')}
active={pathname.split('/')[1].includes('patient')}
onClick={() => navigateTo('/patients')}
className="nav-item"
style={listItemStyle}
>
<Icon icon="patients" /> {t('patients.label')}
</ListItem>
<ListItem
active={path.pathname.includes('appointments')}
active={pathname.split('/')[1].includes('appointments')}
onClick={() => navigateTo('/appointments')}
className="nav-item"
style={listItemStyle}
Expand Down
71 changes: 71 additions & 0 deletions src/patients/appointments/AppointmentsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useEffect, useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useHistory } from 'react-router'
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'

interface Props {
patientId: string
}

const AppointmentsList = (props: Props) => {
const dispatch = useDispatch()
const history = useHistory()
const { t } = useTranslation()

const { patientId } = props
const { appointments } = useSelector((state: RootState) => state.appointments)
const [searchText, setSearchText] = useState<string>('')

useEffect(() => {
dispatch(fetchPatientAppointments(patientId))
}, [dispatch, patientId])

const list = (
// inline style added to pick up on newlines for string literal
<ul style={{ whiteSpace: 'pre-line' }}>
{appointments.map((a) => (
<ListItem action key={a.id} onClick={() => history.push(`/appointments/${a.id}`)}>
{new Date(a.startDateTime).toLocaleString()}
</ListItem>
))}
</ul>
)

const onSearchBoxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(event.target.value)
}

const onSearchFormSubmit = (event: React.FormEvent | React.MouseEvent) => {
event.preventDefault()
dispatch(fetchPatientAppointments(patientId, searchText))
}

return (
<Container>
<form className="form-inline" onSubmit={onSearchFormSubmit}>
<div className="input-group" style={{ width: '100%' }}>
<TextInput
size="lg"
value={searchText}
placeholder={t('actions.search')}
onChange={onSearchBoxChange}
/>
<div className="input-group-append">
<Button onClick={onSearchFormSubmit}>{t('actions.search')}</Button>
</div>
Comment on lines +50 to +58
Copy link
Member

Choose a reason for hiding this comment

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

Now that we have two search boxes, we should consider abstracting that to a component in the /components folder.

</div>
</form>

<Row>
<List layout="flush" style={{ width: '100%', marginTop: '10px', marginLeft: '-25px' }}>
{list}
</List>
</Row>
</Container>
)
}

export default AppointmentsList
9 changes: 9 additions & 0 deletions src/patients/view/ViewPatient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getPatientFullName } from '../util/patient-name-util'
import Patient from '../../model/Patient'
import GeneralInformation from '../GeneralInformation'
import RelatedPerson from '../related-persons/RelatedPersonTab'
import AppointmentsList from '../appointments/AppointmentsList'

const getFriendlyId = (p: Patient): string => {
if (p) {
Expand Down Expand Up @@ -54,6 +55,11 @@ const ViewPatient = () => {
label={t('patient.relatedPersons.label')}
onClick={() => history.push(`/patients/${patient.id}/relatedpersons`)}
/>
<Tab
active={location.pathname === `/patients/${patient.id}/appointments`}
label={t('scheduling.appointments.label')}
onClick={() => history.push(`/patients/${patient.id}/appointments`)}
/>
</TabsHeader>
<Panel>
<Route exact path="/patients/:id">
Expand All @@ -76,6 +82,9 @@ const ViewPatient = () => {
<Route exact path="/patients/:id/relatedpersons">
<RelatedPerson patient={patient} />
</Route>
<Route exact path="/patients/:id/appointments">
<AppointmentsList patientId={patient.id} />
</Route>
</Panel>
</div>
)
Expand Down
17 changes: 17 additions & 0 deletions src/scheduling/appointments/appointments-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ export const fetchAppointments = (): AppThunk => async (dispatch) => {
dispatch(fetchAppointmentsSuccess(appointments))
}

export const fetchPatientAppointments = (
patientId: string,
searchString?: string,
): AppThunk => async (dispatch) => {
dispatch(fetchAppointmentsStart())

let appointments
if (searchString === undefined || searchString.trim() === '') {
const query = { selector: { patientId } }
appointments = await AppointmentRepository.search(query)
} else {
appointments = await AppointmentRepository.searchPatientAppointments(patientId, searchString)
}

dispatch(fetchAppointmentsSuccess(appointments))
}

export const createAppointment = (appointment: Appointment, history: any): AppThunk => async (
dispatch,
) => {
Expand Down