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

Commit

Permalink
feat: update contact info component
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmeyer committed Jun 17, 2020
1 parent 5ad51ac commit 4e17ef3
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 117 deletions.
60 changes: 20 additions & 40 deletions src/__tests__/patients/ContactInfo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import { Router } from 'react-router-dom'
import TextInputWithLabelFormGroup from '../../components/input/TextInputWithLabelFormGroup'
import { ContactInfoPiece } from '../../model/ContactInformation'
import ContactInfo from '../../patients/ContactInfo'
import * as uuid from '../../util/uuid'

describe('Contact Info in its Editable mode', () => {
const data = [
{ value: '123456', type: 'home' },
{ value: '789012', type: undefined },
{ id: '123', value: '123456', type: 'home' },
{ id: '456', value: '789012', type: undefined },
]
const dataForNoAdd = [
{ value: '123456', type: 'home' },
{ value: ' ', type: undefined },
{ id: '987', value: '123456', type: 'home' },
{ id: '654', value: ' ', type: undefined },
]
const errors = ['this is an error', '']
const label = 'this is a label'
Expand Down Expand Up @@ -45,21 +46,6 @@ describe('Contact Info in its Editable mode', () => {
return wrapper
}

it('should show a spinner if no data is present', () => {
const wrapper = setup()
const spinnerWrapper = wrapper.find(Spinner)

expect(spinnerWrapper).toHaveLength(1)
})

it('should call onChange if no data is provided', () => {
setup()

const expectedNewData = [{ value: '' }]
expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith(expectedNewData)
})

it('should render the labels if data is provided', () => {
const wrapper = setup(data)
const headerWrapper = wrapper.find('.header')
Expand Down Expand Up @@ -107,8 +93,8 @@ describe('Contact Info in its Editable mode', () => {
select.simulate('change')

const expectedNewData = [
{ value: '123456', type: 'mobile' },
{ value: '789012', type: undefined },
{ id: '123', value: '123456', type: 'mobile' },
{ id: '456', value: '789012', type: undefined },
]
expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith(expectedNewData)
Expand All @@ -121,8 +107,8 @@ describe('Contact Info in its Editable mode', () => {
input.simulate('change')

const expectedNewData = [
{ value: '777777', type: 'home' },
{ value: '789012', type: undefined },
{ id: '123', value: '777777', type: 'home' },
{ id: '456', value: '789012', type: undefined },
]
expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith(expectedNewData)
Expand All @@ -132,37 +118,38 @@ describe('Contact Info in its Editable mode', () => {
const wrapper = setup(data)
const buttonWrapper = wrapper.find('button')
const onClick = buttonWrapper.prop('onClick') as any
const newId = 'newId'
jest.spyOn(uuid, 'uuid').mockReturnValue(newId)

act(() => {
onClick()
})

const expectedNewData = [...data, { value: '' }]
const expectedNewData = [...data, { id: newId, value: '', type: '' }]

expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith(expectedNewData)
})

it('should call the onChange callback if an add button is clicked with an empty entry', () => {
it('should not call the onChange callback if an add button is clicked with an empty entry', () => {
const wrapper = setup(dataForNoAdd)
const buttonWrapper = wrapper.find('button')
const onClick = buttonWrapper.prop('onClick') as any
const newId = 'newId'
jest.spyOn(uuid, 'uuid').mockReturnValue(newId)

act(() => {
onClick()
})

const expectedNewData = [{ value: '123456', type: 'home' }, { value: '' }]

expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith(expectedNewData)
expect(onChange).toHaveBeenCalledTimes(0)
})
})

describe('Contact Info in its non-Editable mode', () => {
const data = [
{ value: '123456', type: 'home' },
{ value: '789012', type: undefined },
{ id: '123', value: '123456', type: 'home' },
{ id: '456', value: '789012', type: undefined },
]
const label = 'this is a label'
const name = 'this is a name'
Expand All @@ -181,17 +168,11 @@ describe('Contact Info in its non-Editable mode', () => {
/>
</Router>,
)
wrapper.update()

return wrapper
}

it('should render an empty element if no data is present', () => {
const wrapper = setup()
const contactInfoWrapper = wrapper.find(ContactInfo)

expect(contactInfoWrapper.find('div')).toHaveLength(1)
expect(contactInfoWrapper.containsMatchingElement(<div />)).toEqual(true)
})

it('should render the labels if data is provided', () => {
const wrapper = setup(data)
const headerWrapper = wrapper.find('.header')
Expand All @@ -216,7 +197,6 @@ describe('Contact Info in its non-Editable mode', () => {
const inputWrappers = wrapper.find(TextInputWithLabelFormGroup)
for (let i = 0; i < inputWrappers.length; i += 1) {
expect(inputWrappers.at(i).prop('isEditable')).toBeFalsy()
expect(inputWrappers.at(i).prop('onChange')).toBeUndefined()
}
})
})
2 changes: 1 addition & 1 deletion src/model/ContactInformation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type ContactInfoPiece = { value: string; type?: string }
type ContactInfoPiece = { id: string; value: string; type?: string }

interface ContactInformation {
phoneNumbers: ContactInfoPiece[]
Expand Down
130 changes: 56 additions & 74 deletions src/patients/ContactInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Spinner, Row, Column, Icon } from '@hospitalrun/components'
import React, { useEffect, ReactElement } from 'react'
import { Column, Icon, Row } from '@hospitalrun/components'
import { isEmpty } from 'lodash'
import React, { ReactElement, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

import SelectWithLabelFormGroup from '../components/input/SelectWithLableFormGroup'
import TextFieldWithLabelFormGroup from '../components/input/TextFieldWithLabelFormGroup'
import TextInputWithLabelFormGroup from '../components/input/TextInputWithLabelFormGroup'
import { ContactInfoPiece } from '../model/ContactInformation'
import { uuid } from '../util/uuid'
import ContactInfoTypes from './ContactInfoTypes'

interface Props {
Expand All @@ -17,87 +19,74 @@ interface Props {
isEditable?: boolean
onChange?: (newData: ContactInfoPiece[]) => void
}
const initialContacts = { id: uuid(), type: '', value: '' }

const ContactInfo = (props: Props): ReactElement => {
const { component, data, errors, label, name, isEditable, onChange } = props

const [contacts, setContacts] = useState<ContactInfoPiece[]>([])
const { t } = useTranslation()

useEffect(() => {
if (onChange && data.length === 0) {
onChange([...data, { value: '' }])
if (data.length === 0) {
setContacts([initialContacts])
} else {
setContacts([...data])
}
}, [data, onChange])
}, [setContacts, data])

const typeLabel = t('patient.contactInfoType.label')
const typeOptions = Object.values(ContactInfoTypes).map((value) => ({
label: t(`patient.contactInfoType.options.${value}`),
value: `${value}`,
}))

const header = (
<Row className="header mb-2">
<Column xs={12} sm={4}>
<span className="">{typeLabel}</span>
<span className="d-sm-none"> &amp; {t(label)}</span>
</Column>
<Column className="d-none d-sm-block" sm={8}>
{t(label)}
</Column>
</Row>
)

const componentList = {
TextInputWithLabelFormGroup,
TextFieldWithLabelFormGroup,
}
const Component = componentList[component]

// todo: acts strange when deleting empty rows above non-empty rows.
// suspect TextInputWithLabelFormGroup missing value
const entries = data.map((entry, i) => {
const onTypeChange = (event: React.ChangeEvent<HTMLSelectElement>, index: number) => {
if (onChange) {
const newType = event.currentTarget.value
const currentContact = { ...contacts[index], type: newType }
const newContacts = [...contacts]
newContacts.splice(index, 1, currentContact)
onChange(newContacts)
}
}

const onValueChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number,
) => {
if (onChange) {
const newValue = event.currentTarget.value
const currentContact = { ...contacts[index], value: newValue }
const newContacts = [...contacts]
newContacts.splice(index, 1, currentContact)
onChange(newContacts)
}
}

const entries = contacts.map((entry, i) => {
const error = errors ? errors[i] : undefined
return (
// todo: want a better key, and possibly name
// eslint-disable-next-line react/no-array-index-key
<Row key={i}>
<Row key={entry.id}>
<Column sm={4}>
<SelectWithLabelFormGroup
name={`${name}Type${i}`}
value={entry.type}
isEditable={isEditable}
options={typeOptions}
onChange={
onChange
? (event) => {
const newData = data.map((ref) =>
ref.value === entry.value
? { value: entry.value, type: event.currentTarget.value }
: { value: ref.value, type: ref.type },
)
onChange(newData)
}
: undefined
}
onChange={(event) => onTypeChange(event, i)}
/>
</Column>
<Column sm={8}>
<Component
name={`${name}${i}`}
value={entry.value}
isEditable={isEditable}
onChange={
onChange
? (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newData = data.map((ref) =>
ref.value === entry.value
? { value: event.currentTarget.value, type: entry.type }
: { value: ref.value, type: ref.type },
)
onChange(newData)
}
: undefined
}
onChange={(event: any) => onValueChange(event, i)}
feedback={error && t(error)}
isInvalid={!!error}
/>
Expand All @@ -107,37 +96,30 @@ const ContactInfo = (props: Props): ReactElement => {
})

const onAddClick = () => {
if (!onChange) {
return
if (onChange && !contacts.some((c) => isEmpty(c.value.trim()))) {
onChange([...contacts, { id: uuid(), type: '', value: '' }])
}

// 1. pick up only non-empty string
const newData = data.filter(({ value }) => value.trim() !== '')

// 2. add a new entry
newData.push({ value: '' })

// 3. send updates
onChange(newData)
}

const addButton = (
<div className="text-right">
<button type="button" className="btn btn-link" onClick={onAddClick}>
<Icon icon="add" /> {t('actions.add')}
</button>
</div>
)

if (isEditable && data.length === 0) {
return <Spinner color="blue" loading size={20} type="SyncLoader" />
}

return (
<div>
{data.length > 0 ? header : null}
<Row className="header mb-2">
<Column xs={12} sm={4}>
<span className="">{t('patient.contactInfoType.label')}</span>
<span className="d-sm-none"> &amp; {t(label)}</span>
</Column>
<Column className="d-none d-sm-block" sm={8}>
{t(label)}
</Column>
</Row>
{entries}
{isEditable ? addButton : null}
{isEditable && (
<div className="text-right">
<button type="button" className="btn btn-link" onClick={onAddClick}>
<Icon icon="add" /> {t('actions.add')}
</button>
</div>
)}
</div>
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/patients/util/set-patient-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const cleanupPatient = (patient: Patient) => {
.map((entry) => {
const newValue = entry.value.trim()
if ('type' in entry) {
return { value: newValue, type: entry.type }
return { id: entry.id, value: newValue, type: entry.type }
}
return { value: newValue }
return { id: entry.id, value: newValue }
})

if (nonEmpty.length > 0) {
Expand Down

0 comments on commit 4e17ef3

Please sign in to comment.