From b11f2ba0b02ef593a8035ec64d0f064ed6f8f5cf Mon Sep 17 00:00:00 2001 From: Mael Thomas Date: Mon, 27 Mar 2017 18:37:11 +0200 Subject: [PATCH] :gear: Ajout d'un panneau d'explication des termes de l'algorithme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sous forme d'un décorateur de composants Un seul terme implémenté --- source/components/Rule.css | 26 ++++- source/components/Rule.js | 103 ++++++++++++------ .../components/conversation/FormDecorator.js | 6 +- source/engine/known-mecanisms.yaml | 54 +++++++-- source/engine/traverse-common-jsx.js | 65 +++++------ source/engine/traverse.js | 3 +- source/utils.js | 1 + 7 files changed, 184 insertions(+), 74 deletions(-) create mode 100644 source/utils.js diff --git a/source/components/Rule.css b/source/components/Rule.css index 13d8a7b08..1702828ea 100644 --- a/source/components/Rule.css +++ b/source/components/Rule.css @@ -148,9 +148,31 @@ background: rgb(74, 137, 220); color: white; border: none; - font-size: 120%; - padding: .5em; + font-size: 100%; + padding: .4em; margin: auto; + margin-left: 3em; +} + +#algorithm { + margin-top: 3em; + width: 60%; +} + +.dictionaryWrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; +} +.dictionaryPanel { + width: 35%; + border: 1px dashed #aaa; + margin: 1em; + padding: .6em 1em; + min-height: 6em; + border-radius: .3em; + } #rule-rules section { diff --git a/source/components/Rule.js b/source/components/Rule.js index 4496f757d..ef8cbe149 100644 --- a/source/components/Rule.js +++ b/source/components/Rule.js @@ -1,4 +1,5 @@ import React, { Component } from 'react' +import ReactDOM from 'react-dom' // import {findRuleByName} from '../engine/rules.js' import './Rule.css' import JSONTree from 'react-json-tree' @@ -10,6 +11,8 @@ import {START_CONVERSATION} from '../actions' import classNames from 'classnames' import destinataires from '../../règles/destinataires/destinataires.yaml' import references from '../../règles/références/références.yaml' +import {capitalise0} from '../utils' +import knownMecanisms from '../engine/known-mecanisms.yaml' // situationGate function useful for testing : let testingSituationGate = v => // eslint-disable-line no-unused-vars @@ -26,9 +29,6 @@ let testingSituationGate = v => // eslint-disable-line no-unused-vars }) ) export default class Rule extends Component { - state = { - showValues: false - } componentDidMount() { // C'est ici que la génération du formulaire, et donc la traversée des variables commence this.props.startConversation() @@ -51,8 +51,7 @@ export default class Rule extends Component { } let - situationExists = !R.isEmpty(form), - showValues = situationExists && this.state.showValues + situationExists = !R.isEmpty(form) let destinataire = R.path(['attributs', 'destinataire'])(rule), destinataireData = destinataires[destinataire] @@ -63,7 +62,7 @@ export default class Rule extends Component {

{rule.type} - {name} + {capitalise0(name)}

