diff --git a/.eslintrc b/.eslintrc index 796e238a1..4312d7156 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,12 @@ env: browser: true commonjs: true es6: true + +overrides: + files: "*.test.js" + env: + mocha: true + extends: - eslint:recommended - plugin:react/recommended diff --git a/componentTestSetup.js b/componentTestSetup.js new file mode 100644 index 000000000..b328984b1 --- /dev/null +++ b/componentTestSetup.js @@ -0,0 +1,15 @@ +import Enzyme from 'enzyme' +import Adapter from 'enzyme-adapter-react-16' +import chai from 'chai' +import sinonChai from 'sinon-chai' + +chai.use(sinonChai) +Enzyme.configure({ adapter: new Adapter() }) + +// Setup Intl in "en" and "fr" for testing +// var areIntlLocalesSupported = require('intl-locales-supported') + +// var localesMyAppSupports = ['en', 'fr'] +global.Intl = require('intl') +require('intl/locale-data/jsonp/en.js') +require('intl/locale-data/jsonp/fr.js') diff --git a/index.html b/index.html index 3d83ebe29..1a276a0d1 100644 --- a/index.html +++ b/index.html @@ -1,21 +1,24 @@ - - - - - - Simulateur d'embauche - - - - - - -
- - - + + + + + + Simulateur d'embauche + + + + + + - + +
+ + + + + + \ No newline at end of file diff --git a/package.json b/package.json index dc03411f7..b31fd0d4b 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,12 @@ "url": "git@github.com:betagouv/syso.git" }, "engines": { - "node": ">=8.10.0 <10.0.0" + "node": ">=8.10.0" }, - "browserslist": ["> 1% in FR", "not ie < 11"], + "browserslist": [ + "> 1% in FR", + "not ie < 11" + ], "dependencies": { "@babel/core": "=7.0.0-beta.46", "@babel/plugin-proposal-decorators": "=7.0.0-beta.46", @@ -34,8 +37,6 @@ "daggy": "^1.2.0", "dedent-js": "^1.0.1", "deep-assign": "^2.0.0", - "eslint": "^4.19.0", - "eslint-plugin-react": "^7.7.0", "express": "^4.16.3", "fantasy-combinators": "0.0.1", "fantasy-land": "^3.5.0", @@ -54,8 +55,6 @@ "json-loader": "^0.5.7", "live-server": "^1.2.0", "marked": "^0.3.17", - "mocha": "^5.0.4", - "mocha-webpack": "^2.0.0-beta.0", "nearley": "^2.13.0", "nearley-loader": "^2.0.0", "npm": "^5.7.1", @@ -98,28 +97,31 @@ "yaml-loader": "^0.5.0" }, "scripts": { - "pretest": - "LIST=`git diff --name-only HEAD..HEAD^ | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi", + "pretest": "LIST=`git diff --name-only HEAD..HEAD^ | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi", "start": "node source/server.js", "externalize": "node source/externalize.js", "compile": "webpack --config source/webpack.prod.js", - "test": - "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/**/*.test.js\"", - "test-watch": - "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/**/*.test.js\" --watch", - "test-meca": - "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/mecanisms.test.js --watch", - "test-rules": - "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/real-rules.test.js --watch", - "test-inversions": - "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/inversion.test.js\" --watch", + "test": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --include componentTestSetup.js --require test/helpers/browser.js \"./{,!(node_modules)/**/}!(webpack).test.js\"", + "test-watch": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/**/*.test.js\" --watch", + "test-meca": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/mecanisms.test.js --watch", + "test-rules": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/real-rules.test.js --watch", + "test-inversions": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/inversion.test.js\" --watch", + "test-components": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --include componentTestSetup.js --require test/helpers/browser.js \"source/components/**/*.test.js\" --watch", "heroku-postbuild": "yarn install --production=false && yarn compile", - "eslint": - "LIST=`git diff --cached --name-only HEAD | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi", - "eslint-check": - "eslint --print-config .eslintrc | eslint-config-prettier-check" + "eslint": "LIST=`git diff --cached --name-only HEAD | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi", + "eslint-check": "eslint --print-config .eslintrc | eslint-config-prettier-check" }, "devDependencies": { - "eslint-config-prettier": "^2.9.0" + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", + "eslint": "^4.19.0", + "eslint-config-prettier": "^2.9.0", + "eslint-plugin-react": "^7.7.0", + "intl": "^1.2.5", + "intl-locales-supported": "^1.0.0", + "mocha": "^5.0.4", + "mocha-webpack": "^2.0.0-beta.0", + "sinon": "^4.5.0", + "sinon-chai": "^3.0.0" } } diff --git a/source/components/CurrencyInput/CurrencyInput.css b/source/components/CurrencyInput/CurrencyInput.css new file mode 100644 index 000000000..ed810f090 --- /dev/null +++ b/source/components/CurrencyInput/CurrencyInput.css @@ -0,0 +1,17 @@ +.currencyInput__container { + display: flex !important; + align-items: baseline; + justify-content: flex-end; +} + +.currencyInput__input { + height: inherit; + border: none; + text-align: inherit; + font-family: inherit; + font-weight: inherit; + min-width: 0; + color: inherit; + background-color: inherit; + font-size: inherit; +} diff --git a/source/components/CurrencyInput/CurrencyInput.js b/source/components/CurrencyInput/CurrencyInput.js new file mode 100644 index 000000000..979fea42b --- /dev/null +++ b/source/components/CurrencyInput/CurrencyInput.js @@ -0,0 +1,83 @@ +import React, { Component } from 'react' +import { dissoc } from 'ramda' +import classnames from 'classnames' +import './CurrencyInput.css' + +let isCurrencyPrefixed = language => + !!Intl.NumberFormat(language, { + style: 'currency', + currency: 'EUR' + }) + .format(12) + .match(/€.*12/) + +class CurrencyInput extends Component { + state = { + value: '' + } + static getDerivedStateFromProps(nextProps) { + return { + value: nextProps.value + } + } + getSnapshotBeforeUpdate = () => { + return this.input.selectionStart + } + componentDidMount() { + this.adaptInputSize() + } + adaptInputSize = () => { + if (this.input && isCurrencyPrefixed(this.props.language)) + this.input.style.width = this.input.value.length + 0.2 + 'ch' + } + componentDidUpdate = (_, __, cursorPosition) => { + this.input.selectionStart = cursorPosition + this.input.selectionEnd = cursorPosition + this.adaptInputSize() + } + focusInput = () => { + this.input.focus() + } + handleChange = event => { + let value = event.target.value + value = value + .replace(/,/g, '.') + .replace(/[^\d.]/g, '') + .replace(/\.(.*)\.(.*)/g, '$1.$2') + + this.setState({ value }, this.adaptInputSize) + if (value.endsWith('.')) { + return + } + if (this.props.onChange) { + event.target.value = value + this.props.onChange(event) + } + } + render() { + let forwardedProps = dissoc( + ['onChange', 'value', 'language', 'className'], + this.props + ) + return ( +
+ {isCurrencyPrefixed(this.props.language) && '€'} + (this.input = ref)} + value={this.state.value} + /> + {!isCurrencyPrefixed(this.props.language) && <> €} +
+ ) + } +} + +export default CurrencyInput diff --git a/source/components/CurrencyInput/CurrencyInput.test.js b/source/components/CurrencyInput/CurrencyInput.test.js new file mode 100644 index 000000000..90120b297 --- /dev/null +++ b/source/components/CurrencyInput/CurrencyInput.test.js @@ -0,0 +1,68 @@ +import CurrencyInput from './CurrencyInput' +import React from 'react' +import { shallow } from 'enzyme' +import { expect } from 'chai' +import { spy, match } from 'sinon' + +let getInput = component => shallow(component).find('input') +describe('CurrencyInput', () => { + it('should render an input', () => { + expect(getInput()).to.have.length(1) + }) + it('should accept both . and , as decimal separator', () => { + let onChange = spy() + const input = getInput() + input.simulate('change', { target: { value: '12.1' } }) + expect(onChange).to.have.been.calledWith( + match.hasNested('target.value', '12.1') + ) + input.simulate('change', { target: { value: '12,1' } }) + expect(onChange).to.have.been.calledWith( + match.hasNested('target.value', '12.1') + ) + }) + it('should not accept negative number', () => { + let onChange = spy() + const input = getInput() + input.simulate('change', { target: { value: '-12' } }) + expect(onChange).to.have.been.calledWith( + match.hasNested('target.value', '12') + ) + }) + + it('should not accept anything else than number', () => { + let onChange = spy() + const input = getInput() + input.simulate('change', { target: { value: '*1/2abc3' } }) + expect(onChange).to.have.been.calledWith( + match.hasNested('target.value', '123') + ) + }) + it('should pass other props to the input', () => { + const input = getInput() + expect(input.prop('autoFocus')).to.be.true + }) + it('should not call onChange while the decimal part is being written', () => { + let onChange = spy() + const input = getInput() + input.simulate('change', { target: { value: '111,' } }) + expect(onChange).not.to.have.been.called + }) + + it('should change the position of the currency symbol depending on the language', () => { + const inputFr = shallow() + expect( + inputFr + .children() + .last() + .text() + ).to.includes('€') + const inputEn = shallow() + expect( + inputEn + .children() + .first() + .text() + ).to.includes('€') + }) +}) diff --git a/source/components/ResultsGrid.js b/source/components/ResultsGrid.js index c24c03a3e..c6ef0095a 100644 --- a/source/components/ResultsGrid.js +++ b/source/components/ResultsGrid.js @@ -1,6 +1,4 @@ import { - curry, - evolve, path, propEq, pathEq, @@ -14,20 +12,19 @@ import { pathOr, toPairs, keys, - head, find } from 'ramda' import React, { Component } from 'react' import { Trans, translate } from 'react-i18next' -import PropTypes from 'prop-types' import { connect } from 'react-redux' import { withRouter } from 'react-router' import { Link } from 'react-router-dom' +import withLanguage from './withLanguage' import { formValueSelector } from 'redux-form' import './Results.css' import '../engine/mecanismViews/Somme.css' -import { capitalise0, humanFigure } from '../utils' +import { capitalise0 } from '../utils' import { nameLeaf, encodeRuleName, findRuleByDottedName } from 'Engine/rules' // Filtered variables and rules can't be filtered in a uniform way, for now @@ -70,6 +67,12 @@ export let byBranch = analysis => { return result } +let formatCurrency = language => number => + Intl.NumberFormat(language, { + style: 'currency', + currency: 'EUR' + }).format(number) + @withRouter @connect(state => ({ analysis: state.analysis, @@ -79,6 +82,7 @@ export let byBranch = analysis => { inversions: formValueSelector('conversation')(state, 'inversions') })) @translate() +@withLanguage export default class ResultsGrid extends Component { render() { let { @@ -86,7 +90,8 @@ export default class ResultsGrid extends Component { situationGate, targetNames, inversions, - flatRules + flatRules, + language } = this.props, rules = flatRules @@ -123,6 +128,7 @@ export default class ResultsGrid extends Component { flatRules, 'contrat salarié . avantages salarié' ) + let formatLocalizedCurrency = formatCurrency(language) return (
@@ -140,7 +146,7 @@ export default class ResultsGrid extends Component { colSpan={(relevantSalaries.size - 1) * 2} className="element value" id="sommeBase"> - {humanFigure(2)(base)}{' '} + {formatLocalizedCurrency(base)}{' '} @@ -158,7 +164,7 @@ export default class ResultsGrid extends Component { className="element value" id="sommeBase"> + - {humanFigure(2)(avan)}{' '} + {formatLocalizedCurrency(avan)}{' '} @@ -176,21 +182,20 @@ export default class ResultsGrid extends Component { className="element value" id="sommeBase"> = - {humanFigure(2)(brut)}{' '} + {formatLocalizedCurrency(brut)}{' '} {toPairs(results).map(([branch, values]) => { let props = { - key: branch, branch, values, analysis, rules, relevantSalaries } - return + return })} - {humanFigure(2)(net)}{' '} + {formatLocalizedCurrency(net)}{' '} Salaire net @@ -216,7 +221,7 @@ export default class ResultsGrid extends Component { = , - {humanFigure(2)(total)}{' '} + {formatLocalizedCurrency(total)}{' '} Salaire chargé @@ -231,25 +236,31 @@ export default class ResultsGrid extends Component { } @translate() +@withLanguage class Row extends Component { - static contextTypes = { - i18n: PropTypes.object.isRequired - } state = { folded: true } render() { - let { rules, branch, values, analysis, relevantSalaries } = this.props, + let { + rules, + branch, + values, + analysis, + relevantSalaries, + language, + t + } = this.props, detail = byName(values), ruleData = mapObjIndexed( - (v, k, o) => findRuleByDottedName(rules, k), + (v, k) => findRuleByDottedName(rules, k), detail ), - { i18n } = this.context + formatLocalizedCurrency = formatCurrency(language) let title = name => { let node = ruleData[name] - return node.title || capitalise0(i18n.t(node.name)) + return node.title || capitalise0(t(node.name)) } let aggregateRow = ( @@ -257,8 +268,8 @@ class Row extends Component { key="aggregateRow" onClick={() => this.setState({ folded: !this.state.folded })}> - {capitalise0(i18n.t(branch))}  - {this.state.folded ? i18n.t('déplier') + ' >' : i18n.t('replier')} + {capitalise0(t(branch))}  + {this.state.folded ? t('déplier') + ' >' : t('replier')} {this.state.folded ? ( @@ -269,7 +280,7 @@ class Row extends Component { - - {humanFigure(2)(cell(branch, 'salarié', analysis))} + {formatLocalizedCurrency(cell(branch, 'salarié', analysis))} )} @@ -279,7 +290,7 @@ class Row extends Component { + - {humanFigure(2)(cell(branch, 'employeur', analysis))} + {formatLocalizedCurrency(cell(branch, 'employeur', analysis))} )} @@ -305,7 +316,9 @@ class Row extends Component { - - {humanFigure(2)(subCell(detail, subCellName, 'salarié'))} + {formatLocalizedCurrency( + subCell(detail, subCellName, 'salarié') + )} )} @@ -315,7 +328,9 @@ class Row extends Component { + - {humanFigure(2)(subCell(detail, subCellName, 'employeur'))} + {formatLocalizedCurrency( + subCell(detail, subCellName, 'employeur') + )} )} @@ -330,25 +345,23 @@ class Row extends Component { // TODO Ce code est beaucoup trop spécifique // C'est essentiellement une copie de Row @translate() +@withLanguage class ReductionRow extends Component { - static contextTypes = { - i18n: PropTypes.object.isRequired - } state = { folded: true } render() { - let { relevantSalaries, node } = this.props, - { i18n } = this.context + let { relevantSalaries, node, language, t } = this.props if (!relevantSalaries.has('salaire total')) return null let value = node && node.nodeValue ? node.nodeValue : 0 + let formatLocalizedCurrency = formatCurrency(language) let aggregateRow = ( this.setState({ folded: !this.state.folded })}> Réductions  - {this.state.folded ? i18n.t('déplier') + ' >' : i18n.t('replier')} + {this.state.folded ? t('déplier') + ' >' : t('replier')} {this.state.folded ? ( @@ -359,7 +372,7 @@ class ReductionRow extends Component { + - {humanFigure(2)(0)} + {formatLocalizedCurrency(0)} )} @@ -369,7 +382,7 @@ class ReductionRow extends Component { - - {humanFigure(2)(value)} + {formatLocalizedCurrency(value)} )} @@ -393,7 +406,7 @@ class ReductionRow extends Component { + - {humanFigure(2)(0)} + {formatLocalizedCurrency(0)} )} @@ -403,7 +416,7 @@ class ReductionRow extends Component { - - {humanFigure(2)(value)} + {formatLocalizedCurrency(value)} )} diff --git a/source/components/Sondage.js b/source/components/Sondage.js index db119cfe8..3c629e4c6 100644 --- a/source/components/Sondage.js +++ b/source/components/Sondage.js @@ -5,7 +5,7 @@ import ReactPiwik from './Tracker' import ReactCSSTransitionGroup from 'react-addons-css-transition-group' import Smiley from './SatisfactionSmiley' import TypeFormEmbed from './TypeFormEmbed' -import PropTypes from 'prop-types' +import withLanguage from './withLanguage' import { Trans, translate } from 'react-i18next' @connect(state => ({ @@ -15,15 +15,13 @@ import { Trans, translate } from 'react-i18next' conversationStarted: state.conversationStarted })) @translate() +@withLanguage export default class Sondage extends Component { state = { visible: false, showForm: false, askFeedbackTime: 'AFTER_FIRST_ESTIMATE' } - static contextTypes = { - i18n: PropTypes.object.isRequired - } static getDerivedStateFromProps(nextProps, currentState) { let feedbackAlreadyAsked = !!document.cookie.includes('feedback_asked=true') let conditions = { @@ -57,7 +55,8 @@ export default class Sondage extends Component { this.setCookie() } render() { - let { satisfaction, showForm, visible, askFeedbackTime } = this.state + let { satisfaction, showForm, visible, askFeedbackTime } = this.state, + { language } = this.props return ( <> @@ -67,7 +66,7 @@ export default class Sondage extends Component { exterieur: false, satisfaction, answertiming: askFeedbackTime, - language: this.context.i18n.language + language }} /> )} diff --git a/source/components/TargetSelection.css b/source/components/TargetSelection.css index 721205ba1..e422f0106 100644 --- a/source/components/TargetSelection.css +++ b/source/components/TargetSelection.css @@ -80,23 +80,25 @@ #targetSelection .targetInputOrValue { font-size: 125%; display: flex; + height: 1.6em; justify-content: flex-end; - margin-left: .6em; + align-items: flex-end; + margin-left: 0.6em; } #targetSelection .editable { border-bottom: 1px dashed #ffffff91; min-width: 2.5em; + height: inherit; display: inline-block; } #targetSelection .attractClick, -#targetSelection input[type='number'] { - width: 5em !important; +#targetSelection .targetInput { + width: 5.5em !important; display: inline-block; - height: 1.6em; text-align: right; - font-weight: 600; + font-weight: 700; padding: 0; padding: 0em 0.5em; background: white; diff --git a/source/components/TargetSelection.js b/source/components/TargetSelection.js index 70f2c047f..641262700 100644 --- a/source/components/TargetSelection.js +++ b/source/components/TargetSelection.js @@ -1,6 +1,5 @@ import React, { Component } from 'react' import { Trans, translate } from 'react-i18next' -import formValueTypes from 'Components/conversation/formValueTypes' import { findRuleByName } from 'Engine/rules' import { propEq, curry } from 'ramda' import './TargetSelection.css' @@ -11,8 +10,9 @@ import { connect } from 'react-redux' import { RuleValue } from './rule/RuleValueVignette' import classNames from 'classnames' import ProgressCircle from './ProgressCircle/ProgressCircle' +import withLanguage from './withLanguage' import InputSuggestions from 'Components/conversation/InputSuggestions' -import { buildValidationFunction } from './conversation/FormDecorator' +import CurrencyInput from './CurrencyInput/CurrencyInput' export let salaries = ['salaire total', 'salaire de base', 'salaire net'] export let popularTargetNames = [...salaries, 'aides employeur'] @@ -65,7 +65,9 @@ export default class TargetSelection extends Component { Estimation approximative {' '}
- pour un CDI non cadre + + pour une situation par défaut (CDI non cadre). +

Affiner le calcul @@ -143,37 +145,32 @@ let Header = ({ target, conversationStarted, isActiveInput }) => { ) } -let validate = buildValidationFunction(formValueTypes['euros']) -let InputComponent = ({ input, meta: { dirty, error } }) => ( - - {dirty && error && {error}} - - -) -let TargetInputOrValue = ({ - target, - targets, - firstEstimationComplete, - activeInput, - setActiveInput -}) => ( - - {activeInput === target.dottedName ? ( - - ) : ( - - )} - {(firstEstimationComplete || target.question) && ( - - )} - -) +let CurrencyField = props => { + return ( + + ) +} +let TargetInputOrValue = withLanguage( + ({ target, targets, activeInput, setActiveInput, language }) => ( + + {activeInput === target.dottedName ? ( + + ) : ( + + )} + + ) +) @connect( () => ({}), dispatch => ({ @@ -190,9 +187,7 @@ class TargetValue extends Component { setActiveInput } = this.props, targetWithValue = targets.find(propEq('dottedName', target.dottedName)), - value = targetWithValue && targetWithValue.nodeValue, - humanValue = value != null && value.toFixed(0) - + value = targetWithValue && targetWithValue.nodeValue return ( { if (!target.question) return if (value != null) { - setFormValue(target.dottedName, humanValue + '') + setFormValue(target.dottedName, Math.floor(value) + '') setFormValue(activeInput, '') } - setActiveInput(target.dottedName) }}> diff --git a/source/components/conversation/FormDecorator.js b/source/components/conversation/FormDecorator.js index 9ab515e19..b7dc68066 100644 --- a/source/components/conversation/FormDecorator.js +++ b/source/components/conversation/FormDecorator.js @@ -1,19 +1,17 @@ import React, { Component } from 'react' import { Trans, translate } from 'react-i18next' -import PropTypes from 'prop-types' import classNames from 'classnames' import { connect } from 'react-redux' -import { Field, change, formValueSelector } from 'redux-form' +import { Field, change } from 'redux-form' import { stepAction } from '../../actions' import { capitalise0 } from '../../utils' -import { path } from 'ramda' import Explicable from 'Components/conversation/Explicable' import IgnoreStepButton from './IgnoreStepButton' import { findRuleByDottedName } from 'Engine/rules' export let buildValidationFunction = valueType => { let validator = valueType ? valueType.validator : {}, - { pre = v => v, test = v => true, error } = validator + { pre = v => v, test = () => true, error } = validator return v => v != undefined && (test(pre(v)) ? undefined : error) } /* @@ -39,10 +37,8 @@ export var FormDecorator = formType => RenderField => dispatch(change('conversation', field, value)) }) ) + @translate() class extends Component { - static contextTypes = { - i18n: PropTypes.object.isRequired - } state = { helpVisible: false } @@ -62,17 +58,16 @@ export var FormDecorator = formType => RenderField => renderUnfolded() { let { - setFormValue, - stepAction, - subquestion, - possibleChoice, // should be found in the question set theoritically, but it is used for a single choice question -> the question itself is dynamic and cannot be input as code, - defaultValue, - valueType, - fieldName, - inversion, - themeColours - } = this.props, - { i18n } = this.context + setFormValue, + stepAction, + subquestion, + possibleChoice, // should be found in the question set theoritically, but it is used for a single choice question -> the question itself is dynamic and cannot be input as code, + defaultValue, + valueType, + fieldName, + inversion, + themeColours + } = this.props /* There won't be any answer zone here, widen the question zone */ let wideQuestion = formType == 'rhetorical-question' && !possibleChoice @@ -144,21 +139,19 @@ export var FormDecorator = formType => RenderField => dottedName, fieldName, fieldTitle, - flatRules - } = this.props, - { i18n } = this.context + flatRules, + t + } = this.props let answer = situationGate(fieldName), rule = findRuleByDottedName(flatRules, dottedName + ' . ' + answer), - translatedAnswer = (rule && rule.title) || i18n.t(answer) + translatedAnswer = (rule && rule.title) || t(answer) return (
{capitalise0(fieldTitle || title)} - - {translatedAnswer} - + {translatedAnswer} - {}
) } diff --git a/source/components/conversation/Input.js b/source/components/conversation/Input.js index 967362045..c84a90a11 100644 --- a/source/components/conversation/Input.js +++ b/source/components/conversation/Input.js @@ -1,6 +1,5 @@ import React, { Component } from 'react' import { translate } from 'react-i18next' -import PropTypes from 'prop-types' import { FormDecorator } from './FormDecorator' import classnames from 'classnames' import SendButton from './SendButton' @@ -9,9 +8,6 @@ import InputSuggestions from './InputSuggestions' @FormDecorator('input') @translate() export default class Input extends Component { - static contextTypes = { - i18n: PropTypes.object.isRequired - } render() { let { input, @@ -19,13 +15,13 @@ export default class Input extends Component { submit, valueType, meta: { dirty, error, active }, + t, themeColours } = this.props, answerSuffix = valueType.suffix, suffixed = answerSuffix != null, inputError = dirty && error, - submitDisabled = !dirty || inputError, - { i18n } = this.context + submitDisabled = !dirty || inputError return ( @@ -39,7 +35,7 @@ export default class Input extends Component { className={classnames({ suffixed })} id={'step-' + dottedName} inputMode="numeric" - placeholder={i18n.t('votre réponse')} + placeholder={t('votre réponse')} style={ !active ? { border: '2px dashed #ddd' } diff --git a/source/components/conversation/InputSuggestions.js b/source/components/conversation/InputSuggestions.js index 690b11f11..f08d27edc 100644 --- a/source/components/conversation/InputSuggestions.js +++ b/source/components/conversation/InputSuggestions.js @@ -3,24 +3,20 @@ import './InputSuggestions.css' import withColours from '../withColours' import { toPairs } from 'ramda' import { translate } from 'react-i18next' -import PropTypes from 'prop-types' @withColours @translate() -export default class extends Component { - static contextTypes = { - i18n: PropTypes.object.isRequired - } +export default class InputSuggestions extends Component { state = { suggestion: null } render() { let { - suggestions, - onSecondClick, - onFirstClick, - colouredBackground, - colours - } = this.props, - { i18n } = this.context + suggestions, + onSecondClick, + onFirstClick, + colouredBackground, + colours, + t + } = this.props if (!suggestions) return null return ( @@ -41,7 +37,7 @@ export default class extends Component { ? colours.textColour : colours.textColourOnWhite }}> - + {text} diff --git a/source/components/conversation/select/Select.js b/source/components/conversation/select/Select.js index b766fc28c..3422922fe 100644 --- a/source/components/conversation/select/Select.js +++ b/source/components/conversation/select/Select.js @@ -26,7 +26,10 @@ let getOptions = input => @FormDecorator('select') export default class Select extends Component { render() { - let { input: { onChange }, submit, suggestions } = this.props, + let { + input: { onChange }, + submit + } = this.props, submitOnChange = option => { onChange(option.code) submit() diff --git a/source/components/rule/Algorithm.js b/source/components/rule/Algorithm.js index d2c80f88a..24ecbd10d 100644 --- a/source/components/rule/Algorithm.js +++ b/source/components/rule/Algorithm.js @@ -6,30 +6,33 @@ import { AttachDictionary } from '../AttachDictionary' import knownMecanisms from 'Engine/known-mecanisms.yaml' import { makeJsx } from 'Engine/evaluation' import './Algorithm.css' -import { humanFigure } from '../../utils' import { head } from 'ramda' import { analyse } from 'Engine/traverse' import { exampleSituationGateWithDefaults } from './Examples' -import PropTypes from 'prop-types' +import withLanguage from '../withLanguage' let RuleWithoutFormula = () => (

- Cette règle n'a pas de formule de calcul pour l'instant. Sa valeur doit donc - être renseignée directement. + Cette règle n’a pas de formule de calcul pour l’instant. Sa valeur doit + donc être renseignée directement.

) @AttachDictionary(knownMecanisms) @translate() +@withLanguage export default class Algorithm extends React.Component { - static contextTypes = { - i18n: PropTypes.object.isRequired - } render() { - let { rule: displayedRule, showValues, currentExample, rules } = this.props, - { i18n } = this.context, + let { + rule: displayedRule, + showValues, + currentExample, + t, + rules, + language + } = this.props, ruleWithoutFormula = !displayedRule['formule'] || path(['formule', 'explanation', 'une possibilité'], displayedRule) @@ -52,7 +55,9 @@ export default class Algorithm extends React.Component { ) applicabilityMecanisms.length > 0 && (
-

Déclenchement

+

+ Déclenchement +

    {applicabilityMecanisms.map(v => (
  • {makeJsx(v)}
  • @@ -65,7 +70,11 @@ export default class Algorithm extends React.Component {

    Calcul {!ruleWithoutFormula && ( - Cliquez sur chaque chaque valeur pour comprendre + + + Cliquez sur chaque chaque valeur pour comprendre + + )}

    {ruleWithoutFormula ? ( @@ -76,14 +85,16 @@ export default class Algorithm extends React.Component {
+ style={{ visibility: showValues ? 'visible' : 'hidden' }}>