540 Refactor temperature to functional component

This commit is contained in:
Lisa Hillebrand
2022-04-15 18:04:20 +02:00
parent fe9d9b4fdc
commit 585017eadd
3 changed files with 145 additions and 184 deletions
+68 -71
View File
@@ -23,12 +23,11 @@ import info from '../../i18n/en/symptom-info'
import { Colors, Containers, Sizes, Spacing } from '../../styles' import { Colors, Containers, Sizes, Spacing } from '../../styles'
class SymptomEditView extends Component { class SymptomEditView extends Component {
static propTypes = { static propTypes = {
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
symptom: PropTypes.string.isRequired, symptom: PropTypes.string.isRequired,
symptomData: PropTypes.object symptomData: PropTypes.object,
} }
constructor(props) { constructor(props) {
@@ -49,7 +48,7 @@ class SymptomEditView extends Component {
shouldShowInfo: false, shouldShowInfo: false,
shouldShowNote, shouldShowNote,
shouldBoxGroup, shouldBoxGroup,
shouldTabGroup shouldTabGroup,
} }
} }
@@ -97,8 +96,8 @@ class SymptomEditView extends Component {
onSaveTemperature = (value, field) => { onSaveTemperature = (value, field) => {
const data = this.getParsedData() const data = this.getParsedData()
const dataToSave = field === 'value' const dataToSave =
? { [field]: Number(value) } : { [field]: value } field === 'value' ? { [field]: Number(value) } : { [field]: value }
Object.assign(data, { ...dataToSave }) Object.assign(data, { ...dataToSave })
this.setState({ data }) this.setState({ data })
@@ -106,10 +105,10 @@ class SymptomEditView extends Component {
onSelectBox = (key) => { onSelectBox = (key) => {
const data = this.getParsedData() const data = this.getParsedData()
if (key === "other") { if (key === 'other') {
Object.assign(data, { Object.assign(data, {
note: null, note: null,
[key]: !this.state.data[key] [key]: !this.state.data[key],
}) })
} else { } else {
Object.assign(data, { [key]: !this.state.data[key] }) Object.assign(data, { [key]: !this.state.data[key] })
@@ -118,7 +117,7 @@ class SymptomEditView extends Component {
this.setState({ data }) this.setState({ data })
} }
onSelectBoxNote= (value) => { onSelectBoxNote = (value) => {
const data = this.getParsedData() const data = this.getParsedData()
Object.assign(data, { note: value !== '' ? value : null }) Object.assign(data, { note: value !== '' ? value : null })
@@ -147,12 +146,13 @@ class SymptomEditView extends Component {
render() { render() {
const { symptom } = this.props const { symptom } = this.props
const { data, const {
data,
shouldShowExclude, shouldShowExclude,
shouldShowInfo, shouldShowInfo,
shouldShowNote, shouldShowNote,
shouldBoxGroup, shouldBoxGroup,
shouldTabGroup shouldTabGroup,
} = this.state } = this.state
const iconName = shouldShowInfo ? 'chevron-up' : 'chevron-down' const iconName = shouldShowInfo ? 'chevron-up' : 'chevron-down'
const noteText = symptom === 'note' ? data.value : data.note const noteText = symptom === 'note' ? data.value : data.note
@@ -166,73 +166,73 @@ class SymptomEditView extends Component {
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
<CloseIcon onClose={this.closeView} /> <CloseIcon onClose={this.closeView} />
</View> </View>
{symptom === 'temperature' && {symptom === 'temperature' && (
<Temperature <Temperature
data={data} data={data}
save={(value, field) => this.onSaveTemperature(value, field)} save={(value, field) => this.onSaveTemperature(value, field)}
/> />
} )}
{shouldTabGroup && symtomPage[symptom].selectTabGroups.map(group => { {shouldTabGroup &&
return ( symtomPage[symptom].selectTabGroups.map((group) => {
<Segment key={group.key} style={styles.segmentBorder}> return (
<AppText style={styles.title}>{group.title}</AppText> <Segment key={group.key} style={styles.segmentBorder}>
<SelectTabGroup <AppText style={styles.title}>{group.title}</AppText>
activeButton={data[group.key]} <SelectTabGroup
buttons={group.options} activeButton={data[group.key]}
onSelect={value => this.onSelectTab(group, value)} buttons={group.options}
/> onSelect={(value) => this.onSelectTab(group, value)}
</Segment>
)
})
}
{shouldBoxGroup && symtomPage[symptom].selectBoxGroups.map(group => {
const isOtherSelected =
data['other'] !== null
&& data['other'] !== false
&& Object.keys(group.options).includes('other')
return (
<Segment key={group.key} style={styles.segmentBorder} >
<AppText style={styles.title}>{group.title}</AppText>
<SelectBoxGroup
labels={group.options}
onSelect={value => this.onSelectBox(value)}
optionsState={data}
/>
{isOtherSelected &&
<AppTextInput
multiline={true}
placeholder={sharedLabels.enter}
value={data.note}
onChangeText={value => this.onSelectBoxNote(value)}
/> />
} </Segment>
</Segment> )
) })}
}) {shouldBoxGroup &&
} symtomPage[symptom].selectBoxGroups.map((group) => {
{shouldShowExclude && const isOtherSelected =
<Segment style={styles.segmentBorder} > data['other'] !== null &&
data['other'] !== false &&
Object.keys(group.options).includes('other')
return (
<Segment key={group.key} style={styles.segmentBorder}>
<AppText style={styles.title}>{group.title}</AppText>
<SelectBoxGroup
labels={group.options}
onSelect={(value) => this.onSelectBox(value)}
optionsState={data}
/>
{isOtherSelected && (
<AppTextInput
multiline={true}
placeholder={sharedLabels.enter}
value={data.note}
onChangeText={(value) => this.onSelectBoxNote(value)}
/>
)}
</Segment>
)
})}
{shouldShowExclude && (
<Segment style={styles.segmentBorder}>
<AppSwitch <AppSwitch
onToggle={this.onExcludeToggle} onToggle={this.onExcludeToggle}
text={symtomPage[symptom].excludeText} text={symtomPage[symptom].excludeText}
value={data.exclude} value={data.exclude}
/> />
</Segment> </Segment>
} )}
{shouldShowNote && {shouldShowNote && (
<Segment style={styles.segmentBorder} > <Segment style={styles.segmentBorder}>
<AppText>{symtomPage[symptom].note}</AppText> <AppText>{symtomPage[symptom].note}</AppText>
<AppTextInput <AppTextInput
multiline={true} multiline={true}
numberOfLines={3} numberOfLines={3}
onChangeText={this.onEditNote} onChangeText={this.onEditNote}
placeholder={sharedLabels.enter} placeholder={sharedLabels.enter}
testID='noteInput' testID="noteInput"
value={noteText !== null ? noteText : ''} value={noteText !== null ? noteText : ''}
/> />
</Segment> </Segment>
} )}
<View style={styles.buttonsContainer}> <View style={styles.buttonsContainer}>
<Button iconName={iconName} isSmall onPress={this.onPressLearnMore}> <Button iconName={iconName} isSmall onPress={this.onPressLearnMore}>
{sharedLabels.learnMore} {sharedLabels.learnMore}
@@ -244,11 +244,11 @@ class SymptomEditView extends Component {
{sharedLabels.save} {sharedLabels.save}
</Button> </Button>
</View> </View>
{shouldShowInfo && {shouldShowInfo && (
<Segment last style={styles.segmentBorder} > <Segment last style={styles.segmentBorder}>
<AppText>{info[symptom].text}</AppText> <AppText>{info[symptom].text}</AppText>
</Segment> </Segment>
} )}
</ScrollView> </ScrollView>
</AppModal> </AppModal>
) )
@@ -257,7 +257,7 @@ class SymptomEditView extends Component {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
buttonsContainer: { buttonsContainer: {
...Containers.rowContainer ...Containers.rowContainer,
}, },
headerContainer: { headerContainer: {
flexDirection: 'row', flexDirection: 'row',
@@ -275,23 +275,20 @@ const styles = StyleSheet.create({
marginVertical: Sizes.huge * 2, marginVertical: Sizes.huge * 2,
position: 'absolute', position: 'absolute',
minHeight: '40%', minHeight: '40%',
maxHeight: Dimensions.get('window').height * 0.7 maxHeight: Dimensions.get('window').height * 0.7,
}, },
segmentBorder: { segmentBorder: {
borderBottomColor: Colors.greyLight borderBottomColor: Colors.greyLight,
}, },
title: { title: {
fontSize: Sizes.subtitle fontSize: Sizes.subtitle,
} },
}) })
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return({ return {
date: getDate(state), date: getDate(state),
}) }
} }
export default connect( export default connect(mapStateToProps, null)(SymptomEditView)
mapStateToProps,
null,
)(SymptomEditView)
+71 -102
View File
@@ -1,4 +1,4 @@
import React, { Component } from 'react' import React, { useEffect, useState } from 'react'
import { Platform, StyleSheet, View } from 'react-native' import { Platform, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Keyboard } from 'react-native' import { Keyboard } from 'react-native'
@@ -12,129 +12,92 @@ import Segment from '../common/segment'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { getDate } from '../../slices/date' import { getDate } from '../../slices/date'
import { import {
isTemperatureOutOfRange, getTemperatureOutOfRangeMessage,
getPreviousTemperature, getPreviousTemperature,
formatTemperature,
} from '../helpers/cycle-day' } from '../helpers/cycle-day'
import { temperature as labels } from '../../i18n/en/cycle-day' import { temperature as labels } from '../../i18n/en/cycle-day'
import { Colors, Containers, Sizes, Spacing } from '../../styles' import { Colors, Containers, Sizes, Spacing } from '../../styles'
const formatTemperature = (value) => const Temperature = ({ data, date, save }) => {
value === null ? value : Number.parseFloat(value).toFixed(2) const [isTimePickerVisible, setIsTimePickerVisible] = useState(false)
const [temperature, setTemperature] = useState(
formatTemperature(data.value) || getPreviousTemperature(date)
)
class Temperature extends Component { // update state in parent component once to ensure
static propTypes = { // that pre-filled values are saved on button click
data: PropTypes.object, useEffect(() => {
date: PropTypes.string.isRequired, if (temperature) {
save: PropTypes.func, save(temperature, 'value')
}
constructor(props) {
super(props)
const { data, date } = this.props
const { value } = data
const { shouldShowSuggestion, suggestedTemperature } =
getPreviousTemperature(date)
this.state = {
isTimePickerVisible: false,
shouldShowSuggestion,
suggestedTemperature: formatTemperature(suggestedTemperature),
value: formatTemperature(value),
} }
}, [])
function onChangeTemperature(value) {
const formattedValue = value.replace(',', '.').trim()
if (!Number(formattedValue) && value !== '') return false
setTemperature(formattedValue)
} }
onCancelTimePicker = () => { function onShowTimePicker() {
this.setState({ isTimePickerVisible: false })
}
onChangeTemperature = (value) => {
if (!Number(value)) return false
this.setState({
value: value.trim(),
shouldShowSuggestion: false,
})
}
onShowTimePicker = () => {
Keyboard.dismiss() Keyboard.dismiss()
this.setState({ isTimePickerVisible: true }) setIsTimePickerVisible(true)
} }
setTemperature = () => { function setTime(jsDate) {
const { value } = this.state
this.props.save(value, 'value')
}
setTime = (jsDate) => {
const time = moment(jsDate).format('HH:mm') const time = moment(jsDate).format('HH:mm')
const isTimePickerVisible = false
this.props.save(time, 'time') save(time, 'time')
this.setState({ isTimePickerVisible }) setIsTimePickerVisible(false)
} }
render() { const { time } = data
const { shouldShowSuggestion, suggestedTemperature, value } = this.state
const { time } = this.props.data
const inputStyle = const inputStyle = { color: Colors.greyDark }
shouldShowSuggestion && value === null const outOfRangeWarning = getTemperatureOutOfRangeMessage(temperature)
? { color: Colors.grey }
: { color: Colors.greyDark }
const outOfRangeWarning = isTemperatureOutOfRange(value)
let temperatureToShow = null
if (value) { return (
temperatureToShow = value <React.Fragment>
} else if (shouldShowSuggestion) { <Segment>
temperatureToShow = suggestedTemperature <AppText style={styles.title}>{labels.temperature.explainer}</AppText>
} <View style={styles.container}>
return (
<React.Fragment>
<Segment>
<AppText style={styles.title}>{labels.temperature.explainer}</AppText>
<View style={styles.container}>
<AppTextInput
value={temperatureToShow === null ? '' : temperatureToShow}
onChangeText={this.onChangeTemperature}
onEndEditing={this.setTemperature}
keyboardType="numeric"
maxLength={5}
style={inputStyle}
testID="temperatureInput"
underlineColorAndroid="transparent"
/>
<AppText>°C</AppText>
</View>
{outOfRangeWarning !== null && (
<View style={styles.hintContainer}>
<AppText style={styles.hint}>{outOfRangeWarning}</AppText>
</View>
)}
</Segment>
<Segment>
<AppText style={styles.title}>{labels.time}</AppText>
<AppTextInput <AppTextInput
onFocus={this.onShowTimePicker} value={temperature}
testID="timeInput" onChangeText={onChangeTemperature}
value={time} onEndEditing={() => save(temperature, 'value')}
keyboardType="numeric"
maxLength={5}
style={inputStyle}
testID="temperatureInput"
underlineColorAndroid="transparent"
/> />
<DateTimePicker <AppText>°C</AppText>
isVisible={this.state.isTimePickerVisible} </View>
mode="time" {!!outOfRangeWarning && (
onConfirm={this.setTime} <View style={styles.hintContainer}>
onCancel={this.onCancelTimePicker} <AppText style={styles.hint}>{outOfRangeWarning}</AppText>
display={Platform.OS === 'ios' ? 'spinner' : 'default'} </View>
/> )}
</Segment> </Segment>
</React.Fragment> <Segment>
) <AppText style={styles.title}>{labels.time}</AppText>
} <AppTextInput
onFocus={onShowTimePicker}
testID="timeInput"
value={time}
/>
<DateTimePicker
isVisible={isTimePickerVisible}
mode="time"
onConfirm={setTime}
onCancel={() => setIsTimePickerVisible(false)}
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
/>
</Segment>
</React.Fragment>
)
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -153,6 +116,12 @@ const styles = StyleSheet.create({
}, },
}) })
Temperature.propTypes = {
data: PropTypes.object.isRequired,
date: PropTypes.string.isRequired,
save: PropTypes.func.isRequired,
}
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
date: getDate(state), date: getDate(state),
+6 -11
View File
@@ -35,22 +35,17 @@ export const getPreviousTemperature = (date) => {
return formatTemperature(previousTemperature) return formatTemperature(previousTemperature)
} }
export const isTemperatureOutOfRange = (temperature) => { export const getTemperatureOutOfRangeMessage = (temperature) => {
if (!temperature) return null if (!temperature) return null
const value = Number(temperature) const value = Number(temperature)
const range = { min: TEMP_MIN, max: TEMP_MAX }
const scale = scaleObservable.value const scale = scaleObservable.value
let warningMsg = null return value < TEMP_MIN || value > TEMP_MAX
? labels.temperature.outOfAbsoluteRangeWarning
if (value < range.min || value > range.max) { : value < scale.min || value > scale.max
warningMsg = labels.temperature.outOfAbsoluteRangeWarning ? labels.temperature.outOfRangeWarning
} else if (value < scale.min || value > scale.max) { : ''
warningMsg = labels.temperature.outOfRangeWarning
}
return warningMsg
} }
export const blank = { export const blank = {