@@ -87,31 +86,7 @@ export default class Rule extends Component { {this.renderReferences(rule)}
-
- { do { - let [,cond] = - R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || [] - cond != null && -
-

Conditions de déclenchement

- {cond.jsx} -
- }} -
-

Calcul

- {rule['formule'].jsx} -
- {situationExists && - - } -
- - - {/*
-						
-				
*/} + ) } @@ -147,6 +122,72 @@ export default class Rule extends Component { } } +// On ajoute à la section la possibilité d'ouvrir à droite un panneau d'explication des termes. +// Il suffit à la section d'appeler une fonction fournie en lui donnant du JSX +// Ne pas oublier de réduire la largeur de la section pour laisser de la place au dictionnaire. +let AttachDictionary = dictionary => Decorated => + class extends React.Component { + state = { + explanation: null + } + explain = explanation => + this.setState({explanation}) + componentDidMount() { + let decoratedNode = ReactDOM.findDOMNode(this.decorated) + decoratedNode.addEventListener('click', e => { + let term = e.target.dataset['termDefinition'] + this.explain(R.path([term, 'description'], dictionary)) + }) + } + render(){ + return ( +
+ this.decorated = decorated} {...this.props} explain={this.explain}/> + {this.state.explanation && +
+ {this.state.explanation} +
+ } +
+ ) + } + } + +@AttachDictionary(knownMecanisms) +class Algorithm extends React.Component { + state = { + showValues: false + } + render(){ + let {rule, situationExists, explain} = this.props, + showValues = situationExists && this.state.showValues + return ( +
+
+ { do { + let [,cond] = + R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || [] + cond != null && +
+

Conditions de déclenchement

+ {cond.jsx} +
+ }} +
+

Calcul

+ {rule['formule'].jsx} +
+
+ {situationExists &&
+ +
} +
+ ) + } +} + let JSONView = ({o, rootKey}) => (
RenderField => name, stepAction, answer, - themeColours + themeColours, + step: {title} } = this.props, ignored = this.step.state === 'ignored' return (
- {this.props.step.title} + {capitalise0(title)} {answer} diff --git a/source/engine/known-mecanisms.yaml b/source/engine/known-mecanisms.yaml index 76d5e815f..0274d5018 100644 --- a/source/engine/known-mecanisms.yaml +++ b/source/engine/known-mecanisms.yaml @@ -1,7 +1,47 @@ -- l'une de ces conditions -- toutes ces conditions -- logique numérique -- taux -- multiplication -- le maximum de -- somme +l'une de ces conditions: + description: | + C'est un 'ou' logique. + Contient une liste de conditions. + Renvoie vrai si l'une des conditions sont vraies. +toutes ces conditions: + description: | + C'est un 'et' logique. + Contient une liste de conditions. + Renvoie vrai si toutes les conditions vraies. + +logique numérique: + description: | + Contient une liste de couples condition-conséquence. + Un par un, si la condition est vraie, alors on choisit la conséquence. + Cette conséquence peut elle-même être un mécanisme "logique numérique" ou plus simplement un taux. + Si aucune condition n'est vraie, alors ce mécanisme renvoie implilcitement 'non applicable' (ce qui peut se traduire par la valeur 0 si nous sommes dans un contexte numérique). + +taux: + description: | + C'est tout simplement une valeur numérique exprimée en pourcentage. + +multiplication: + description: | + C'est une multiplication un peu améliorée, très utile pour exprimer les cotisations, souvent linéaires. + Sa propriété 'assiette' est multipliée par un pourcentage, 'taux', ou par un 'facteur' quand ce nom est plus approprié. + + La multiplication peut être plafonnée : ce plafond sépare l'assiette en deux, et la partie au-dessus du plafond est tout simplement ignorée. + Dans ce cas, elle se comporte comme une barème en taux marginaux à deux tranches, la deuxième au taux nul et allant de 'plafond' à l'infini. + +le maximum de: + description: | + Calcule le maximum de la liste de propositions fournie. + Ces propositions doivent avoir un mécanisme de calcul ou être une valeur numérique. + Il est conseillé de renseigner une description de chaque propositions par exemple quand elles représentent des méthodes de calcul alternatives parmi lesquelles il faut en choisir une. + +somme: + description: | + C'est tout simplement la somme de chaque terme de la liste. + + +# à venir +# barème: +# description: | +# C'est un barème en taux marginaux, mécanisme de calcul connu pour l'impôt sur le revenu. +# L'assiette est divisée en plusieurs tranches, multipliées par un taux prope à chaque tranche. +# Les tranches sont très souvent exprimées sous forme de facteurs (par exemple [1, 2, 4]) d'une variable, couramment le plafonde de la sécurité sociale. diff --git a/source/engine/traverse-common-jsx.js b/source/engine/traverse-common-jsx.js index 6af3e7fb4..57134e4df 100644 --- a/source/engine/traverse-common-jsx.js +++ b/source/engine/traverse-common-jsx.js @@ -2,40 +2,43 @@ import React from 'react' import R from 'ramda' import classNames from 'classnames' -let treatValue = data => data == null ? - '?' - : ( R.is(Number)(data) ? - Math.round(data) - : ( data ? 'oui' : 'non') - ) - -let NodeValue = ({data}) => - ←  - {treatValue(data)} - +let treatValue = data => + data == null + ? '?' + : R.is(Number)(data) ? Math.round(data) : data ? 'oui' : 'non' +let NodeValue = ({data}) => ( + + ←  + {treatValue(data)} + +) // Un élément du graphe de calcul qui a une valeur interprétée (à afficher) -export let Node = ({classes, name, value, child}) => -
- {name && - - {name} - - +export class Node extends React.Component { + render() { + let {classes, name, value, child, termDefinition} = this.props + + return ( +
+ {name && + + {name} + + } + {child} + {!name && } +
+ ) } - {child} - {!name && } -
- - +} // Un élément du graphe de calcul qui a une valeur interprétée (à afficher) -export let Leaf = ({classes, name, value}) => - - {name && - - {name} - - } - +export let Leaf = ({classes, name, value}) => ( + + {name && + + {name} + } + +) diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 67c4b02b1..d1ba3b0ce 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -219,7 +219,7 @@ let treat = (situationGate, rule) => rawNode => { throw ' doit être un Number, String ou Object' } - let mecanisms = R.intersection(R.keys(rawNode), knownMecanisms) + let mecanisms = R.intersection(R.keys(rawNode), R.keys(knownMecanisms)) if (mecanisms.length != 1) throw 'OUPS !' let k = R.head(mecanisms), v = rawNode[k] @@ -248,6 +248,7 @@ let treat = (situationGate, rule) => rawNode => { )(v) return {...result, jsx: name[0].toUpperCase() + name.slice(1)