-
+ return (
+
+
-
+
-
+
-
"Sex"}>
- {sexValues.map((i) => (
- setSex(i.value)}
- >
- {i.label}
-
- ))}
-
+
'Sex'}>
+ {sexValues.map((i) => (
+ setSex(i.value)}
+ >
+ {i.label}
+
+ ))}
+
-
+
-
- Birth Date
-
+
+ Birth Date
+
-
- Age{" "}
-
+
+ Age{' '}
+
-
- Phone
-
+
+ Phone
+
-
+
-
"First Visit?"}>
- {radioYesNo.map((i) => (
- setFirstVisit(i.value)}
- >
- {i.label}
-
- ))}
-
+
'First Visit?'}>
+ {radioYesNo.map((i) => (
+ setFirstVisit(i.value)}
+ >
+ {i.label}
+
+ ))}
+
- {/* Only display the 'female-only' div if the patient is female */}
- {sex ? (
-
-
-
"Pregnant?"}>
- {radioYesNo.map((i) => (
- setPregnant(i.value)}
- >
- {i.label}
-
- ))}
-
+ {/* Only display the 'female-only' div if the patient is female */}
+ {sex ? (
+
+
+ 'Pregnant?'}>
+ {radioYesNo.map((i) => (
+ setPregnant(i.value)}
+ >
+ {i.label}
+
+ ))}
+
-
- G
-
+
+ G
+
-
- P
-
+
+ P
+
-
- A
-
+
+ A
+
-
- LMP
-
+
+ LMP
+
- "Wants Planning?"}>
- {radioYesNo.map((i) => (
- setPlanning(i.value)}
- >
- {i.label}
-
- ))}
-
+ 'Wants Planning?'}>
+ {radioYesNo.map((i) => (
+ setPlanning(i.value)}
+ >
+ {i.label}
+
+ ))}
+
+
+ ) : null}
+
+
+
+
+ Weight (kg)
+
+
+
+ Height (cm)
+
+
+
+ BP
+
+
+
+ Temp (F)
+
+
+
+ Pulse
+
+
+
+ Z-Score
+
+
+
'Albendazole?'}>
+ {radioYesNo.map((i) => (
+ setAlbendazole(i.value)}
+ >
+ {i.label}
+
+ ))}
+
+
+
'Allergies'}>
+
+
+
+
'Medicines'}>
+
+
+
+
'Chief Complaint'}>
+
+
+
+
+
+
'History'}>
+
+
+
+
'Exam'}>
+
+
+
+
+
+
+
+
+
+
- ) : null}
-
-
-
-
- Weight (kg)
-
-
-
- Height (cm)
-
-
-
- BP
-
-
-
- Temp (F)
-
-
-
- Pulse
-
-
-
- Z-Score
-
-
-
"Albendazole?"}>
- {radioYesNo.map((i) => (
- setAlbendazole(i.value)}
- >
- {i.label}
-
- ))}
-
-
-
"Allergies"}>
-
-
-
-
"Medicines"}>
-
-
-
-
"Chief Complaint"}>
-
-
-
-
-
-
"History"}>
-
-
-
-
"Exam"}>
-
-
-
-
-
-
-
-
-
-
-
- );
+ )
}
diff --git a/pwa/src/components/PatientSearch.test.tsx b/pwa/src/components/PatientSearch.test.tsx
new file mode 100644
index 0000000..ebb5d75
--- /dev/null
+++ b/pwa/src/components/PatientSearch.test.tsx
@@ -0,0 +1,100 @@
+import React from 'react'
+import { render, fireEvent } from '@testing-library/react'
+import PatientSearch from './PatientSearch'
+
+// TODO: mock out patient data
+
+let searchComponent: any
+let searchBar: HTMLInputElement
+
+beforeEach(() => {
+ searchComponent = render(
)
+ searchBar = searchComponent.getByTestId('search-bar') as HTMLInputElement
+})
+
+afterEach(() => {
+ searchComponent = null
+ searchBar = null
+})
+
+describe('search component initalization', () => {
+ test('it should have a search bar', () => {
+ expect(searchBar).toBeInTheDocument()
+ })
+
+ test('it should allow the search bar to be focused', () => {
+ expect(searchBar).not.toHaveFocus()
+ searchBar.focus()
+ expect(searchBar).toHaveFocus()
+ })
+})
+
+describe('search bar functionality', () => {
+ test('it should not display a list when no search has been performed', () => {
+ expect(searchComponent.queryByTestId('match-list')).toBeFalsy()
+ })
+
+ test('it should not display a list when search bar is empty', () => {
+ expect(searchComponent.queryByTestId('match-list')).toBeFalsy()
+ fireEvent.change(searchBar, { target: { value: 'alex' } })
+ expect(searchBar.value).toBe('alex')
+ const entryOne = searchComponent.queryByTestId('matched-patient-0')
+ expect(entryOne).toBeInTheDocument()
+ })
+
+ test('it should show one element for an exact match', () => {
+ expect(searchComponent.queryByTestId('match-list')).toBeFalsy()
+ fireEvent.change(searchBar, { target: { value: 'alex powers' } })
+ expect(searchBar.value).toBe('alex powers')
+ const entryOne = searchComponent.queryByTestId('matched-patient-0')
+ expect(entryOne).toBeInTheDocument()
+ expect(searchComponent.queryByTestId('matched-patient-1')).toBeFalsy()
+ fireEvent.change(searchBar, { target: { value: '' } })
+ expect(searchBar.value).toBe('')
+ expect(entryOne).not.toBeInTheDocument()
+ })
+
+ test('it should show add new patient for a no match', () => {
+ fireEvent.change(searchBar, { target: { value: 'not a real name' } })
+ expect(searchBar.value).toBe('not a real name')
+ const entryOne = searchComponent.queryByTestId('no-matched-patient')
+ expect(entryOne).toBeInTheDocument()
+
+ fireEvent.change(searchBar, { target: { value: '' } })
+ expect(searchBar.value).toBe('')
+ expect(entryOne).not.toBeInTheDocument()
+ })
+
+ test('it should have a list and defocus the search bar on enter', () => {
+ expect(true).toBe(true)
+ })
+})
+
+// TODO: make these methods more robust
+// by evaluating the change of state once the navigation and new patient functions are implemented
+describe('list element clicking', () => {
+ test('clicking an existing element should call navigateToPatientPage', () => {
+ // TODO: check something more robust than console log
+ const spy = jest.spyOn(console, 'log')
+ fireEvent.change(searchBar, { target: { value: 'Alex Powers' } })
+ expect(searchBar.value).toBe('Alex Powers')
+ const entryOne = searchComponent.queryByTestId('matched-patient-0')
+ expect(entryOne).toBeInTheDocument()
+
+ fireEvent.click(entryOne)
+ expect(spy).toHaveBeenCalled()
+ })
+
+ test('clicking an existing element should call navigateToPatientPage', () => {
+ // TODO: check something more robust than console log
+ const spy = jest.spyOn(console, 'log')
+
+ fireEvent.change(searchBar, { target: { value: 'not a real name' } })
+ expect(searchBar.value).toBe('not a real name')
+ const entryOne = searchComponent.queryByTestId('no-matched-patient')
+ expect(entryOne).toBeInTheDocument()
+
+ fireEvent.click(entryOne)
+ expect(spy).toHaveBeenCalled()
+ })
+})
diff --git a/pwa/src/components/PatientSearch.tsx b/pwa/src/components/PatientSearch.tsx
new file mode 100644
index 0000000..ae61866
--- /dev/null
+++ b/pwa/src/components/PatientSearch.tsx
@@ -0,0 +1,80 @@
+import React, { ReactElement, useState } from 'react'
+
+import { SearchBar, WhiteSpace, List } from 'antd-mobile'
+
+import '../scss/PatientSearch.scss'
+import Patient from '../state/models/patient.model'
+
+const patientsSeed: Array
= [
+ { pk_patient_id: 1, first_name: 'Alex', last_name: 'Powers' },
+ { pk_patient_id: 2, first_name: 'Meghan', last_name: 'McLaughlin' },
+ { pk_patient_id: 3, first_name: 'Ben', last_name: 'Mitchinson' },
+ { pk_patient_id: 4, first_name: 'Zain', last_name: 'Khan' },
+ { pk_patient_id: 5, first_name: 'Hans', last_name: 'Johnson' },
+]
+
+export default function PatientSearch(): ReactElement {
+ const [matches, setMatches] = useState([])
+ const [patients] = useState(patientsSeed)
+ const [searchValue, setSearchValue] = useState('')
+
+ // TODO: replace this logic
+ // this could potentially use Fuse.js or fuzzy database queries
+ const searchForName = (value: string): void => {
+ setSearchValue(value)
+ if (value) {
+ const listOfMatches = patients.filter((element: Patient) => {
+ return `${element.first_name} ${element.last_name}`
+ .toLowerCase()
+ .includes(value.toLowerCase())
+ })
+ setMatches(listOfMatches)
+ } else {
+ setMatches([])
+ }
+ }
+
+ const addNewPatient = (): void => {
+ console.log('NO PATIENT FOUND, ADDING A NEW ONE')
+ }
+
+ const navigateToPatientPage = (patient: Patient): void => {
+ console.log(`Navigating to patient ${JSON.stringify(patient)}`)
+ }
+
+ return (
+ <>
+ searchForName(value)}
+ />
+
+ {searchValue ? (
+ <>
+
+ {matches.map((matchElement: Patient, index: number) => (
+
+ navigateToPatientPage(matchElement)
+ }
+ >{`${matchElement.first_name} ${matchElement.last_name}`}
+ ))}
+ {matches.length === 0 && (
+ addNewPatient()}
+ >
+ Add New Patient
+
+ )}
+
+ >
+ ) : null}
+ >
+ )
+}
diff --git a/pwa/src/scss/DiagnosisForm.scss b/pwa/src/scss/DiagnosisForm.scss
new file mode 100644
index 0000000..3ee4ed2
--- /dev/null
+++ b/pwa/src/scss/DiagnosisForm.scss
@@ -0,0 +1,5 @@
+#diagnosis-form-wrapper {
+ .list-whitespace {
+ background-color: #f5f5f9;
+ }
+}
diff --git a/pwa/src/scss/PatientSearch.scss b/pwa/src/scss/PatientSearch.scss
new file mode 100644
index 0000000..417a2b9
--- /dev/null
+++ b/pwa/src/scss/PatientSearch.scss
@@ -0,0 +1,3 @@
+.am-search-clear-show {
+ display: none !important;
+}
diff --git a/pwa/src/state/enums.ts b/pwa/src/state/enums.ts
new file mode 100644
index 0000000..f26a865
--- /dev/null
+++ b/pwa/src/state/enums.ts
@@ -0,0 +1,13 @@
+/* istanbul ignore file */
+
+export enum SEX {
+ male = 'male',
+ female = 'female',
+}
+
+export enum YN {
+ YES = 1,
+ NO = 0,
+}
+
+export default {}
diff --git a/pwa/src/state/models/patient.model.ts b/pwa/src/state/models/patient.model.ts
new file mode 100644
index 0000000..32b4801
--- /dev/null
+++ b/pwa/src/state/models/patient.model.ts
@@ -0,0 +1,6 @@
+interface Patient {
+ pk_patient_id?: number
+ first_name?: string
+ last_name?: string
+}
+export default Patient
diff --git a/pwa/src/state/models/patientInfoForm.model.ts b/pwa/src/state/models/patientInfoForm.model.ts
new file mode 100644
index 0000000..e9aefd7
--- /dev/null
+++ b/pwa/src/state/models/patientInfoForm.model.ts
@@ -0,0 +1,48 @@
+import { Action, action } from 'easy-peasy'
+import { SEX, YN } from '../enums'
+
+interface PatientInfoFormFields {
+ firstName?: string
+ lastName?: string
+ nickname?: string
+ sex?: SEX
+ birthDate?: Date
+ age?: number
+ phone?: string
+ town?: string
+ firstVisit?: YN
+ pregnant?: YN
+ G?: number
+ P?: number
+ A?: number
+ LMP?: Date
+ wantsPlanning?: YN
+ weight?: number
+ height?: number
+ bloodPressure?: string
+ temperature?: number
+ pulse?: number
+ zScore?: number
+ albendazole?: YN
+ allergies?: string
+ medicines?: string
+ chiefComplaint?: string[]
+ history?: string
+ exam?: string
+}
+
+export interface PatientInfoFormModel {
+ fields: PatientInfoFormFields
+ updateFields: Action
+}
+
+/* istanbul ignore next */
+// Form submission requests validated in integration test
+const patientInfoFormModel: PatientInfoFormModel = {
+ fields: {},
+ updateFields: action((state, formObject) => {
+ state.fields = formObject
+ }),
+}
+
+export default patientInfoFormModel
diff --git a/pwa/src/state/store.ts b/pwa/src/state/store.ts
new file mode 100644
index 0000000..7142739
--- /dev/null
+++ b/pwa/src/state/store.ts
@@ -0,0 +1,8 @@
+/* istanbul ignore file */
+
+import { createStore, persist } from 'easy-peasy'
+import storeIndex from './storeIndex'
+
+const store = createStore(persist(storeIndex))
+
+export default store
diff --git a/pwa/src/state/storeIndex.ts b/pwa/src/state/storeIndex.ts
new file mode 100644
index 0000000..b77da81
--- /dev/null
+++ b/pwa/src/state/storeIndex.ts
@@ -0,0 +1,17 @@
+/* istanbul ignore file */
+import { createTypedHooks } from 'easy-peasy'
+
+import patientInfoFormModel from './models/patientInfoForm.model'
+
+export const storeModels = {
+ patientInfoFormModel,
+}
+
+export const {
+ useStore,
+ useStoreState,
+ useStoreActions,
+ useStoreDispatch,
+} = createTypedHooks()
+
+export default storeModels