⚙️ 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
parent
0ef19ed6dc
commit
b11f2ba0b0
|
@ -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 {
|
||||
|
|
|
@ -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> {!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> {!showValues ? 'Injecter votre situation' : 'Cacher votre situation'}
|
||||
</button>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let JSONView = ({o, rootKey}) => (
|
||||
<div className="json">
|
||||
<JSONTree
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)}>←
|
||||
{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)}>
|
||||
←
|
||||
{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>
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export let capitalise0 = name => name[0].toUpperCase() + name.slice(1)
|
Loading…
Reference in New Issue