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

Commit

Permalink
feat(viewpatients): add paging feature in ViewPatients component
Browse files Browse the repository at this point in the history
feat #1969
  • Loading branch information
akshay-ap committed Apr 28, 2020
1 parent 02c4e6b commit dff2b3e
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 47 deletions.
53 changes: 36 additions & 17 deletions src/__tests__/patients/list/ViewPatients.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,47 @@ import { mocked } from 'ts-jest/utils'
import { act } from 'react-dom/test-utils'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import format from 'date-fns/format'
import Page from 'clients/Page'
import { Unsorted } from 'clients/db/SortRequest'
import ViewPatients from '../../../patients/list/ViewPatients'
import PatientRepository from '../../../clients/db/PatientRepository'
import * as patientSlice from '../../../patients/patients-slice'
import Patient from '../../../model/Patient'
import { UnpagedRequest } from '../../../clients/db/PageRequest'

const middlewares = [thunk]
const mockStore = configureStore(middlewares)

describe('Patients', () => {
const patients = [
{
id: '123',
fullName: 'test test',
givenName: 'test',
familyName: 'test',
code: 'P12345',
sex: 'male',
dateOfBirth: new Date().toISOString(),
},
]
const patients: Page<Patient> = {
content: [
{
id: '123',
fullName: 'test test',
isApproximateDateOfBirth: false,
givenName: 'test',
familyName: 'test',
code: 'P12345',
sex: 'male',
dateOfBirth: new Date().toISOString(),
phoneNumber: '99999999',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
rev: '',
},
],
hasNext: false,
hasPrevious: false,
pageRequest: UnpagedRequest,
}
const mockedPatientRepository = mocked(PatientRepository, true)

const setup = (isLoading?: boolean) => {
const store = mockStore({
patients: {
patients,
isLoading,
pageRequest: UnpagedRequest,
},
})
return mount(
Expand Down Expand Up @@ -80,12 +95,12 @@ describe('Patients', () => {
expect(tableHeaders.at(3).text()).toEqual('patient.sex')
expect(tableHeaders.at(4).text()).toEqual('patient.dateOfBirth')

expect(tableColumns.at(0).text()).toEqual(patients[0].code)
expect(tableColumns.at(1).text()).toEqual(patients[0].givenName)
expect(tableColumns.at(2).text()).toEqual(patients[0].familyName)
expect(tableColumns.at(3).text()).toEqual(patients[0].sex)
expect(tableColumns.at(0).text()).toEqual(patients.content[0].code)
expect(tableColumns.at(1).text()).toEqual(patients.content[0].givenName)
expect(tableColumns.at(2).text()).toEqual(patients.content[0].familyName)
expect(tableColumns.at(3).text()).toEqual(patients.content[0].sex)
expect(tableColumns.at(4).text()).toEqual(
format(new Date(patients[0].dateOfBirth), 'yyyy-MM-dd'),
format(new Date(patients.content[0].dateOfBirth), 'yyyy-MM-dd'),
)
})

Expand Down Expand Up @@ -130,7 +145,11 @@ describe('Patients', () => {
wrapper.update()

expect(searchPatientsSpy).toHaveBeenCalledTimes(1)
expect(searchPatientsSpy).toHaveBeenLastCalledWith(expectedSearchText)
expect(searchPatientsSpy).toHaveBeenLastCalledWith(
expectedSearchText,
Unsorted,
UnpagedRequest,
)
})
})
})
71 changes: 55 additions & 16 deletions src/__tests__/patients/patients-slice.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import '../../__mocks__/matchMediaMock'
import { AnyAction } from 'redux'
import { mocked } from 'ts-jest/utils'
import { UnpagedRequest } from 'clients/db/PageRequest'
import patients, {
fetchPatientsStart,
fetchPatientsSuccess,
searchPatients,
} from '../../patients/patients-slice'
import Patient from '../../model/Patient'
import PatientRepository from '../../clients/db/PatientRepository'

