From 4753e1f53594fcd2156bb4dccaf78b347abd0645 Mon Sep 17 00:00:00 2001 From: mama Date: Wed, 17 Jan 2018 16:11:14 +0100 Subject: [PATCH 01/15] =?UTF-8?q?Les=20r=C3=A9sultats=20niveau=201=20sont?= =?UTF-8?q?=20arrondis=20=C3=A0=20l'entier=20pr=C3=A8s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/components/rule/RuleValueVignette.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/components/rule/RuleValueVignette.js b/source/components/rule/RuleValueVignette.js index 945a4e70e..98fd90dc1 100644 --- a/source/components/rule/RuleValueVignette.js +++ b/source/components/rule/RuleValueVignette.js @@ -2,7 +2,6 @@ import React from 'react' import { Link } from 'react-router-dom' import { encodeRuleName } from 'Engine/rules' import classNames from 'classnames' -import { capitalise0 } from '../../utils' let fmt = new Intl.NumberFormat('fr-FR').format export let humanFigure = decimalDigits => value => fmt(value.toFixed(decimalDigits)) @@ -45,7 +44,7 @@ let RuleValue = ({ unsatisfied, irrelevant, conversationStarted, ruleValue }) => ? ['irrelevant', "Vous n'êtes pas concerné"] : unsatisfied ? ['unsatisfied', 'En attente de vos réponses...'] - : ['figure', humanFigure(2)(ruleValue) + ' €'] + : ['figure', humanFigure(0)(ruleValue) + ' €'] { /*

Pourquoi ?

