From 092947a14536248c898fa23d71ae3aa54a0a6349 Mon Sep 17 00:00:00 2001 From: Mael Thomas Date: Wed, 22 Mar 2017 18:18:37 +0100 Subject: [PATCH] =?UTF-8?q?WIP=20:gear:=20impl=C3=A9mentation=20du=20retou?= =?UTF-8?q?r=20en=20arri=C3=A8re=20(brut=20visuellement)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reste : corriger le fix dans reducers Bonus : brouillons d'icônes --- finsvg.svg | 87 ++++++ icônes-CDD.svg | 203 +++++++++++++ source/actions.js | 4 +- source/components/CDD.js | 19 +- .../components/conversation/FormDecorator.js | 27 +- source/engine/conversation.js | 1 - source/engine/rules.js | 2 +- source/reducers.js | 270 +++++++++--------- 8 files changed, 457 insertions(+), 156 deletions(-) create mode 100644 finsvg.svg create mode 100644 icônes-CDD.svg diff --git a/finsvg.svg b/finsvg.svg new file mode 100644 index 000000000..cf25a7981 --- /dev/null +++ b/finsvg.svg @@ -0,0 +1,87 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + FIN + + diff --git a/icônes-CDD.svg b/icônes-CDD.svg new file mode 100644 index 000000000..3224e02d9 --- /dev/null +++ b/icônes-CDD.svg @@ -0,0 +1,203 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + https://thenounproject.com/search/?q=job+search&i=873829 + + + + FUSÉE ? + + + https://thenounproject.com/search/?q=lesson&i=118285 + + + diff --git a/source/actions.js b/source/actions.js index 43365e634..d55637aaa 100644 --- a/source/actions.js +++ b/source/actions.js @@ -2,8 +2,8 @@ // The state keeps track of which of them have been submitted // The user can also come back to one of his answers and edit it export const STEP_ACTION = 'STEP_ACTION' -export function stepAction(name, newState) { - return {type: STEP_ACTION, name, newState} +export function stepAction(name, step) { + return {type: STEP_ACTION, name, step} } export const START_CONVERSATION = 'START_CONVERSATION' diff --git a/source/components/CDD.js b/source/components/CDD.js index ce0b69a02..94d02563e 100644 --- a/source/components/CDD.js +++ b/source/components/CDD.js @@ -14,7 +14,8 @@ let situationSelector = formValueSelector('conversation') @connect( state => ({ situation: variableName => situationSelector(state, variableName), - steps: state.submittedSteps.concat(state.steps), + foldedSteps: state.foldedSteps, + unfoldedSteps: state.unfoldedSteps, themeColours: state.themeColours, analysedSituation: state.analysedSituation, }), @@ -28,12 +29,22 @@ export default class CDD extends Component { this.props.startConversation() } render() { - let {steps, situation} = this.props + let {foldedSteps, unfoldedSteps, situation} = this.props - let conversation = steps.map(step => ( - + let conversation = foldedSteps.map(step => + ).concat(unfoldedSteps.map(step => )) + return (
diff --git a/source/components/conversation/FormDecorator.js b/source/components/conversation/FormDecorator.js index a425e2af7..817839ee3 100644 --- a/source/components/conversation/FormDecorator.js +++ b/source/components/conversation/FormDecorator.js @@ -19,7 +19,7 @@ export var FormDecorator = formType => RenderField => themeColours: state.themeColours }), dispatch => ({ - stepAction: (name, newState) => dispatch(stepAction(name, newState)), + stepAction: (name, step) => dispatch(stepAction(name, step)), setFormValue: (field, value) => dispatch(change('conversation', field, value)), pointOutObjectives: objectives => dispatch({type: POINT_OUT_OBJECTIVES, objectives}) }) @@ -33,11 +33,13 @@ export var FormDecorator = formType => RenderField => stepAction, themeColours, setFormValue, - pointOutObjectives + pointOutObjectives, + /* Une étape déjà répondue est marquée 'folded'. Dans ce dernier cas, un résumé + de la réponse est affiché */ + unfolded } = this.props, { name, - visible, 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, // formerly in conversation-steps valueType, @@ -48,20 +50,10 @@ export var FormDecorator = formType => RenderField => helpText, suggestions, subquestion, - objectives + objectives, } = this.props.step - this.step = this.props.step - /* La saisie peut être cachée car ce n'est pas encore son tour, - ou parce qu'elle a déjà été remplie. Dans ce dernier cas, un résumé - de la réponse est affiché */ - let stepState = this.step.state, - completed = stepState && stepState != 'editing', - unfolded = !completed - - if (!visible) return null - /* Nos propriétés personnalisées à envoyer au RenderField. Elles sont regroupées dans un objet précis pour pouvoir être enlevées des props passées à ce dernier, car React 15.2 n'aime pas les attributes inconnus @@ -73,7 +65,7 @@ export var FormDecorator = formType => RenderField => optionsURL, /* Select component's data source */ possibleChoice, /* RhetoricalQuestion component's only choice :'-( */ //TODO hack, enables redux-form/CHANGE to update the form state before the traverse functions are run - submit: () => setTimeout(() => stepAction(name, 'filled'), 1), + submit: () => setTimeout(() => stepAction('fold', name), 1), setFormValue: value => setFormValue(name, value), valueType, suggestions, @@ -85,7 +77,7 @@ export var FormDecorator = formType => RenderField => return (
pointOutObjectives(objectives)} onMouseOut={() => pointOutObjectives([])}> {this.state.helpVisible && this.renderHelpBox(helpText)} @@ -142,9 +134,8 @@ export var FormDecorator = formType => RenderField => {this.props.step.title} {answer} - + stepAction('unfold', name)}> stepAction(name, 'editing')} className="fa fa-pencil-square-o" aria-hidden="true"> diff --git a/source/engine/conversation.js b/source/engine/conversation.js index d20b14d1f..ef0ac5b85 100644 --- a/source/engine/conversation.js +++ b/source/engine/conversation.js @@ -11,7 +11,6 @@ export let constructStepMeta = ({titre, question, subquestion, dottedName, name} lightBackground={true} />, title: titre || name, - dependencyOfVariables: ['chai pas'], subquestion, // Legacy properties : diff --git a/source/engine/rules.js b/source/engine/rules.js index 7f4277610..57ccab637 100644 --- a/source/engine/rules.js +++ b/source/engine/rules.js @@ -128,7 +128,7 @@ let collectNodeMissingVariables = target => (root, source=root, results=[]) => { } -export let collectMissingVariables = (groupMethod='groupByMissingVariable', analysedSituation) => +export let collectMissingVariables = (groupMethod='groupByMissingVariable') => analysedSituation => R.pipe( R.unless(R.is(Array), R.of), R.chain( v => diff --git a/source/reducers.js b/source/reducers.js index 84d86a590..61b901255 100644 --- a/source/reducers.js +++ b/source/reducers.js @@ -43,6 +43,143 @@ function pointedOutObjectives(state=[], {type, objectives}) { } } +let handleSteps = (state, action) => { + + let returnObject = { + ...state, + analysedSituation: analyse(state) + } + if (action.type == START_CONVERSATION) { + return { + ...returnObject, + unfoldedSteps: buildNextSteps(returnObject.analysedSituation) + } + } + if (action.type == STEP_ACTION && action.name == 'fold') { + return { + ...returnObject, + foldedSteps: [...state.foldedSteps, R.head(state.unfoldedSteps)], + unfoldedSteps: buildNextSteps(returnObject.analysedSituation) + } + } + if (action.type == STEP_ACTION && action.name == 'unfold') { + let stepFinder = R.propEq('name', action.step), + foldedSteps = R.pipe( + R.splitWhen(stepFinder), + R.head + )(state.foldedSteps) + console.log('foldedSteps', foldedSteps) + return { + ...returnObject, + foldedSteps, + unfoldedSteps: [R.find(stepFinder)(state.foldedSteps)] + } + } + return state +} + +let analyse = R.pipe( + situationGate, + // une liste des objectifs de la simulation (des 'rules' aussi nommées 'variables') + analyseSituation +) + +let missingVariables + +let buildNextSteps = R.pipe( + /* + on collecte les variables manquantes : celles qui sont nécessaires pour + remplir les objectifs de la simulation (calculer des cotisations) mais qui n'ont pas + encore été renseignées + + TODO perf : peut-on le faire en même temps que l'on traverse l'AST ? + Oui sûrement, cette liste se complète en remontant l'arbre. En fait, on le fait déjà pour nodeValue, + et quand nodeValue vaut null, c'est qu'il y a des missingVariables ! Il suffit donc de remplacer les + null par un tableau, et d'ailleurs utiliser des fonction d'aide pour mutualiser ces tests. + + missingVariables: {variable: [objectives]} + */ + R.path(['formule', 'explanation', 'explanation']), + analysedSituation => { + //TODO temporary fix + missingVariables = collectMissingVariables('groupByMissingVariable')(analysedSituation) + return missingVariables + }, + R.keys, + /* + Certaines variables manquantes peuvent être factorisées dans des groupes. + Par exemple, au lieu de : + + q1: "Pensez vous porlonger le CDD en CDI", + r1: Oui | Non + q2: "Pensez-vous qu'une rupture pour faute grave est susceptible d'arriver" + r2: Oui | Non + + on préfère : + + q: "Pensez-vous être confronté à l'un de ces événements ?" + r: Prolongation du CDD en CDI | Rupture pour faute grave + */ + R.groupBy(parentName), + // on va maintenant construire la liste des composants React qui afficheront les questions à l'utilisateur pour que l'on obtienne les variables manquantes + R.pipe( + R.mapObjIndexed((variables, group) => + R.pipe( + findGroup, + R.cond([ + // Pas de groupe trouvé : ce sont des variables individuelles + [R.isNil, () => variables.map(dottedName => { + let rule = findRuleByDottedName(dottedName) + return Object.assign(constructStepMeta(rule), + rule.format == 'nombre positif' || + rule.format == 'période' ? + { + component: Input, + valueType: rule.format == 'nombre positif' ? euro : months, + attributes: { + inputMode: 'numeric', + placeholder: 'votre réponse' + }, + suggestions: rule.suggestions + } : { + component: Question, + choices: [ + {value: 'non', label: 'Non'}, + {value: 'oui', label: 'Oui'} + ] + }, + { + objectives: missingVariables[dottedName] + } + )})], + [R.T, group => do { + let possibilities = group['une possibilité'] + Object.assign( + constructStepMeta(group), + { + component: Question, + choices: + possibilities.concat( + group['langue au chat possible'] === 'oui' ? + [{value: '_', label: 'Aucun'}] : [] + ) + }, + { + objectives: R.pipe( + R.chain(v => missingVariables[group.dottedName + ' . ' + v]), + R.uniq() + )(possibilities) + } + )}] + ]) + )(group) + ), + R.values, + R.unnest + ) +) + + export default reduceReducers( combineReducers({ // this is handled by redux-form, pas touche ! @@ -50,9 +187,9 @@ export default reduceReducers( /* Have forms been filled or ignored ? false means the user is reconsidering its previous input */ - steps: (steps=[]) => steps, + foldedSteps: (steps=[]) => steps, + unfoldedSteps: (steps=[]) => steps, - submittedSteps: (steps=[]) => steps, analysedSituation: (state = []) => state, @@ -63,132 +200,5 @@ export default reduceReducers( pointedOutObjectives }), // cross-cutting concerns because here `state` is the whole state tree - (state, action) => { - if (action.type == STEP_ACTION || action.type == START_CONVERSATION) { - - // pour débugguer : - window.situationGate = situationGate(state) - - let newlySubmittedSteps = - action.newState == 'filled' - ? [{ - ...state.steps.find(s => s.name === action.name), - state: 'filled' - }] - : [] - - - // on calcule la prochaine étape, à ajouter sur la pile - let - // une liste des objectifs de la simulation (des 'rules' aussi nommées 'variables') - analysedSituation = analyseSituation( - situationGate(state) - ), - - // y = console.log('analysedSituation',analysedSituation), - - /* - on collecte les variables manquantes : celles qui sont nécessaires pour - remplir les objectifs de la simulation (calculer des cotisations) mais qui n'ont pas - encore été renseignées - - TODO perf : peut-on le faire en même temps que l'on traverse l'AST ? - Oui sûrement, cette liste se complète en remontant l'arbre. En fait, on le fait déjà pour nodeValue, - et quand nodeValue vaut null, c'est qu'il y a des missingVariables ! Il suffit donc de remplacer les - null par un tableau, et d'ailleurs utiliser des fonction d'aide pour mutualiser ces tests. - - missingVariables: {variable: [objectives]} - */ - - missingVariables = collectMissingVariables('groupByMissingVariable', R.path(['formule', 'explanation', 'explanation'])(analysedSituation)), - // yy = console.log('missingVariables',missingVariables), - - missingVariablesList = R.keys(missingVariables), - /* - Certaines variables manquantes peuvent être factorisées dans des groupes. - Par exemple, au lieu de : - - q1: "Pensez vous porlonger le CDD en CDI", - r1: Oui | Non - q2: "Pensez-vous qu'une rupture pour faute grave est susceptible d'arriver" - r2: Oui | Non - - on préfère : - - q: "Pensez-vous être confronté à l'un de ces événements ?" - r: Prolongation du CDD en CDI | Rupture pour faute grave - */ - groups = R.groupBy( - parentName - )(missingVariablesList), - - // on va maintenant construire la liste des composants React qui afficheront les questions à l'utilisateur pour que l'on obtienne les variables manquantes - steps = R.pipe( - R.mapObjIndexed((variables, group) => - R.pipe( - findGroup, - R.cond([ - // Pas de groupe trouvé : ce sont des variables individuelles - [R.isNil, () => variables.map(dottedName => { - let rule = findRuleByDottedName(dottedName) - return Object.assign(constructStepMeta(rule), - rule.format == 'nombre positif' || - rule.format == 'période' ? - { - component: Input, - valueType: rule.format == 'nombre positif' ? euro : months, - attributes: { - inputMode: 'numeric', - placeholder: 'votre réponse' - }, - suggestions: rule.suggestions - } : { - component: Question, - choices: [ - {value: 'non', label: 'Non'}, - {value: 'oui', label: 'Oui'} - ] - }, - { - objectives: missingVariables[dottedName] - } - )})], - [R.T, group => do { - let possibilities = group['une possibilité'] - Object.assign( - constructStepMeta(group), - { - component: Question, - choices: - possibilities.concat( - group['langue au chat possible'] === 'oui' ? - [{value: '_', label: 'Aucun'}] : [] - ) - }, - { - objectives: R.pipe( - R.chain(v => missingVariables[group.dottedName + ' . ' + v]), - R.uniq() - )(possibilities) - } - )}] - ]) - )(group) - ), - R.values, - R.unnest - )(groups) - - return { - ...state, - steps, - submittedSteps: state.submittedSteps.concat(newlySubmittedSteps), - analysedSituation - } - - } else { - return state - } - - } + handleSteps )