Simplifie l'interface de la fonction makeJsx

pull/993/head
Maxime Quandalle 2020-04-20 11:46:13 +02:00 committed by Johan Girod
parent b4b278d4cf
commit 234e12350b
27 changed files with 119 additions and 149 deletions

View File

@ -1,4 +1,3 @@
import Value from 'Components/Value'
import React, { useContext } from 'react'
import emoji from 'react-easy-emoji'
import { animated, config, useSpring } from 'react-spring'

View File

@ -1,6 +1,6 @@
import RuleLink from 'Components/RuleLink'
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
import { EvaluatedRule } from 'Engine/types'
import { EvaluatedRule, Evaluation, Types } from 'Engine/types'
import React from 'react'
import { animated, useSpring } from 'react-spring'
import { DottedName } from 'Rules'
@ -83,7 +83,7 @@ export function roundedPercentages(values: Array<number>) {
type StackedBarChartProps = {
data: Array<{
color?: string
value: number | undefined
value: Evaluation<Types>
legend: React.ReactNode
key: string
}>
@ -93,7 +93,9 @@ export function StackedBarChart({ data }: StackedBarChartProps) {
const [intersectionRef, displayChart] = useDisplayOnIntersecting({
threshold: 0.5
})
const percentages = roundedPercentages(data.map(d => d.value ?? 0))
const percentages = roundedPercentages(
data.map(d => (typeof d.value === 'number' && d.value) || 0)
)
const dataWithPercentage = data.map((data, index) => ({
...data,
percentage: percentages[index]

View File

@ -15,10 +15,13 @@
border-top: none;
}
#targetSelection .targets > li.small-target * {
font-size: 1rem;
#targetSelection .targets > li.small-target {
font-size: 85%;
}
#targetSelection .targets > li.small-target .optionTitle a {
font-weight: normal;
}
#targetSelection .targets > li.small-target {
border-top: none;
}
@ -47,9 +50,6 @@
#targetSelection .targets > li p {
margin: 0.2em 0 0;
font-style: italic;
opacity: 0.8;
line-height: 1.2rem;
}
#targetSelection li .header {
@ -60,12 +60,9 @@
display: none;
}
#targetSelection .optionTitle {
font-size: 115%;
font-weight: 600;
}
#targetSelection .optionTitle a {
color: inherit;
font-weight: bold;
text-decoration: none;
}
#targetSelection .optionTitle a:hover,
@ -74,12 +71,8 @@
}
@media (hover: none) {
#targetSelection .optionTitle {
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 0.25em;
padding: 0.05em 0.4em;
font-size: 125%;
font-weight: 400;
#targetSelection .optionTitle a {
text-decoration: underline;
}
}

View File

@ -173,7 +173,7 @@ let Header = ({ target }) => {
{hackyShowPeriod && ' ' + t('mensuel')}
</Link>
</span>
<p>{target.summary}</p>
<p className="ui__ notice">{target.summary}</p>
</span>
</span>
)

View File