*/ From cc1f25e4d71e1c14039dd45dfe9cab3aa9b29642 Mon Sep 17 00:00:00 2001 From: mama Date: Wed, 17 Jan 2018 16:56:30 +0100 Subject: [PATCH 02/15] =?UTF-8?q?Recalcul=20des=20r=C3=A9sultats=20en=20te?= =?UTF-8?q?mps=20r=C3=A9el=20pendant=20la=20saisie=20num=C3=A9rique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/conversation/FormDecorator.js | 2 +- source/reducers.js | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/source/components/conversation/FormDecorator.js b/source/components/conversation/FormDecorator.js index f477c6c2c..1db92d615 100644 --- a/source/components/conversation/FormDecorator.js +++ b/source/components/conversation/FormDecorator.js @@ -75,11 +75,11 @@ export var FormDecorator = formType => RenderField => props passées à ce dernier, car React 15.2 n'aime pas les attributes inconnus des balises html, dans notre cas. */ + //TODO hack, enables redux-form/CHANGE to update the form state before the traverse functions are run let submit = () => setTimeout(() => stepAction('fold', fieldName), 1), stepProps = { ...this.props.step, inverted, - //TODO hack, enables redux-form/CHANGE to update the form state before the traverse functions are run submit, setFormValue: (value, name = fieldName) => setFormValue(name, value) } diff --git a/source/reducers.js b/source/reducers.js index 4286eaf52..319f34bd2 100644 --- a/source/reducers.js +++ b/source/reducers.js @@ -50,10 +50,17 @@ export let reduceSteps = (tracker, flatRules, answerSource) => ( // Optimization - don't parse on each analysis if (!state.parsedRules) state.parsedRules = parseAll(flatRules) - if (![START_CONVERSATION, STEP_ACTION].includes(action.type)) return state + if ( + ![START_CONVERSATION, STEP_ACTION, '@@redux-form/CHANGE'].includes( + action.type + ) + ) + return state let targetNames = - action.type == START_CONVERSATION ? action.targetNames : state.targetNames + action.type == START_CONVERSATION + ? action.targetNames + : state.targetNames || [] let sim = targetNames.length === 1 ? findRuleByName(flatRules, targetNames[0]) : {}, @@ -66,9 +73,14 @@ export let reduceSteps = (tracker, flatRules, answerSource) => ( situationWithDefaults = assume(intermediateSituation, rulesDefaults) let analysis = analyseMany(state.parsedRules, targetNames)( - situationWithDefaults(state) - ), - nextWithDefaults = getNextSteps(situationWithDefaults(state), analysis), + situationWithDefaults(state) + ) + + if (action.type === '@@redux-form/CHANGE') { + return { ...state, analysis, situationGate: situationWithDefaults(state) } + } + + let nextWithDefaults = getNextSteps(situationWithDefaults(state), analysis), assumptionsMade = !isEmpty(rulesDefaults), done = nextWithDefaults.length == 0 From 110a9fd82c8b38035858dcf71a71380bb468dd05 Mon Sep 17 00:00:00 2001 From: mama Date: Wed, 17 Jan 2018 17:39:26 +0100 Subject: [PATCH 03/15] Debounce de la saisie utilisateur --- source/debounceFormChangeActions.js | 34 +++++++++++++++++++++++++++++ source/entry.js | 13 +++++++++-- source/reducers.js | 4 ++-- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 source/debounceFormChangeActions.js diff --git a/source/debounceFormChangeActions.js b/source/debounceFormChangeActions.js new file mode 100644 index 000000000..66d1dbfbe --- /dev/null +++ b/source/debounceFormChangeActions.js @@ -0,0 +1,34 @@ +// Thank you, github.com/ryanseddon/redux-debounced + +export default () => { + let timers = {} + + let time = 500 + + let middleware = () => dispatch => action => { + let { type } = action + + let key = type + + const shouldDebounce = key === '@@redux-form/CHANGE' + + if (!shouldDebounce) { + return dispatch(action) + } + + if (timers[key]) { + clearTimeout(timers[key]) + } + + dispatch(action) + return new Promise(resolve => { + timers[key] = setTimeout(() => { + resolve(dispatch({ type: 'USER_INPUT_UPDATE' })) + }, time) + }) + } + + middleware._timers = timers + + return middleware +} diff --git a/source/entry.js b/source/entry.js index 37c45582d..f425367a1 100644 --- a/source/entry.js +++ b/source/entry.js @@ -1,10 +1,11 @@ import React from 'react' import { render } from 'react-dom' -import { compose, createStore } from 'redux' +import { compose, createStore, applyMiddleware } from 'redux' import App from './containers/App' import reducers from './reducers' import DevTools from './DevTools' import { AppContainer } from 'react-hot-loader' +import debounceFormChangeActions from './debounceFormChangeActions' import computeThemeColours from './components/themeColours' import { getIframeOption, getUrl } from './utils' @@ -13,7 +14,15 @@ let initialStore = { themeColours: computeThemeColours(getIframeOption('couleur')) } -let store = createStore(reducers, initialStore, compose(DevTools.instrument())) +let createStoreWithMiddleware = applyMiddleware(debounceFormChangeActions())( + createStore +) + +let store = createStoreWithMiddleware( + reducers, + initialStore, + compose(DevTools.instrument()) +) let anchor = document.querySelector('#js') render(, anchor) diff --git a/source/reducers.js b/source/reducers.js index 319f34bd2..c371c3648 100644 --- a/source/reducers.js +++ b/source/reducers.js @@ -51,7 +51,7 @@ export let reduceSteps = (tracker, flatRules, answerSource) => ( if (!state.parsedRules) state.parsedRules = parseAll(flatRules) if ( - ![START_CONVERSATION, STEP_ACTION, '@@redux-form/CHANGE'].includes( + ![START_CONVERSATION, STEP_ACTION, 'USER_INPUT_UPDATE'].includes( action.type ) ) @@ -76,7 +76,7 @@ export let reduceSteps = (tracker, flatRules, answerSource) => ( situationWithDefaults(state) ) - if (action.type === '@@redux-form/CHANGE') { + if (action.type === 'USER_INPUT_UPDATE') { return { ...state, analysis, situationGate: situationWithDefaults(state) } } From dc4500d6ebdd1dd646c572e8fa8f4b5f4272a56c Mon Sep 17 00:00:00 2001 From: mama Date: Wed, 17 Jan 2018 18:34:58 +0100 Subject: [PATCH 04/15] =?UTF-8?q?Suggestions=20vraiment=20ins=C3=A9r=C3=A9?= =?UTF-8?q?es=20au=20clic=20seulement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/components/conversation/Input.js | 25 +++++++++++-------- .../components/conversation/conversation.css | 7 +++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/source/components/conversation/Input.js b/source/components/conversation/Input.js index 50162c065..6e6a77b94 100644 --- a/source/components/conversation/Input.js +++ b/source/components/conversation/Input.js @@ -8,7 +8,7 @@ import SendButton from './SendButton' @FormDecorator('input') export default class Input extends Component { state = { - hoverSuggestion: null + lastValue: '' } render() { let { @@ -20,7 +20,6 @@ export default class Input extends Component { answerSuffix = valueType.suffix, suffixed = answerSuffix != null, inputError = dirty && error, - { hoverSuggestion } = this.state, submitDisabled = !dirty || inputError return ( @@ -33,7 +32,6 @@ export default class Input extends Component { }} type="text" {...input} - value={hoverSuggestion != null ? hoverSuggestion : input.value} className={classnames({ suffixed })} id={'step-' + dottedName} {...attributes} @@ -104,7 +102,7 @@ export default class Input extends Component { ) } renderSuggestions(themeColours) { - let { setFormValue, submit, suggestions, inverted } = this.props.stepProps + let { setFormValue, suggestions, inverted } = this.props.stepProps if (!suggestions || inverted) return null return ( @@ -114,16 +112,21 @@ export default class Input extends Component { {toPairs(suggestions).map(([text, value]) => (
  • - setFormValue('' + value) && submit() && e.preventDefault() + onClick={() => { + this.setState({ lastValue: null }) + setFormValue('' + value) + }} + onMouseOver={() => { + this.setState({ lastValue: this.props.input.value }) + setFormValue('' + value) + }} + onMouseOut={() => + this.state.lastValue != null && + setFormValue('' + this.state.lastValue) } - onMouseOver={() => this.setState({ hoverSuggestion: value })} - onMouseOut={() => this.setState({ hoverSuggestion: null })} style={{ color: themeColours.textColourOnWhite }} > - - {text} - + {text}
  • ))} diff --git a/source/components/conversation/conversation.css b/source/components/conversation/conversation.css index 87623988d..f4a67d11c 100644 --- a/source/components/conversation/conversation.css +++ b/source/components/conversation/conversation.css @@ -315,7 +315,7 @@ font-style: italic; float: right; clear: right; - font-size: 70%; + font-size: 75%; color: #222; } .step .inputSuggestions ul { @@ -326,11 +326,12 @@ } .step .inputSuggestions li { } -.step .inputSuggestions a { +.step .inputSuggestions span { padding: 0.1em 0.9em; font-weight: 600; + cursor: pointer; } -.step .inputSuggestions a:hover { +.step .inputSuggestions span:hover { text-decoration: none; } From 20d9c98c95d37d910c9c2a19b5ef18d871ce9038 Mon Sep 17 00:00:00 2001 From: mama Date: Wed, 17 Jan 2018 19:12:54 +0100 Subject: [PATCH 05/15] =?UTF-8?q?Introduction=20du=20bouton=20valider=20da?= =?UTF-8?q?ns=20les=20questions=20=C3=A0=20choix=20multiple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pour pouvoir comparer l'impact de deux réponses sans devoir revenir en arrière --- source/components/conversation/Question.js | 43 +++++++++++++--------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/source/components/conversation/Question.js b/source/components/conversation/Question.js index b55fd4c52..207e9b0d4 100644 --- a/source/components/conversation/Question.js +++ b/source/components/conversation/Question.js @@ -4,7 +4,7 @@ import { answer, answered } from './userAnswerButtonStyle' import HoverDecorator from '../HoverDecorator' import Explicable from './Explicable' import { pipe, split, reverse, reduce, is } from 'ramda' - +import SendButton from './SendButton' /* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans une liste, ou une liste de listes. Les données @choices sont un arbre de type: - nom: motif CDD # La racine, unique, qui formera la Question. Ses enfants sont les choix possibles @@ -21,26 +21,33 @@ import { pipe, split, reverse, reduce, is } from 'ramda' */ -let dottedNameToObject = pipe( - split(' . '), - reverse, - reduce((memo, next) => ({ [next]: memo }), 'oui') -) - // FormDecorator permet de factoriser du code partagé par les différents types de saisie, // dont Question est un example @FormDecorator('question') export default class Question extends Component { render() { - let { stepProps: { choices } } = this.props + let { + stepProps: { choices }, + themeColours, + stepProps: { submit } + } = this.props + let choiceElements = is(Array)(choices) + ? this.renderBinaryQuestion() + : this.renderChildren(choices) - if (is(Array)(choices)) return this.renderBinaryQuestion() - else return this.renderChildren(choices) + return ( + <> + {choiceElements} + + + ) } renderBinaryQuestion() { let { input, // vient de redux-form - stepProps: { submit, choices }, + stepProps: { submit, choices, setFormValue }, themeColours } = this.props @@ -49,7 +56,7 @@ export default class Question extends Component { {choices.map(({ value, label }) => ( ))} @@ -62,7 +69,7 @@ export default class Question extends Component { themeColours } = this.props, { name } = input, - { submit } = stepProps, + { submit, setFormValue } = stepProps, // seront stockées ainsi dans le state : // [parent object path]: dotted name relative to parent relativeDottedName = radioDottedName => @@ -79,7 +86,8 @@ export default class Question extends Component { input, submit, themeColours, - dottedName: null + dottedName: null, + setFormValue }} /> @@ -101,7 +109,8 @@ export default class Question extends Component { dottedName, input, submit, - themeColours + themeColours, + setFormValue }} /> @@ -121,7 +130,7 @@ let RadioLabel = props => ( @HoverDecorator class RadioLabelContent extends Component { render() { - let { value, label, input, submit, hover, themeColours } = this.props, + let { value, label, input, hover, themeColours, setFormValue } = this.props, // value = when(is(Object), prop('value'))(choice), labelStyle = Object.assign( value === input.value || hover @@ -136,7 +145,7 @@ class RadioLabelContent extends Component { setFormValue(value)} value={value} checked={value === input.value ? 'checked' : ''} /> From 562e26639dfa89d6022955a923eeefabfe65fe3a Mon Sep 17 00:00:00 2001 From: mama Date: Thu, 18 Jan 2018 18:58:15 +0100 Subject: [PATCH 06/15] =?UTF-8?q?:art:=20Am=C3=A9lioration=20du=20bouton?= =?UTF-8?q?=20valider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/components/conversation/SendButton.js | 2 +- .../components/conversation/conversation.css | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/source/components/conversation/SendButton.js b/source/components/conversation/SendButton.js index c995eb547..f198e074d 100644 --- a/source/components/conversation/SendButton.js +++ b/source/components/conversation/SendButton.js @@ -21,7 +21,7 @@ export default class SendButton extends Component { onClick={this.getAction()} > valider - +