⚙️🎨 Ajoute la possibilité d'explorer les calculs dans un recalcul

pull/1504/head
Johan Girod 2021-05-05 13:55:59 +02:00
parent f9fb6fc4b6
commit 7147583081
12 changed files with 170 additions and 38 deletions

View File

@ -28,3 +28,4 @@ trim_trailing_whitespace = false
trim_trailing_whitespace = false
indent_style = space
indent_size = 4

View File

@ -8,5 +8,8 @@
"cSpell.words": [
"mycompanyinfrance",
"smarttag"
]
],
"search.exclude": {
"**/dist": true
}
}

View File

@ -942,6 +942,7 @@ dirigeant . indépendant . cotisations et contributions . début activité:
règle: cotisations et contributions
avec:
assiette des cotisations: assiette forfaitaire
assiette des cotisations . sans plancher: assiette forfaitaire
situation personnelle . RSA: non
références:
Fiche Urssaf: https://www.urssaf.fr/portail/home/independant/mes-cotisations/les-etapes-de-calcul/le-mode-de-calcul/lajustement-et-la-regularisation.html

View File

@ -68,7 +68,8 @@
}
.ui__.card.plain small,
.ui__.card.plain .notice {
color: rgba(255, 255, 255, 0.8);
opacity: 0.9;
color: white;
}
.ui__.card.plain .targetInput {

View File

@ -96,6 +96,7 @@ span.ui__.enumeration:not(:last-of-type)::after {
font-size: 85%;
line-height: initial;
padding: 0.4rem 0.6rem;
white-space: nowrap;
font-weight: bold;
color: white !important;
background: var(--darkColor);

View File

@ -1,4 +1,4 @@
import { EvaluationFunction } from '..'
import Engine, { EvaluationFunction } from '..'
import { ASTNode, EvaluatedNode } from '../AST/types'
import { defaultNode } from '../evaluation'
import { registerEvaluationFunction } from '../evaluationFunctions'
@ -11,6 +11,7 @@ export type RecalculNode = {
explanation: {
recalcul: ASTNode
amendedSituation: Array<[ReferenceNode, ASTNode]>
parsedSituation?: Engine['parsedSituation']
}
nodeKind: 'recalcul'
}
@ -46,6 +47,7 @@ const evaluateRecalcul: EvaluationFunction<'recalcul'> = function (node) {
),
})
: this
engine.cache._meta.inRecalcul = true
const evaluatedNode = engine.evaluate(node.explanation.recalcul)
engine.cache._meta.inRecalcul = false
@ -55,7 +57,9 @@ const evaluateRecalcul: EvaluationFunction<'recalcul'> = function (node) {
nodeValue: evaluatedNode.nodeValue,
explanation: {
recalcul: evaluatedNode,
engine,
amendedSituation,
parsedSituation: engine.parsedSituation,
},
missingVariables: evaluatedNode.missingVariables,
...('unit' in evaluatedNode && { unit: evaluatedNode.unit }),

View File

@ -1,8 +1,12 @@
import Engine, { utils } from 'publicodes'
import React, { useContext } from 'react'
import emoji from 'react-easy-emoji'
import { Link } from 'react-router-dom'
import Engine, { utils } from 'publicodes'
import { BasepathContext, EngineContext } from './contexts'
import { Link, useLocation } from 'react-router-dom'
import {
BasepathContext,
EngineContext,
SituationMetaContext,
} from './contexts'
const { encodeRuleName } = utils
@ -14,6 +18,7 @@ type RuleLinkProps<Name extends string> = Omit<
engine: Engine<Name>
documentationPath: string
displayIcon?: boolean
situationName?: string
children?: React.ReactNode
}
@ -21,6 +26,7 @@ export function RuleLink<Name extends string>({
dottedName,
engine,
documentationPath,
situationName,
displayIcon = false,
children,
...props
@ -46,7 +52,16 @@ export function RuleLink<Name extends string>({
throw new Error(`Unknown rule: ${dottedName}`)
}
return (
<Link to={newPath} {...props}>
<Link
to={{
pathname: newPath,
state: {
situation: engine.parsedSituation,
situationName,
},
}}
{...props}
>
{children || rule.title}{' '}
{displayIcon && rule.rawNode.icônes && (
<span>{emoji(rule.rawNode.icônes)} </span>
@ -63,11 +78,14 @@ export function RuleLinkWithContext(
throw new Error('an engine should be provided in context')
}
const documentationPath = useContext(BasepathContext)
const { state } = useLocation<{ situationName?: string } | undefined>()
const situationName =
useContext(SituationMetaContext)?.name ?? state?.situationName
return (
<RuleLink
engine={engine}
documentationPath={documentationPath}
situationName={situationName}
{...props}
/>
)

View File

@ -1,6 +1,12 @@
import { createContext } from 'react'
import Engine from 'publicodes'
import { createContext } from 'react'
export const BasepathContext = createContext<string>('/documentation')
export const SituationMetaContext = createContext<{ name: string } | undefined>(
undefined
)
export const EngineContext = createContext<Engine<string> | null>(null)
export const RegisterEngineContext = createContext<(engine: Engine) => void>(
() => {}
)
export const ReferencesImagesContext = createContext<Record<string, string>>({})

View File

@ -1,14 +1,15 @@
import Engine, { utils } from 'publicodes'
import { Route } from 'react-router-dom'
import { useCallback, useRef } from 'react'
import { Route, useLocation } from 'react-router-dom'
import {
BasepathContext,
EngineContext,
ReferencesImagesContext,
RegisterEngineContext,
} from './contexts'
import References from './rule/References'
import RulePage from './rule/RulePage'
const { decodeRuleName, encodeRuleName } = utils
export { default as Explanation } from './Explanation'
export { RuleLink } from './RuleLink'
export { References }
@ -20,30 +21,73 @@ type DocumentationProps = {
referenceImages?: Record<string, string>
}
function useCacheEngineBySituation(
defaultEngine: Engine,
currentSituation?: Engine['parsedSituation']
) {
const registeredEngines = useRef(
new WeakMap().set(
defaultEngine.parsedSituation,
defaultEngine.shallowCopy()
)
)
const registerEngine = useCallback(
(engine: Engine) =>
registeredEngines.current.set(
engine.parsedSituation,
engine.shallowCopy()
),
[registeredEngines]
)
if (currentSituation && !registeredEngines.current.has(currentSituation)) {
registeredEngines.current.set(
currentSituation,
defaultEngine.shallowCopy().setSituation(currentSituation)
)
}
const engine = currentSituation
? registeredEngines.current.get(currentSituation)
: defaultEngine
return [engine, registerEngine]
}
export function Documentation({
documentationPath,
engine,
engine: defaultEngine,
referenceImages = {},
}: DocumentationProps) {
const { state } = useLocation<
| {
situation?: Engine['parsedSituation']
situationName?: string
}
| undefined
>()
const [engine, cacheEngine] = useCacheEngineBySituation(
defaultEngine,
state?.situation
)
return (
<EngineContext.Provider value={engine}>
<BasepathContext.Provider value={documentationPath}>
<ReferencesImagesContext.Provider value={referenceImages}>
<Route
path={documentationPath + '/:name+'}
render={({ match }) => {
return (
<RulePage
dottedName={decodeRuleName(match.params.name)}
engine={engine}
language={'fr'}
/>
)
}}
/>
</ReferencesImagesContext.Provider>
</BasepathContext.Provider>
</EngineContext.Provider>
<RegisterEngineContext.Provider value={cacheEngine}>
<EngineContext.Provider value={engine}>
<BasepathContext.Provider value={documentationPath}>
<ReferencesImagesContext.Provider value={referenceImages}>
<Route
path={documentationPath + '/:name+'}
render={({ match }) => {
return (
<RulePage
situationName={state?.situationName}
dottedName={decodeRuleName(match.params.name)}
language={'fr'}
/>
)
}}
/>
</ReferencesImagesContext.Provider>
</BasepathContext.Provider>
</EngineContext.Provider>
</RegisterEngineContext.Provider>
)
}

View File

@ -1,16 +1,36 @@
import { useContext } from 'react'
import {
EngineContext,
RegisterEngineContext,
SituationMetaContext,
} from '../contexts'
import Explanation from '../Explanation'
import { RuleLinkWithContext } from '../RuleLink'
import { Mecanism } from './common'
export default function Recalcul({ nodeValue, explanation, unit }) {
const engine = useContext(EngineContext)
if (!engine) {
throw new Error()
}
useContext(RegisterEngineContext)(
engine.shallowCopy().setSituation(explanation.parsedSituation)
)
return (
<Mecanism name="recalcul" value={nodeValue} unit={unit}>
<>
{explanation.recalcul && (
<>
Recalcul de la valeur de <Explanation node={explanation.recalcul} />{' '}
avec la situation suivante :
</>
<EngineContext.Provider value={explanation.engine}>
<SituationMetaContext.Provider
value={{
name: 'Mécanisme recalcul',
}}
>
Recalcul de la valeur de{' '}
<Explanation node={explanation.recalcul} /> avec la situation
suivante :
</SituationMetaContext.Provider>
</EngineContext.Provider>
)}
<ul>
{explanation.amendedSituation.map(([origin, replacement]) => (

View File

@ -1,9 +1,9 @@
import React, { useContext } from 'react'
import { utils } from 'publicodes'
import { RuleLinkWithContext } from '../RuleLink'
import React, { useContext } from 'react'
import styled from 'styled-components'
import Meta from './Meta'
import { EngineContext } from '../contexts'
import { RuleLinkWithContext } from '../RuleLink'
import Meta from './Meta'
export default function RuleHeader({ dottedName }) {
const engine = useContext(EngineContext)

View File

@ -5,6 +5,9 @@ import Engine, {
utils,
} from 'publicodes'
import { isEmpty } from 'ramda'
import { useContext } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { EngineContext } from '../contexts'
import Explanation from '../Explanation'
import { Markdown } from '../Markdown'
import { RuleLinkWithContext } from '../RuleLink'
@ -12,7 +15,13 @@ import RuleHeader from './Header'
import References from './References'
import RuleSource from './RuleSource'
export default function Rule({ dottedName, engine, language }) {
export default function Rule({ dottedName, language, situationName }) {
const engine = useContext(EngineContext)
const { pathname } = useLocation()
if (!engine) {
throw new Error('Engine expected')
}
if (!(dottedName in engine.getParsedRules())) {
return <p>Cette règle est introuvable dans la base</p>
}
@ -21,7 +30,30 @@ export default function Rule({ dottedName, engine, language }) {
const { parent, valeur } = rule.explanation
return (
<div id="documentationRuleRoot">
{situationName && (
<div
className="ui__ card notice light-bg"
style={{
display: 'flex',
alignItems: 'baseline',
flexWrap: 'wrap',
margin: '1rem 0',
paddingTop: '0.4rem',
paddingBottom: '0.4rem',
}}
>
<div>
Vous explorez la documentation avec le contexte{' '}
<strong className="ui__ label">{situationName}</strong>{' '}
</div>
<div style={{ flex: 1 }} />
<div>
<Link to={pathname}>Retourner à la version de base</Link>
</div>
</div>
)}
<RuleHeader dottedName={dottedName} />
<section>
<Markdown source={description || question} />
</section>
@ -56,6 +88,7 @@ export default function Rule({ dottedName, engine, language }) {
)}
<h2>Comment cette donnée est-elle calculée ?</h2>
<Explanation node={valeur} />
<RuleSource key={dottedName} dottedName={dottedName} engine={engine} />