describe('patients slice', () => {
Expand All @@ -18,14 +18,34 @@ describe('patients slice', () => {
it('should create the proper initial state with empty patients array', () => {
const patientsStore = patients(undefined, {} as AnyAction)
expect(patientsStore.isLoading).toBeFalsy()
expect(patientsStore.patients).toHaveLength(0)
expect(patientsStore.patients.content).toHaveLength(0)
})

it('should handle the FETCH_PATIENTS_SUCCESS action', () => {
const expectedPatients = [{ id: '1234' }]
const expectedPatients = {
content: [
{
id: '123',
fullName: 'test test',
isApproximateDateOfBirth: false,
givenName: 'test',
familyName: 'test',
code: 'P12345',
sex: 'male',
dateOfBirth: new Date().toISOString(),
phoneNumber: '99999999',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
rev: '',
},
],
hasNext: false,
hasPrevious: false,
pageRequest: UnpagedRequest,
}
const patientsStore = patients(undefined, {
type: fetchPatientsSuccess.type,
payload: [{ id: '1234' }],
payload: expectedPatients,
})

expect(patientsStore.isLoading).toBeFalsy()
Expand All @@ -43,39 +63,58 @@ describe('patients slice', () => {
expect(dispatch).toHaveBeenCalledWith({ type: fetchPatientsStart.type })
})

it('should call the PatientRepository search method with the correct search criteria', async () => {
it('should call the PatientRepository searchPaged method with the correct search criteria', async () => {
const dispatch = jest.fn()
const getState = jest.fn()
jest.spyOn(PatientRepository, 'search')
jest.spyOn(PatientRepository, 'searchPaged')

const expectedSearchString = 'search string'
await searchPatients(expectedSearchString)(dispatch, getState, null)

expect(PatientRepository.search).toHaveBeenCalledWith(expectedSearchString)
expect(PatientRepository.searchPaged).toHaveBeenCalledWith(
expectedSearchString,
UnpagedRequest,
)
})

it('should call the PatientRepository findAll method if there is no string text', async () => {
it('should call the PatientRepository findAllPaged method if there is no string text', async () => {
const dispatch = jest.fn()
const getState = jest.fn()
jest.spyOn(PatientRepository, 'findAll')
jest.spyOn(PatientRepository, 'findAllPaged')

await searchPatients('')(dispatch, getState, null)

expect(PatientRepository.findAll).toHaveBeenCalledTimes(1)
expect(PatientRepository.findAllPaged).toHaveBeenCalledTimes(1)
})

it('should dispatch the FETCH_PATIENTS_SUCCESS action', async () => {
const dispatch = jest.fn()
const getState = jest.fn()

const expectedPatients = [
{
id: '1234',
},
] as Patient[]
const expectedPatients = {
content: [
{
id: '123',
fullName: 'test test',
isApproximateDateOfBirth: false,
givenName: 'test',
familyName: 'test',
code: 'P12345',
sex: 'male',
dateOfBirth: new Date().toISOString(),
phoneNumber: '99999999',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
rev: '',
},
],
hasNext: false,
hasPrevious: false,
pageRequest: UnpagedRequest,
}

const mockedPatientRepository = mocked(PatientRepository, true)
mockedPatientRepository.search.mockResolvedValue(expectedPatients)
mockedPatientRepository.searchPaged.mockResolvedValue(expectedPatients)

await searchPatients('search string')(dispatch, getState, null)

Expand Down
9 changes: 9 additions & 0 deletions src/clients/Page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import AbstractDBModel from '../model/AbstractDBModel'
import PageRequest from './db/PageRequest'

export default interface Page<T extends AbstractDBModel> {
content: T[]
hasNext?: boolean
hasPrevious?: boolean
pageRequest?: PageRequest
}
5 changes: 5 additions & 0 deletions src/clients/db/PageRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default interface PageRequest {
limit: number | undefined
skip: number
}
export const UnpagedRequest: PageRequest = { limit: undefined, skip: 0 }
44 changes: 44 additions & 0 deletions src/clients/db/PatientRepository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import shortid from 'shortid'
import Page from 'clients/Page'
import Patient from '../../model/Patient'
import Repository from './Repository'
import { patients } from '../../config/pouchdb'
import PageRequest, { UnpagedRequest } from './PageRequest'

const formatPatientCode = (prefix: string, sequenceNumber: string) => `${prefix}${sequenceNumber}`

Expand All @@ -10,6 +12,9 @@ const getPatientCode = (): string => formatPatientCode('P-', shortid.generate())
export class PatientRepository extends Repository<Patient> {
constructor() {
super(patients)
patients.createIndex({
index: { fields: ['code', 'fullName'] },
})
}

async search(text: string): Promise<Patient[]> {
Expand All @@ -29,6 +34,45 @@ export class PatientRepository extends Repository<Patient> {
})
}

async searchPaged(
text: string,
pageRequest: PageRequest = UnpagedRequest,
): Promise<Page<Patient>> {
return super
.search({
selector: {
$or: [
{
fullName: {
$regex: RegExp(text, 'i'),
},
},
{
code: text,
},
],
},
skip: pageRequest.skip,
limit: pageRequest.limit,
})
.then(
(searchedData) =>
new Promise<Page<Patient>>((resolve) => {
const pagedResult: Page<Patient> = {
content: searchedData,
pageRequest,
hasNext: pageRequest.limit !== undefined && searchedData.length === pageRequest.limit,
hasPrevious: pageRequest.skip > 0,
}
resolve(pagedResult)
}),
)
.catch((err) => {
console.log(err)
return err
})
}

async save(entity: Patient): Promise<Patient> {
const patientCode = getPatientCode()
entity.code = patientCode
Expand Down
31 changes: 31 additions & 0 deletions src/clients/db/Repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint "@typescript-eslint/camelcase": "off" */
import { v4 as uuidv4 } from 'uuid'
import Page from 'clients/Page'
import AbstractDBModel from '../../model/AbstractDBModel'
import { Unsorted } from './SortRequest'
import PageRequest, { UnpagedRequest } from './PageRequest'

function mapDocument(document: any): any {
const { _id, _rev, ...values } = document
Expand Down Expand Up @@ -41,6 +43,35 @@ export default class Repository<T extends AbstractDBModel> {
return result.docs.map(mapDocument)
}

async findAllPaged(sort = Unsorted, pageRequest: PageRequest = UnpagedRequest): Promise<Page<T>> {
const selector: any = {
_id: { $gt: null },
}

sort.sorts.forEach((s) => {
selector[s.field] = { $gt: null }
})

const result = await this.db.find({
selector,
sort: sort.sorts.length > 0 ? sort.sorts.map((s) => ({ [s.field]: s.direction })) : undefined,
limit: pageRequest.limit,
skip: pageRequest.skip,
})
const mappedResult = result.docs.map(mapDocument)

const pagedResult: Page<T> = {
content: mappedResult,
hasNext: pageRequest.limit !== undefined && mappedResult.length === pageRequest.limit,
hasPrevious: pageRequest.skip > 0,
pageRequest: {
skip: pageRequest.skip,
limit: pageRequest.limit,
},
}
return pagedResult
}

async search(criteria: any): Promise<T[]> {
const response = await this.db.find(criteria)
return response.docs.map(mapDocument)
Expand Down
42 changes: 42 additions & 0 deletions src/components/PageComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import { Button } from '@hospitalrun/components'
import { useTranslation } from 'react-i18next'

const PageComponent = ({
hasNext,
hasPrevious,
pageNumber,
setPreviousPageRequest,
setNextPageRequest,
}: any) => {
const { t } = useTranslation()

return (
<div style={{ textAlign: 'center' }}>
<Button
key="actions.previous"
outlined
disabled={!hasPrevious}
style={{ float: 'left' }}
color="success"
onClick={setPreviousPageRequest}
>
{t('actions.previous')}
</Button>
<div style={{ display: 'inline-block' }}>
{t('actions.page')} {pageNumber}
</div>
<Button
key="actions.next"
outlined
style={{ float: 'right' }}
disabled={!hasNext}
color="success"
onClick={setNextPageRequest}
>
{t('actions.next')}
</Button>
</div>
)
}
export default PageComponent
3 changes: 3 additions & 0 deletions src/locales/enUs/translations/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ export default {
list: 'List',
search: 'Search',
confirmDelete: 'Delete Confirmation',
next: 'Next',
previous: 'Previous',
page: 'Page',
},
}
Loading

0 comments on commit dff2b3e

Please sign in to comment.