1
0
Fork 0
mirror of https://github.com/betagouv/mon-entreprise synced 2025-02-09 04:05:01 +00:00
mon-entreprise/source/components/TargetSelection.js
Maxime Quandalle 0663c97204 Utilisation du hook useTranslation de react-i18next
Suppression de notre composant withLanguage qui rajoutait une abstraction
inutile.

Note: de nombreux appels à withTranslation et withLanguage était inutile
car le composant augmenté n'utilisait pas les paramètres fournis (language, t, i18n).
L'utilisation des hooks nous permet de mieux gérer le code mort, car il s'agit
de simples variables dont le non-usage est détecté par l'analyse statique.
2019-09-11 11:17:23 +02:00

382 lines
8.9 KiB
JavaScript

import classNames from 'classnames'
import { T } from 'Components'
import InputSuggestions from 'Components/conversation/InputSuggestions'
import PercentageField from 'Components/PercentageField'
import PeriodSwitch from 'Components/PeriodSwitch'
import RuleLink from 'Components/RuleLink'
import withColours from 'Components/utils/withColours'
import withSitePaths from 'Components/utils/withSitePaths'
import { encodeRuleName } from 'Engine/rules'
import { serialiseUnit } from 'Engine/units'
import { compose, isEmpty, isNil, propEq } from 'ramda'
import React, { memo, useEffect, useState } from 'react'
import emoji from 'react-easy-emoji'
import { useTranslation } from 'react-i18next'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'
import { Link } from 'react-router-dom'
import { change, Field, formValueSelector, reduxForm } from 'redux-form'
import {
analysisWithDefaultsSelector,
flatRulesSelector
} from 'Selectors/analyseSelectors'
import Animate from 'Ui/animate'
import AnimatedTargetValue from 'Ui/AnimatedTargetValue'
import CurrencyInput from './CurrencyInput/CurrencyInput'
import './TargetSelection.css'
export default compose(
withColours,
reduxForm({
form: 'conversation',
destroyOnUnmount: false
}),
connect(
state => ({
getTargetValue: dottedName =>
formValueSelector('conversation')(state, dottedName),
analysis: analysisWithDefaultsSelector(state),
flatRules: flatRulesSelector(state),
activeInput: state.activeTargetInput,
objectifs: state.simulation?.config.objectifs || [],
secondaryObjectives:
state.simulation?.config['objectifs secondaires'] || []
}),
dispatch => ({
setFormValue: (field, name) =>
dispatch(change('conversation', field, name)),
setActiveInput: name =>
dispatch({ type: 'SET_ACTIVE_TARGET_INPUT', name })
})
),
memo
)(function TargetSelection({
secondaryObjectives,
analysis,
getTargetValue,
setFormValue,
colours,
activeInput,
setActiveInput,
objectifs
}) {
const [initialRender, setInitialRender] = useState(true)
useEffect(() => {
let targets = getTargets()
// Initialize defaultValue for target that can't be computed
targets
.filter(
target =>
(!target.formule || isEmpty(target.formule)) &&
(!isNil(target.defaultValue) ||
!isNil(target.explanation?.defaultValue)) &&
!getTargetValue(target.dottedName)
)
.forEach(target => {
setFormValue(
target.dottedName,
!isNil(target.defaultValue)
? target.defaultValue
: target.explanation?.defaultValue
)
})
if (initialRender) {
setInitialRender(false)
}
}, [])
const getTargets = () => {
if (!analysis) return []
return analysis.targets.filter(
t =>
!secondaryObjectives.includes(t.dottedName) &&
t.dottedName !== 'contrat salarié . aides employeur'
)
}
let targets = getTargets()
return (
<div id="targetSelection">
{(typeof objectifs[0] === 'string' ? [{ objectifs }] : objectifs).map(
({ icône, objectifs: groupTargets, nom }, index) => (
<React.Fragment key={nom || '0'}>
<div style={{ display: 'flex', alignItems: 'end' }}>
<div style={{ flex: 1 }}>
{nom && (
<h2 style={{ marginBottom: 0 }}>
{emoji(icône)} <T>{nom}</T>
</h2>
)}
</div>
{index === 0 && <PeriodSwitch />}
</div>
<section
className="ui__ plain card"
style={{
marginTop: '.6em',
color: colours.textColour,
background: `linear-gradient(
60deg,
${colours.darkColour} 0%,
${colours.colour} 100%
)`
}}>
<Targets
{...{
activeInput,
setActiveInput,
setFormValue,
targets: targets.filter(({ dottedName }) =>
groupTargets.includes(dottedName)
),
initialRender
}}
/>
</section>
</React.Fragment>
)
)}
</div>
)
})
let Targets = ({
activeInput,
setActiveInput,
setFormValue,
targets,
initialRender
}) => (
<div>
<ul className="targets">
{targets
.map(target => target.explanation || target)
.filter(target => {
return (
target.isApplicable !== false &&
(target.question || target.nodeValue)
)
})
.map(target => (
<Target
key={target.dottedName}
initialRender={initialRender}
{...{
target,
setFormValue,
activeInput,
setActiveInput,
targets
}}
/>
))}
</ul>
</div>
)
const Target = ({
target,
activeInput,
targets,
setActiveInput,
setFormValue,
initialRender
}) => {
const isSmallTarget =
!target.question || !target.formule || isEmpty(target.formule)
return (
<li
key={target.name}
className={isSmallTarget ? 'small-target' : undefined}>
<Animate.appear unless={initialRender}>
<div>
<div className="main">
<Header
{...{
target,
isActiveInput: activeInput === target.dottedName
}}
/>
{isSmallTarget && (
<span
style={{
flex: 1,
borderBottom: '1px dashed #ffffff91',
marginLeft: '1rem'
}}
/>
)}
<TargetInputOrValue
{...{
target,
targets,
activeInput,
setActiveInput,
setFormValue
}}
/>
</div>
{activeInput === target.dottedName && (
<Animate.fromTop>
<InputSuggestions
suggestions={target.suggestions}
onFirstClick={value => {
setFormValue(target.dottedName, '' + value)
}}
rulePeriod={target.période}
colouredBackground={true}
/>
</Animate.fromTop>
)}
</div>
</Animate.appear>
</li>
)
}
let Header = withSitePaths(({ target, sitePaths }) => {
const ruleLink =
sitePaths.documentation.index + '/' + encodeRuleName(target.dottedName)
return (
<span className="header">
<span className="texts">
<span className="optionTitle">
<Link to={ruleLink}>{target.title || target.name}</Link>
</span>
<p>{target.summary}</p>
</span>
</span>
)
})
let CurrencyField = withColours(props => {
return (
<CurrencyInput
style={{
color: props.colours.textColour,
borderColor: props.colours.textColour
}}
debounce={600}
className="targetInput"
value={props.input.value}
{...props.input}
{...props}
/>
)
})
let DebouncedPercentageField = props => (
<PercentageField debounce={600} {...props} />
)
let TargetInputOrValue = ({
target,
targets,
activeInput,
setActiveInput,
firstStepCompleted
}) => {
const {
i18n: { language }
} = useTranslation()
let inputIsActive = activeInput === target.dottedName
return (
<span className="targetInputOrValue">
{inputIsActive || !target.formule || isEmpty(target.formule) ? (
<Field
name={target.dottedName}
onBlur={event => event.preventDefault()}
component={
{ '€': CurrencyField, '%': DebouncedPercentageField }[
serialiseUnit(target.unit)
]
}
{...(inputIsActive ? { autoFocus: true } : {})}
language={language}
/>
) : (
<TargetValue
{...{
targets,
target,
activeInput,
setActiveInput,
firstStepCompleted
}}
/>
)}
{target.dottedName.includes('rémunération . total') && <AidesGlimpse />}
</span>
)
}
const TargetValue = connect(
state => ({
blurValue: analysisWithDefaultsSelector(state)?.cache.inversionFail
}),
dispatch => ({
setFormValue: (field, name) => dispatch(change('conversation', field, name))
})
)(function TargetValue({
targets,
target,
blurValue,
setFormValue,
activeInput,
setActiveInput
}) {
let targetWithValue = targets?.find(propEq('dottedName', target.dottedName)),
value = targetWithValue && targetWithValue.nodeValue
const showField = value => () => {
if (!target.question) return
if (value != null && !Number.isNaN(value))
setFormValue(target.dottedName, Math.round(value) + '')
if (activeInput) setFormValue(activeInput, '')
setActiveInput(target.dottedName)
}
return (
<div
className={classNames({
editable: target.question,
attractClick: target.question && isNil(target.nodeValue)
})}
style={blurValue ? { filter: 'blur(3px)' } : {}}
{...(target.question ? { tabIndex: 0 } : {})}
onClick={showField(value)}
onFocus={showField(value)}>
<AnimatedTargetValue value={value} />
</div>
)
})
const AidesGlimpse = compose(
withRouter,
connect(state => ({ analysis: analysisWithDefaultsSelector(state) }))
)(({ analysis: { targets }, colours }) => {
const aides = targets?.find(
t => t.dottedName === 'contrat salarié . aides employeur'
)
if (!aides || !aides.nodeValue) return null
return (
<Animate.appear>
<div className="aidesGlimpse">
<RuleLink {...aides}>
-{' '}
<strong>
<AnimatedTargetValue value={aides.nodeValue} />
</strong>{' '}
<T>d'aides</T> {emoji(aides.icons)}
</RuleLink>
</div>
</Animate.appear>
)
})