⚙️ Ajout d'un panneau d'explication des termes de l'algorithme

Sous forme d'un décorateur de composants
Un seul terme implémenté
pull/6/head
Mael Thomas 2017-03-27 18:37:11 +02:00
parent 0ef19ed6dc
commit b11f2ba0b0
7 changed files with 184 additions and 74 deletions

View File

@ -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 {

View File

@ -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 {
<PageTypeIcon type="comprendre"/>
<h1>
<span className="rule-type">{rule.type}</span>
<span className="rule-name">{name}</span>
<span className="rule-name">{capitalise0(name)}</span>
</h1>
<section id="rule-meta">
<div id="meta-paragraph">
@ -87,31 +86,7 @@ export default class Rule extends Component {
{this.renderReferences(rule)}
</div>
</section>
<section id="rule-rules" className={classNames({showValues})}>
{ do {
let [,cond] =
R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || []
cond != null &&
<section id="declenchement">
<h2>Conditions de déclenchement</h2>
{cond.jsx}
</section>
}}
<section id="formule">
<h2>Calcul</h2>
{rule['formule'].jsx}
</section>
{situationExists &&
<button id="showValues" onClick={() => this.setState({showValues: !this.state.showValues})}>
<i className="fa fa-rocket" aria-hidden="true"></i> &nbsp;{!showValues ? 'Injecter votre situation' : 'Cacher votre situation'}
</button>
}
</section>
{/* <pre>
<JSONView data={rule} />
</pre> */}
<Algorithm {...{rule, situationExists}}/>
</div>
)
}
@ -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 (
<div className="dictionaryWrapper">
<Decorated ref={decorated => this.decorated = decorated} {...this.props} explain={this.explain}/>
{this.state.explanation &&
<div className="dictionaryPanel">
{this.state.explanation}
</div>
}
</div>
)
}
}
@AttachDictionary(knownMecanisms)
class Algorithm extends React.Component {
state = {
showValues: false
}
render(){
let {rule, situationExists, explain} = this.props,
showValues = situationExists && this.state.showValues
return (
<div id="algorithm">
<section id="rule-rules" className={classNames({showValues})}>
{ do {
let [,cond] =
R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || []
cond != null &&
<section id="declenchement">
<h2>Conditions de déclenchement</h2>
{cond.jsx}
</section>
}}
<section id="formule">
<h2>Calcul</h2>
{rule['formule'].jsx}
</section>
</section>
{situationExists && <div>
<button id="showValues" onClick={() => this.setState({showValues: !this.state.showValues})}>
<i className="fa fa-rocket" aria-hidden="true"></i> &nbsp;{!showValues ? 'Injecter votre situation' : 'Cacher votre situation'}
</button>
</div>}
</div>
)
}
}
let JSONView = ({o, rootKey}) => (
<div className="json">
<JSONTree

View File

@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import {Field, change} from 'redux-form'
import {stepAction, POINT_OUT_OBJECTIVES} from '../../actions'
import StepAnswer from './StepAnswer'
import {capitalise0} from '../../utils'
/*
This higher order component wraps "Form" components (e.g. Question.js), that represent user inputs,
@ -127,13 +128,14 @@ export var FormDecorator = formType => RenderField =>
name,
stepAction,
answer,
themeColours
themeColours,
step: {title}
} = this.props,
ignored = this.step.state === 'ignored'
return (
<div className="foldedQuestion">
<span className="borderWrapper">
<span className="title">{this.props.step.title}</span>
<span className="title">{capitalise0(title)}</span>
<span className="answer">
{answer}
</span>

View File

@ -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.

View File

@ -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}) =>
<span className={"situationValue " + treatValue(data)}>&nbsp;
{treatValue(data)}
</span>
let treatValue = data =>
data == null
? '?'
: R.is(Number)(data) ? Math.round(data) : data ? 'oui' : 'non'
let NodeValue = ({data}) => (
<span className={'situationValue ' + treatValue(data)}>
&nbsp;
{treatValue(data)}
</span>
)
// Un élément du graphe de calcul qui a une valeur interprétée (à afficher)
export let Node = ({classes, name, value, child}) =>
<div className={classNames(classes, 'node')}>
{name &&
<span className="nodeHead">
<span className="name">{name}</span>
<NodeValue data={value}/>
</span>
export class Node extends React.Component {
render() {
let {classes, name, value, child, termDefinition} = this.props
return (
<div className={classNames(classes, 'node')}>
{name &&
<span className="nodeHead">
<span className="name" data-term-definition={termDefinition} >{name}</span>
<NodeValue data={value} />
</span>}
{child}
{!name && <NodeValue data={value} />}
</div>
)
}
{child}
{!name && <NodeValue data={value}/>}
</div>
}
// Un élément du graphe de calcul qui a une valeur interprétée (à afficher)
export let Leaf = ({classes, name, value}) =>
<span className={classNames(classes, 'leaf')}>
{name &&
<span className="nodeHead">
<span className="name">{name}<NodeValue data={value}/></span>
</span>
}
</span>
export let Leaf = ({classes, name, value}) => (
<span className={classNames(classes, 'leaf')}>
{name &&
<span className="nodeHead">
<span className="name">{name}<NodeValue data={value} /></span>
</span>}
</span>
)

View File

@ -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: <Node
termDefinition={result.name}
classes="mecanism list"
name={result.name}
value={result.nodeValue}

1
source/utils.js Normal file
View File

@ -0,0 +1 @@
export let capitalise0 = name => name[0].toUpperCase() + name.slice(1)