diff --git a/src/__tests__/patients/ContactInfo.test.tsx b/src/__tests__/patients/ContactInfo.test.tsx index cc6001edd7..9e28f73c02 100644 --- a/src/__tests__/patients/ContactInfo.test.tsx +++ b/src/__tests__/patients/ContactInfo.test.tsx @@ -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' @@ -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') @@ -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) @@ -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) @@ -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' @@ -181,17 +168,11 @@ describe('Contact Info in its non-Editable mode', () => { /> , ) + 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(
)).toEqual(true) - }) - it('should render the labels if data is provided', () => { const wrapper = setup(data) const headerWrapper = wrapper.find('.header') @@ -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() } }) }) diff --git a/src/model/ContactInformation.ts b/src/model/ContactInformation.ts index 7ae4965a9c..65bae63b5d 100644 --- a/src/model/ContactInformation.ts +++ b/src/model/ContactInformation.ts @@ -1,4 +1,4 @@ -type ContactInfoPiece = { value: string; type?: string } +type ContactInfoPiece = { id: string; value: string; type?: string } interface ContactInformation { phoneNumbers: ContactInfoPiece[] diff --git a/src/patients/ContactInfo.tsx b/src/patients/ContactInfo.tsx index bf3693b622..ac18e365bd 100644 --- a/src/patients/ContactInfo.tsx +++ b/src/patients/ContactInfo.tsx @@ -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 { @@ -17,68 +19,66 @@ 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([]) 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 = ( - - - {typeLabel} - & {t(label)} - - - {t(label)} - - - ) - 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, 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, + 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 - + { - 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)} /> @@ -86,18 +86,7 @@ const ContactInfo = (props: Props): ReactElement => { name={`${name}${i}`} value={entry.value} isEditable={isEditable} - onChange={ - onChange - ? (event: React.ChangeEvent) => { - 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} /> @@ -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 = ( -
- -
- ) - - if (isEditable && data.length === 0) { - return } return (
- {data.length > 0 ? header : null} + + + {t('patient.contactInfoType.label')} + & {t(label)} + + + {t(label)} + + {entries} - {isEditable ? addButton : null} + {isEditable && ( +
+ +
+ )}
) } diff --git a/src/patients/util/set-patient-helper.ts b/src/patients/util/set-patient-helper.ts index 307c137249..641ef8c013 100644 --- a/src/patients/util/set-patient-helper.ts +++ b/src/patients/util/set-patient-helper.ts @@ -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) {