540 Refactor temperature to functional component
This commit is contained in:
@@ -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)
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user