@ -23,10 +23,8 @@ import {
} from './temporal'
import { ParsedRule, ParsedRules } from './types'
export let makeJsx = node =>
typeof node.jsx == 'function'
? node.jsx(node.nodeValue, node.explanation, node.unit)
: node.jsx
export let makeJsx = (node: EvaluatedNode): JSX.Element =>
typeof node.jsx == 'function' ? node.jsx(node) : <></>
export let collectNodeMissing = node => node.missingVariables || {}
@ -124,7 +122,9 @@ export const evaluateArrayWithFilter = (evaluationFilter, reducer, start) => (
export let defaultNode = nodeValue => ({
nodeValue,
// eslint-disable-next-line
jsx: nodeValue => <span className="value">{nodeValue}</span>,
jsx: ({ nodeValue }: EvaluatedNode) => (
<span className="value">{nodeValue}</span>
),
isDefault: true
})

View File

@ -3,7 +3,10 @@ import React from 'react'
import { makeJsx } from '../evaluation'
import { Node } from './common'
export default function Allègement(nodeValue, rawExplanation) {
export default function Allègement({
nodeValue,
explanations: rawExplanation
}) {
// Don't display attributes with default values
let explanation = map(k => (k && !k.isDefault ? k : null), rawExplanation)
return (

View File

@ -6,7 +6,7 @@ import './Barème.css'
import { Node, NodeValuePointer } from './common'
import { parseUnit } from 'Engine/units'
export default function Barème(nodeValue, explanation, unit) {
export default function Barème({ nodeValue, explanation, unit }) {
return (
<Node classes="mecanism barème" name="barème" value={nodeValue} unit={unit}>
<ul className="properties">

View File

@ -7,7 +7,7 @@ import { makeJsx } from '../evaluation'
import { InlineMecanism, Node } from './common'
import './Composantes.css'
let Comp = function Composantes({ nodeValue, explanation, unit }) {
export default function Composantes({ nodeValue, explanation, unit }) {
const { i18n } = useTranslation()
return (
@ -70,8 +70,3 @@ let Comp = function Composantes({ nodeValue, explanation, unit }) {
</Node>
)
}
// eslint-disable-next-line
export default (nodeValue, explanation, unit) => (
<Comp {...{ nodeValue, explanation, unit }} />
)

View File

@ -3,7 +3,7 @@ import { BarèmeAttributes, TrancheTable } from './Barème'
import './Barème.css'
import { Node } from './common'
export default function Grille(nodeValue, explanation, unit) {
export default function Grille({ nodeValue, explanation, unit }) {
return (
<Node classes="mecanism barème" name="grille" value={nodeValue} unit={unit}>
<ul className="properties">

View File

@ -3,7 +3,7 @@ import React from 'react'
import { Node } from './common'
import './InversionNumérique.css'
let Comp = function InversionNumérique({ nodeValue, explanation }) {
export default function InversionNumérique({ nodeValue, explanation }) {
return (
<Node
classes="mecanism inversionNumérique"
@ -50,8 +50,3 @@ let Comp = function InversionNumérique({ nodeValue, explanation }) {
</Node>
)
}
//eslint-disable-next-line
export default (nodeValue, explanation) => (
<Comp {...{ nodeValue, explanation }} />
)

View File

@ -4,7 +4,7 @@ import { Trans } from 'react-i18next'
import { Node } from './common'
import './InversionNumérique.css'
export default function ProductView(nodeValue, explanation, unit) {
export default function ProductView({ nodeValue, explanation, unit }) {
return (
// The rate and factor and threshold are given defaut neutral values. If there is nothing to explain, don't display them at all
<Node

View File

@ -5,7 +5,7 @@ import { Trans } from 'react-i18next'
import { DottedName } from 'Rules'
import { Node } from './common'
export default function Recalcul(nodeValue, explanation) {
export default function Recalcul({ nodeValue, explanation }) {
return (
<Node
classes="mecanism recalcul"

View File

@ -3,9 +3,8 @@ import { Trans } from 'react-i18next'
import { BarèmeAttributes, TrancheTable } from './Barème'
import './Barème.css'
import { Node, NodeValuePointer } from './common'
import { Unit } from 'Engine/units'
export default function TauxProgressif(nodeValue, explanation, unit: Unit) {
export default function TauxProgressif({ nodeValue, explanation, unit }) {
return (
<Node
classes="mecanism barème"

View File

@ -7,7 +7,7 @@ import { makeJsx } from '../evaluation'
import { InlineMecanism, Node } from './common'
import './Variations.css'
let Comp = function Variations({ nodeValue, explanation, unit }) {
export default function Variations({ nodeValue, explanation, unit }) {
let [expandedVariation, toggleVariation] = useState(null)
const { i18n } = useTranslation()
return (
@ -107,7 +107,3 @@ let Comp = function Variations({ nodeValue, explanation, unit }) {
</Node>
)
}
// eslint-disable-next-line
export default (nodeValue, explanation, unit) => (
<Comp {...{ nodeValue, explanation, unit }} />
)

View File

@ -39,17 +39,19 @@ import Recalcul from './mecanismViews/Recalcul'
import Somme from './mecanismViews/Somme'
import uniroot from './uniroot'
import { parseUnit } from './units'
import { EvaluatedRule } from './types'
export let mecanismOneOf = (recurse, k, v) => {
if (!is(Array, v)) throw new Error('should be array')
let explanation = map(recurse, v)
let jsx = (nodeValue, explanation) => (
let jsx = ({ nodeValue, explanation, unit }) => (
<Node
classes="mecanism conditions list"
name="une de ces conditions"
value={nodeValue}
unit={unit}
>
<ul>
{explanation.map((item, i) => (
@ -95,11 +97,12 @@ export let mecanismAllOf = (recurse, k, v) => {
let explanation = map(recurse, v)
let jsx = (nodeValue, explanation) => (
let jsx = ({ nodeValue, explanation, unit }) => (
<Node
classes="mecanism conditions list"
name="toutes ces conditions"
value={nodeValue}
unit={unit}
>
<ul>
{explanation.map((item, i) => (
@ -194,13 +197,15 @@ let evaluateInversion = (oldCache, situationGate, parsedRules, node) => {
const candidateNode = evaluateWithValue(x)
return (
candidateNode.nodeValue -
convertNodeToUnit(candidateNode.unit, inversedWith).nodeValue
// TODO: convertNodeToUnit migth return null or false
(convertNodeToUnit(candidateNode.unit, inversedWith)
.nodeValue as number)
)
},
node.explanation.negativeValuesAllowed ? -1000000 : 0,
100000000,
0.1, // tolerance
10 // number of iteration max
0.1,
10
)
if (nodeValue === undefined) {
@ -312,7 +317,7 @@ export let mecanismSum = (recurse, k, v) => {
return {
evaluate,
// eslint-disable-next-line
jsx: (nodeValue, explanation, unit) => (
jsx: ({ nodeValue, explanation, unit }: EvaluatedRule) => (
<Somme nodeValue={nodeValue} explanation={explanation} unit={unit} />
),
explanation,
@ -520,11 +525,12 @@ export let mecanismMax = (recurse, k, v) => {
}
let evaluate = evaluateArray(max, Number.NEGATIVE_INFINITY)
let jsx = (nodeValue, explanation) => (
let jsx = ({ nodeValue, explanation, unit }) => (
<Node
classes="mecanism list maximum"
name="le maximum de"
value={nodeValue}
unit={unit}
>
<ul>
{explanation.map((item, i) => (
@ -553,11 +559,12 @@ export let mecanismMin = (recurse, k, v) => {
let evaluate = evaluateArray(min, Infinity)
let jsx = (nodeValue, explanation) => (
let jsx = ({ nodeValue, explanation, unit }) => (
<Node
classes="mecanism list minimum"
name="le minimum de"
value={nodeValue}
unit={unit}
>
<ul>
{explanation.map((item, i) => (
@ -614,7 +621,7 @@ export let mecanismSynchronisation = (recurse, k, v) => {
return {
explanation: { ...v, API: recurse(v.API) },
evaluate,
jsx: function Synchronisation(nodeValue, explanation) {
jsx: function Synchronisation({ explanation }) {
return (
<p>
Obtenu à partir de la saisie <SimpleRuleLink rule={explanation.API} />

View File

@ -96,10 +96,7 @@ export default (recurse, k, v) => {
return {
explanation,
evaluate,
// eslint-disable-next-line
jsx: (nodeValue, explanation) => (
<MecanismRound nodeValue={nodeValue} explanation={explanation} />
),
jsx: MecanismRound,
category: 'mecanism',
name: 'arrondi',
type: 'numeric',

View File

@ -69,14 +69,7 @@ export default (recurse, k, v) => {
return {
evaluate,
// eslint-disable-next-line
jsx: (nodeValue, explanation, unit) => (
<MecanismDurée
nodeValue={nodeValue}
explanation={explanation}
unit={unit}
/>
),
jsx: MecanismDurée,
explanation,
category: 'mecanism',
name: 'Durée',

View File

@ -96,14 +96,7 @@ export default (recurse, k, v) => {
return {
evaluate,
// eslint-disable-next-line
jsx: (nodeValue, explanation, unit) => (
<MecanismEncadrement
nodeValue={nodeValue}
explanation={explanation}
unit={unit}
/>
),
jsx: MecanismEncadrement,
explanation,
category: 'mecanism',
name: 'encadrement',

View File

@ -86,7 +86,7 @@ export default (k, operatorFunction, symbol) => (recurse, k, v) => {
let [node1, node2] = explanation
let unit = inferUnit(k, [node1.unit, node2.unit])
let jsx = (nodeValue, explanation, unit) => (
let jsx = ({ nodeValue, explanation, unit }) => (
<Node classes={'inlineExpression ' + k} value={nodeValue} unit={unit}>
<span className="nodeContent">
{(explanation[0].nodeValue !== 0 ||

View File

@ -44,6 +44,7 @@ import {
mecanismSynchronisation
} from './mecanisms'
import { parseReferenceTransforms } from './parseReference'
import { EvaluatedRule } from './types'
export const parse = (rules, rule, parsedRules) => rawNode => {
if (rawNode == null) {
@ -240,7 +241,7 @@ const statelessParseFunction = {
nodeValue: v.nodeValue,
unit: v.unit,
// eslint-disable-next-line
jsx: (nodeValue, _, unit) => (
jsx: ({ nodeValue, unit }: EvaluatedRule) => (
<span className={v.type}>
{formatValue({
unit,

View File

@ -230,15 +230,13 @@ export let parseReference = (
return {
evaluate: evaluateReference(filter, rule.dottedName),
//eslint-disable-next-line react/display-name
jsx: (nodeValue, _, nodeUnit) => (
<>
<Leaf
className="variable filtered"
rule={parsedRules[dottedName]}
nodeValue={nodeValue}
unit={nodeUnit || parsedRules[dottedName]?.unit || unit}
/>
</>
jsx: ({ nodeValue, unit: nodeUnit }) => (
<Leaf
className="variable filtered"
rule={parsedRules[dottedName]}
nodeValue={nodeValue}
unit={nodeUnit || parsedRules[dottedName]?.unit || unit}
/>
),
name: partialReference,
category: 'reference',

View File

@ -85,7 +85,7 @@ export default function<Names extends string>(
parents.map(parent => {
let node = parse(rules, rule, parsedRules)(parent)
let jsx = (nodeValue, explanation) =>
let jsx = ({ nodeValue, explanation }) =>
nodeValue === null ? (
<div>Active seulement si {makeJsx(explanation)}</div>
) : nodeValue === true ? (
@ -143,7 +143,7 @@ export default function<Names extends string>(
let child = parse(rules, rule, parsedRules)(value)
let jsx = (_nodeValue, explanation) => makeJsx(explanation)
let jsx = ({ explanation }) => makeJsx(explanation)
return {
evaluate,
@ -205,7 +205,7 @@ export default function<Names extends string>(
}
},
jsx: (_nodeValue, { isDisabledBy }) => {
jsx: ({ explanation: { isDisabledBy } }) => {
return (
isDisabledBy.length > 0 && (
<>
@ -248,7 +248,7 @@ let evolveCond = (dottedName, rule, rules, parsedRules) => value => {
let child = parse(rules, rule, parsedRules)(value)
let jsx = (nodeValue, explanation, unit) => (
let jsx = ({ nodeValue, explanation, unit }) => (
<Node
classes="ruleProp mecanism cond"
name={dottedName}

View File

@ -67,6 +67,7 @@ export type EvaluatedNode<
nodeValue: Evaluation<T>
explanation?: Object
isDefault?: boolean
jsx?: (node: EvaluatedNode) => JSX.Element
missingVariables: Partial<Record<Names, number>>
} & (T extends number
? {

View File

@ -8,32 +8,32 @@
* Copyright (c) 2012 Borgar Thorsteinsson <borgar@borgar.net>
* MIT License, http://www.opensource.org/licenses/mit-license.php
*
* @param {function} function for which the root is sought.
* @param {number} the lower point of the interval to be searched.
* @param {number} the upper point of the interval to be searched.
* @param {number} the desired accuracy (convergence tolerance).
* @param {number} the maximum number of iterations.
* @param {function} func function for which the root is sought.
* @param {number} lowerLimit the lower point of the interval to be searched.
* @param {number} upperLimit the upper point of the interval to be searched.
* @param {number} errorTol the desired accuracy (convergence tolerance).
* @param {number} maxIter the maximum number of iterations.
* @returns an estimate for the root within accuracy.
*
*/
export default function uniroot(
func,
lowerLimit,
upperLimit,
errorTol,
maxIter
func: (x: number) => number,
lowerLimit: number,
upperLimit: number,
errorTol: number,
maxIter: number
) {
var a = lowerLimit,
let a = lowerLimit,
b = upperLimit,
c = a,
fa = func(a),
fb = func(b),
fc = fa,
tol_act, // Actual tolerance
new_step, // Step at this iteration
prev_step, // Distance from the last but one to the last approximation
p, // Interpolation step is calculated in the form p/q; division is delayed until the last moment
q
tol_act: number, // Actual tolerance
new_step: number, // Step at this iteration
prev_step: number, // Distance from the last but one to the last approximation
p: number, // Interpolation step is calculated in the form p/q; division is delayed until the last moment
q: number
errorTol = errorTol || 0
maxIter = maxIter || 1000
@ -57,7 +57,7 @@ export default function uniroot(
// Decide if the interpolation can be tried
if (Math.abs(prev_step) >= tol_act && Math.abs(fa) > Math.abs(fb)) {
// If prev_step was large enough and was in true direction, Interpolatiom may be tried
var t1, cb, t2
let t1: number, cb: number, t2: number
cb = c - b
if (a === c) {
// If we have only two distinct points linear interpolation can only be applied
@ -101,22 +101,3 @@ export default function uniroot(
}
}
}
/*
var test_counter;
function f1 (x) { test_counter++; return (Math.pow(x,2)-1)*x - 5; }
function f2 (x) { test_counter++; return Math.cos(x)-x; }
function f3 (x) { test_counter++; return Math.sin(x)-x; }
function f4 (x) { test_counter++; return (x + 3) * Math.pow(x - 1, 2); }
[
[f1, 2, 3],
[f2, 2, 3],
[f2, -1, 3],
[f3, -1, 3],
[f4, -4, 4/3]
].forEach(function (args) {
test_counter = 0;
var root = uniroot.apply( pv, args );
;;;console.log( 'uniroot:', args.slice(1), root, test_counter );
})
*/

View File

@ -5,7 +5,8 @@ objectifs:
- aide déclaration revenu indépendant 2019 . CFP
- aide déclaration revenu indépendant 2019 . total charges sociales déductible
- aide déclaration revenu indépendant 2019 . assiette sociale
situation:
dirigeant: 'indépendant'
dirigeant: "'indépendant'"
aide déclaration revenu indépendant 2019: true
unités par défaut: ['€/an']
unité par défaut: '€/an'

View File

@ -26,6 +26,7 @@ import { CompanySection } from '../Home'
import simulationConfig from './config.yaml'
import { Results } from './Result'
import { useNextQuestions } from 'Components/utils/useNextQuestion'
import { Dot } from 'recharts'
const lauchComputationWhenResultsInViewport = () => {
const dottedName = 'dirigeant . rémunération totale'
@ -58,11 +59,13 @@ const lauchComputationWhenResultsInViewport = () => {
export default function AideDéclarationIndépendant() {
const dispatch = useDispatch()
const rules = useContext(EngineContext).getParsedRules()
const company = useSelector(
(state: RootState) => state.inFranceApp.existingCompany
)
dispatch(setSimulationConfig(simulationConfig, true))
useEffect(() => {
dispatch(setSimulationConfig(simulationConfig, true))
}, [])
const {
resultsRef,
displayForm,
@ -163,8 +166,9 @@ export default function AideDéclarationIndépendant() {
<SimpleField dottedName="entreprise . date de création" />
<SubSection dottedName="aide déclaration revenu indépendant 2019 . nature de l'activité" />
{/* PLNR */}
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . retraite complémentaire . taux spécifique PLNR" />
<SimpleField dottedName="entreprise . catégorie d'activité . débit de tabac" />
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . déduction tabac" />
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . retraite complémentaire . taux spécifique PLNR" />
<h2>
<Trans>Situation personnelle</Trans>
@ -222,17 +226,18 @@ function SubSection({
const nextSteps = useNextQuestions()
const situation = useSelector(situationSelector)
const title = hideTitle ? null : ruleTitle
const subQuestions = Object.values(parsedRules).filter(
({ dottedName, question }) =>
Boolean(question) &&
dottedName.startsWith(sectionDottedName) &&
(Object.keys(situation).includes(dottedName) ||
nextSteps.includes(dottedName))
)
const subQuestions = [
...(Object.keys(situation) as Array<DottedName>),
...nextSteps
].filter(nextStep => {
const { dottedName, question } = parsedRules[nextStep]
return !!question && dottedName.startsWith(sectionDottedName)
})
return (
<>
{!!subQuestions.length && title && <h3>{title}</h3>}
{subQuestions.map(({ dottedName }) => (
{subQuestions.map(dottedName => (
<SimpleField key={dottedName} dottedName={dottedName} />
))}
</>
@ -246,10 +251,11 @@ type SimpleFieldProps = {
}
function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
const dispatch = useDispatch()
const evaluatedRule = useEvaluation(dottedName)
const evaluatedRule = useEvaluation(dottedName, { useDefaultValues: false })
const rules = useContext(EngineContext).getParsedRules()
const value = useSelector(situationSelector)[dottedName]
const [currentValue, setCurrentValue] = useState(value)
const dispatchValue = useCallback(
value => {
dispatch(updateSituation(dottedName, value))
@ -268,8 +274,10 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
useEffect(() => {
setCurrentValue(value)
}, [value])
if (!evaluatedRule.isApplicable) {
if (
evaluatedRule.isApplicable === false ||
evaluatedRule.isApplicable === null
) {
return null
}
return (

View File

@ -190,11 +190,19 @@ export default function Stats() {
<h2>Avis des visiteurs</h2>
<Indicators>
<Indicator
main={formatPercentage(stats.feedback.simulator)}
main={formatValue({
nodeValue: stats.feedback.simulator,
unit: '%',
language: 'fr'
})}
subTitle="Taux de satisfaction sur les simulateurs"
/>
<Indicator
main={formatPercentage(stats.feedback.content)}
main={formatValue({
nodeValue: stats.feedback.content,
unit: '%',
language: 'fr'
})}
subTitle="Taux de satisfaction sur le contenu"
/>
</Indicators>
@ -315,7 +323,7 @@ function LineChartVisits({ periodicity }: LineChartVisitsProps) {
<YAxis
dataKey="visiteurs"
tickFormatter={tickItem =>
formatValue({ value: tickItem, language: 'fr' })
formatValue({ nodeValue: tickItem, language: 'fr' })
}
/>
{periodicity === 'daily' ? (
@ -391,7 +399,7 @@ const CustomTooltip = ({
},
{
value: formatValue({
value: payload[0].payload.visiteurs,
nodeValue: payload[0].payload.visiteurs,
language: 'fr'
}),
unit: ' visiteurs'