import { setActiveTarget, updateSituation } from 'Actions/actions' import { T } from 'Components' import InputSuggestions from 'Components/conversation/InputSuggestions' import PeriodSwitch from 'Components/PeriodSwitch' import RuleLink from 'Components/RuleLink' import { ThemeColoursContext } from 'Components/utils/withColours' import { SitePathsContext } from 'Components/utils/withSitePaths' import { formatCurrency } from 'Engine/format' import { encodeRuleName } from 'Engine/rules' import { isEmpty, isNil } from 'ramda' import React, { useContext, useEffect, useState } from 'react' import emoji from 'react-easy-emoji' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { Link } from 'react-router-dom' import { RootState } from 'Reducers/rootReducer' import { analysisWithDefaultsSelector, situationSelector, useTarget } from 'Selectors/analyseSelectors' import { Rule } from 'Types/rule' import Animate from 'Ui/animate' import AnimatedTargetValue from 'Ui/AnimatedTargetValue' import CurrencyInput from './CurrencyInput/CurrencyInput' import './TargetSelection.css' export default function TargetSelection() { const [initialRender, setInitialRender] = useState(true) const analysis = useSelector(analysisWithDefaultsSelector) const objectifs = useSelector( (state: RootState) => state.simulation?.config.objectifs || [] ) const secondaryObjectives = useSelector( (state: RootState) => state.simulation?.config['objectifs secondaires'] || [] ) const situation = useSelector(situationSelector) const dispatch = useDispatch() const colours = useContext(ThemeColoursContext) const targets = analysis?.targets.filter( t => !secondaryObjectives.includes(t.dottedName) && t.dottedName !== 'contrat salarié . aides employeur' ) || [] useEffect(() => { // Initialize defaultValue for target that can't be computed // TODO: this logic shouldn't be here targets .filter( target => (!target.formule || isEmpty(target.formule)) && (!isNil(target.defaultValue) || !isNil(target.explanation?.defaultValue)) && !situation[target.dottedName] ) .forEach(target => { dispatch( updateSituation( target.dottedName, !isNil(target.defaultValue) ? target.defaultValue : target.explanation?.defaultValue ) ) }) setInitialRender(false) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return ( <div id="targetSelection"> {((typeof objectifs[0] === 'string' ? [{ objectifs }] : objectifs) as any).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 {...{ targets: targets.filter(({ dottedName }) => groupTargets.includes(dottedName) ), initialRender }} /> </section> </React.Fragment> ) )} </div> ) } let Targets = ({ 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 }} /> ))} </ul> </div> ) const Target = ({ target, initialRender }) => { const activeInput = useSelector((state: RootState) => state.activeTargetInput) const dispatch = useDispatch() const isActiveInput = activeInput === target.dottedName 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 }} /> {isSmallTarget && ( <span style={{ flex: 1, borderBottom: '1px dashed #ffffff91', marginLeft: '1rem' }} /> )} <TargetInputOrValue {...{ target, isActiveInput, isSmallTarget }} /> </div> {isActiveInput && ( <Animate.fromTop> <InputSuggestions suggestions={target.suggestions} onFirstClick={value => { dispatch(updateSituation(target.dottedName, value)) }} rulePeriod={target.période} colouredBackground={true} /> </Animate.fromTop> )} </div> </Animate.appear> </li> ) } let Header = ({ target }) => { const sitePaths = useContext(SitePathsContext) 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> ) } type TargetInputOrValueProps = { target: Rule isActiveInput: boolean isSmallTarget: boolean } let TargetInputOrValue = ({ target, isActiveInput, isSmallTarget }: TargetInputOrValueProps) => { const { language } = useTranslation().i18n const colors = useContext(ThemeColoursContext) const dispatch = useDispatch() const situationValue = Math.round( useSelector(situationSelector)[target.dottedName] ) const targetWithValue = useTarget(target.dottedName) const value = targetWithValue?.nodeValue ? Math.round(targetWithValue?.nodeValue) : undefined const inversionFail = useSelector( (state: RootState) => analysisWithDefaultsSelector(state)?.cache.inversionFail ) const blurValue = inversionFail && !isActiveInput && value return ( <span className="targetInputOrValue" style={blurValue ? { filter: 'blur(3px)' } : {}} > {target.question ? ( <> {!isActiveInput && <AnimatedTargetValue value={value} />} <CurrencyInput style={{ color: colors.textColour, borderColor: colors.textColour }} debounce={600} name={target.dottedName} value={situationValue || value} className={ isActiveInput || isNil(value) ? 'targetInput' : 'editableTarget' } onChange={evt => dispatch( updateSituation(target.dottedName, Number(evt.target.value)) ) } onFocus={() => { if (isSmallTarget) return dispatch(setActiveTarget(target.dottedName)) }} language={language} /> <span className="targetInputBottomBorder"> {formatCurrency(value, language)} </span> </> ) : ( <span> {Number.isNaN(value) ? '—' : formatCurrency(value, language)} </span> )} {target.dottedName.includes('prix du travail') && <AidesGlimpse />} </span> ) } function AidesGlimpse() { const aides = useTarget('contrat salarié . aides employeur') const { language } = useTranslation().i18n // Dans le cas où il n'y a qu'une seule aide à l'embauche qui s'applique, nous // faisons un lien direct vers cette aide, plutôt qu'un lien vers la liste qui // est une somme des aides qui sont toutes nulle sauf l'aide active. const aidesNode = aides.explanation const aidesDetail = aides.explanation.formule.explanation.explanation const aidesNotNul = aidesDetail.filter(node => node.nodeValue !== 0) const aideLink = aidesNotNul.length === 1 ? aidesNotNul[0] : aidesNode if (!aides?.nodeValue) return null return ( <Animate.appear> <div className="aidesGlimpse"> <RuleLink {...aideLink}> <T>en incluant</T>{' '} <strong> <AnimatedTargetValue value={aides.nodeValue}> <span>{formatCurrency(aides.nodeValue, language)}</span> </AnimatedTargetValue> </strong>{' '} <T>d'aides</T> {emoji(aides.explanation.icons)} </RuleLink> </div> </Animate.appear> ) }