Nouveau composant de simulation qui compare des situations
parent
568768eaa8
commit
3f9b06cd0a
|
@ -15,9 +15,6 @@ rules:
|
|||
react/no-unescaped-entities: 0
|
||||
react/display-name: 1
|
||||
parser: babel-eslint
|
||||
parserOptions:
|
||||
ecmaFeatures:
|
||||
legacyDecorators: true
|
||||
|
||||
plugins:
|
||||
- react
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { isEmpty, compose } from 'ramda'
|
||||
import Answers from 'Components/AnswerList'
|
||||
import Conversation from 'Components/conversation/Conversation'
|
||||
import withColours from 'Components/utils/withColours'
|
||||
import ComparativeTargets from 'Components/ComparativeTargets'
|
||||
import './GenericSimulation.css'
|
||||
import {
|
||||
nextStepsSelector,
|
||||
analysisWithDefaultsSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import { reduxForm } from 'redux-form'
|
||||
import PeriodSwitch from 'Components/PeriodSwitch'
|
||||
import Controls from './Controls'
|
||||
import situations from './simulateur-rémunération-dirigeant.yaml'
|
||||
|
||||
export default compose(
|
||||
withColours,
|
||||
connect(state => ({
|
||||
previousAnswers: state.conversationSteps.foldedSteps,
|
||||
noNextSteps: nextStepsSelector(state).length == 0,
|
||||
analysis: analysisWithDefaultsSelector(state)
|
||||
}))
|
||||
)(
|
||||
class extends React.Component {
|
||||
state = {
|
||||
displayAnswers: false
|
||||
}
|
||||
render() {
|
||||
let {
|
||||
colours,
|
||||
noNextSteps,
|
||||
previousAnswers,
|
||||
analysis: { controls }
|
||||
} = this.props
|
||||
return (
|
||||
<div id="GenericSimulation">
|
||||
<header>
|
||||
<img src="https://images.unsplash.com/photo-1488722796624-0aa6f1bb6399?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" />
|
||||
<h1>Quel revenu au régime des indépendants ?</h1>
|
||||
<PeriodSwitch />
|
||||
</header>
|
||||
<div className="ui__ container" id="simulationContent">
|
||||
{!isEmpty(previousAnswers) && (
|
||||
<button
|
||||
style={{
|
||||
background: colours.colour,
|
||||
color: colours.textColour
|
||||
}}
|
||||
onClick={() => this.setState({ displayAnswers: true })}>
|
||||
Mes réponses
|
||||
</button>
|
||||
)}
|
||||
|
||||
{this.state.displayAnswers && (
|
||||
<Answers
|
||||
onClose={() => this.setState({ displayAnswers: false })}
|
||||
/>
|
||||
)}
|
||||
<Conversation
|
||||
textColourOnWhite={this.props.colours.textColourOnWhite}
|
||||
/>
|
||||
<Controls {...{ controls }} />
|
||||
{noNextSteps && (
|
||||
<>
|
||||
<h2>Plus de questions ! </h2>
|
||||
<p>Vous avez atteint l'estimation la plus précise.</p>
|
||||
</>
|
||||
)}
|
||||
<ComparativeTargets />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react'
|
||||
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
|
||||
import { connect } from 'react-redux'
|
||||
import './Targets.css'
|
||||
import withColours from 'Components/utils/withColours'
|
||||
import { Link } from 'react-router-dom'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { compose } from 'ramda'
|
||||
import branches from './simulateur-rémunération-dirigeant.yaml'
|
||||
|
||||
export default compose(
|
||||
connect(state => ({
|
||||
analyses: analysisWithDefaultsSelector(state, { branches })
|
||||
})),
|
||||
withColours
|
||||
)(
|
||||
class Targets extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div id="targets">
|
||||
<div
|
||||
className="content"
|
||||
style={{ color: this.props.colours.textColour }}>
|
||||
<ul>
|
||||
{this.props.analyses.map((analysis, i) => {
|
||||
let { title, nodeValue, dottedName } = analysis.targets[0]
|
||||
return (
|
||||
<li>
|
||||
{branches[i].nom}
|
||||
<span className="figure">
|
||||
<span className="value">{nodeValue?.toFixed(1)}</span>{' '}
|
||||
</span>
|
||||
<Link
|
||||
title="Quel est calcul ?"
|
||||
style={{ color: this.props.colours.colour }}
|
||||
to={'/règle/' + dottedName}
|
||||
className="explanation">
|
||||
{emoji('📖')}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
- nom: Revenu en indépendant
|
||||
situation:
|
||||
indépendant: oui
|
||||
- situation:
|
||||
micro entreprise: oui
|
||||
nom: revenu en micro-entreprise
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Each one will be a line in the simulation box
|
||||
export let mainTargetNames = ["micro entreprise . revenu net d'impôt"]
|
||||
export let mainTargetNames = ["revenu net d'impôt"]
|
||||
|
||||
// Some others will be displayed too so need to be computed
|
||||
export let simulationTargetNames = [...mainTargetNames]
|
||||
|
|
|
@ -131,7 +131,10 @@ export let disambiguateRuleReference = (
|
|||
found = reduce(
|
||||
(res, path) =>
|
||||
when(is(Object), reduced)(
|
||||
findRuleByDottedName(allRules, [...path, partialName].join(' . '))
|
||||
do {
|
||||
let dottedNameToCheck = [...path, partialName].join(' . ')
|
||||
findRuleByDottedName(allRules, dottedNameToCheck)
|
||||
}
|
||||
),
|
||||
null,
|
||||
pathPossibilities
|
||||
|
|
|
@ -835,7 +835,7 @@
|
|||
description: Notion mal définie mais reconnue par les conventions collectives et déterminant l'appartenance à une caise de retraite de base spécifique
|
||||
par défaut: non
|
||||
|
||||
- nom: plafond sécurité sociale
|
||||
- nom: plafond sécurité sociale temps plein
|
||||
description: Le plafond de Sécurité sociale est le montant maximum des rémunérations à prendre en compte pour le calcul de certaines cotisations.
|
||||
période: mois
|
||||
formule: 3377
|
||||
|
@ -847,7 +847,7 @@
|
|||
- espace: contrat salarié
|
||||
nom: plafond sécurité sociale
|
||||
période: flexible
|
||||
formule: plafond sécurité sociale * quotité de travail
|
||||
formule: plafond sécurité sociale temps plein * quotité de travail
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: SMIC temps plein
|
||||
|
@ -2501,6 +2501,7 @@
|
|||
|
||||
- espace: impôt
|
||||
nom: revenu abattu par défaut
|
||||
période: flexible
|
||||
description: Dans le cas général, l'impôt est calculé après l'application d'un abattement forfaitaire fixe. Chacun peut néanmoins opter pour la déclaration de ses *frais réels*, qui viendront remplacer ce forfait par défaut.
|
||||
formule:
|
||||
allègement:
|
||||
|
@ -2546,6 +2547,17 @@
|
|||
contrat salarié . rémunération . net imposable: 4000
|
||||
valeur attendue: 7253.26
|
||||
|
||||
- nom: revenu net de cotisations
|
||||
période: flexible
|
||||
formule:
|
||||
somme:
|
||||
- contrat salarié . salaire . net
|
||||
- indépendant . revenu net de cotisations
|
||||
- micro entreprise . revenu net de cotisations
|
||||
|
||||
- nom: revenu net d'impôt
|
||||
période: flexible
|
||||
formule: revenu net de cotisations - impôt . impôt sur le revenu à payer
|
||||
|
||||
- espace: entreprise
|
||||
nom: chiffre d'affaires
|
||||
|
@ -2625,7 +2637,7 @@
|
|||
alors:
|
||||
barème continu:
|
||||
assiette: base des cotisations
|
||||
multiplicateur: plafond sécurité sociale
|
||||
multiplicateur: plafond sécurité sociale temps plein
|
||||
points:
|
||||
0: 1.5%
|
||||
1.1: 6.5%
|
||||
|
@ -2639,7 +2651,7 @@
|
|||
multiplication:
|
||||
assiette: base des cotisations
|
||||
taux: 0.85%
|
||||
plafond: 5 * plafond sécurité sociale
|
||||
plafond: 5 * plafond sécurité sociale temps plein
|
||||
|
||||
- espace: indépendant . cotisations . maladie
|
||||
nom: artisans commerçants
|
||||
|
@ -2652,7 +2664,7 @@
|
|||
formule:
|
||||
barème continu:
|
||||
assiette: base des cotisations
|
||||
multiplicateur: plafond sécurité sociale
|
||||
multiplicateur: plafond sécurité sociale temps plein
|
||||
points:
|
||||
0: 0%
|
||||
0.4: 3.16%
|
||||
|
@ -2670,7 +2682,7 @@
|
|||
- espace: indépendant . cotisations . maladie . artisans commerçants . part revenus élevés
|
||||
nom: seuil
|
||||
période: flexible
|
||||
formule: 5 * plafond sécurité sociale
|
||||
formule: 5 * plafond sécurité sociale temps plein
|
||||
|
||||
|
||||
- espace: indépendant . cotisations
|
||||
|
@ -2753,7 +2765,7 @@
|
|||
formule:
|
||||
multiplication:
|
||||
assiette: base des cotisations
|
||||
plafond: plafond sécurité sociale
|
||||
plafond: plafond sécurité sociale temps plein
|
||||
taux: 1.3%
|
||||
|
||||
# TODO invalidité décès pour les libéraux
|
||||
|
@ -2790,7 +2802,7 @@
|
|||
multiplication:
|
||||
assiette: base des cotisations
|
||||
taux: 0.29%
|
||||
plafond: plafond sécurité sociale
|
||||
plafond: plafond sécurité sociale temps plein
|
||||
# C'est le taux pour les artisans.
|
||||
# TODO Pour les commerçants, le taux est de 0.25%
|
||||
#
|
||||
|
@ -2800,18 +2812,13 @@
|
|||
formule:
|
||||
barème continu:
|
||||
assiette: base des cotisations
|
||||
multiplicateur: plafond sécurité sociale
|
||||
multiplicateur: plafond sécurité sociale temps plein
|
||||
points:
|
||||
0: 0%
|
||||
1.1: 0%
|
||||
1.4: 3.1%
|
||||
|
||||
|
||||
- espace: indépendant
|
||||
nom: revenu net de cotisations
|
||||
période: flexible
|
||||
formule: base des cotisations - prélèvements
|
||||
|
||||
- espace: indépendant
|
||||
nom: revenu net de cotisations
|
||||
période: flexible
|
||||
|
|
|
@ -12,10 +12,9 @@ import {
|
|||
rulesFr as baseRulesFr
|
||||
} from 'Engine/rules'
|
||||
import { analyse, analyseMany, parseAll } from 'Engine/traverse'
|
||||
import { contains, equals, head, isEmpty, isNil, path, pick } from 'ramda'
|
||||
import { contains, equals, head, isEmpty, pick } from 'ramda'
|
||||
import { getFormValues } from 'redux-form'
|
||||
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect'
|
||||
import { mainTargetNames } from '../config'
|
||||
|
||||
// create a "selector creator" that uses deep equal instead of ===
|
||||
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, equals)
|
||||
|
@ -52,7 +51,7 @@ export let situationSelector = createDeepEqualSelector(
|
|||
|
||||
export let noUserInputSelector = createSelector(
|
||||
[situationSelector],
|
||||
situation => console.log({ situation }) || !situation || isEmpty(situation)
|
||||
situation => !situation || isEmpty(situation)
|
||||
)
|
||||
|
||||
export let formattedSituationSelector = createSelector(
|
||||
|
@ -68,14 +67,20 @@ let validatedStepsSelector = createSelector(
|
|||
(foldedSteps, target) => [...foldedSteps, target]
|
||||
)
|
||||
|
||||
export let validatedSituationSelector = createSelector(
|
||||
[formattedSituationSelector, validatedStepsSelector],
|
||||
(situation, validatedSteps) => pick(validatedSteps, situation)
|
||||
let situationBranchesSelector = createSelector(
|
||||
[formattedSituationSelector, (state, props) => props?.branches || [{}]],
|
||||
(situation, branches) =>
|
||||
branches.map(branchPatch => ({ ...situation, ...branchPatch }))
|
||||
)
|
||||
export let validatedSituationsSelector = createSelector(
|
||||
[situationBranchesSelector, validatedStepsSelector],
|
||||
(situations, validatedSteps) => situations.map(s => pick(validatedSteps, s))
|
||||
)
|
||||
|
||||
let situationWithDefaultsSelector = createSelector(
|
||||
[ruleDefaultsSelector, formattedSituationSelector],
|
||||
(defaults, situation) => ({ ...defaults, ...situation })
|
||||
let situationsWithDefaultsSelector = createSelector(
|
||||
[ruleDefaultsSelector, situationBranchesSelector],
|
||||
(defaults, situations) =>
|
||||
situations.map(situation => ({ ...defaults, ...situation }))
|
||||
)
|
||||
|
||||
let analyseRule = (parsedRules, ruleDottedName, situation) =>
|
||||
|
@ -87,20 +92,21 @@ export let ruleAnalysisSelector = createSelector(
|
|||
[
|
||||
parsedRulesSelector,
|
||||
(_, { dottedName }) => dottedName,
|
||||
situationWithDefaultsSelector
|
||||
situationsWithDefaultsSelector
|
||||
],
|
||||
analyseRule
|
||||
(rules, dottedName, situations) =>
|
||||
analyseRule(rules, dottedName, situations[0])
|
||||
)
|
||||
|
||||
let exampleSituationSelector = createSelector(
|
||||
[
|
||||
parsedRulesSelector,
|
||||
situationWithDefaultsSelector,
|
||||
situationsWithDefaultsSelector,
|
||||
({ currentExample }) => currentExample
|
||||
],
|
||||
(rules, situation, example) =>
|
||||
(rules, situations, example) =>
|
||||
example && {
|
||||
...situation,
|
||||
...situations[0],
|
||||
...disambiguateExampleSituation(
|
||||
rules,
|
||||
findRuleByDottedName(rules, example.dottedName)
|
||||
|
@ -119,15 +125,19 @@ export let exampleAnalysisSelector = createSelector(
|
|||
let makeAnalysisSelector = situationSelector =>
|
||||
createDeepEqualSelector(
|
||||
[parsedRulesSelector, targetNamesSelector, situationSelector],
|
||||
(parsedRules, targetNames, situation) =>
|
||||
analyseMany(parsedRules, targetNames)(dottedName => situation[dottedName])
|
||||
(parsedRules, targetNames, situations) => {
|
||||
let analyses = situations.map(s =>
|
||||
analyseMany(parsedRules, targetNames)(dottedName => s[dottedName])
|
||||
)
|
||||
return situations.length === 1 ? analyses[0] : analyses
|
||||
}
|
||||
)
|
||||
|
||||
export let analysisWithDefaultsSelector = makeAnalysisSelector(
|
||||
situationWithDefaultsSelector
|
||||
situationsWithDefaultsSelector
|
||||
)
|
||||
let analysisValidatedOnlySelector = makeAnalysisSelector(
|
||||
validatedSituationSelector
|
||||
validatedSituationsSelector
|
||||
)
|
||||
|
||||
export let blockingInputControlsSelector = state => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { createSelector } from 'reselect'
|
|||
import {
|
||||
analysisWithDefaultsSelector,
|
||||
flatRulesSelector,
|
||||
validatedSituationSelector
|
||||
validatedSituationsSelector
|
||||
} from './analyseSelectors'
|
||||
import type { FlatRules } from 'Types/State'
|
||||
import type {
|
||||
|
@ -43,9 +43,9 @@ export const règleLocaliséeSelector = createSelector(
|
|||
|
||||
export const règleValeurSelector = createSelector(
|
||||
analysisWithDefaultsSelector,
|
||||
validatedSituationSelector,
|
||||
validatedSituationsSelector,
|
||||
règleLocaliséeSelector,
|
||||
(analysis: Analysis, situation, règleLocalisée: string => Règle) => (
|
||||
(analysis: Analysis, situations, règleLocalisée: string => Règle) => (
|
||||
dottedName: string
|
||||
): RègleValeur => {
|
||||
if (!analysis) {
|
||||
|
@ -58,7 +58,9 @@ export const règleValeurSelector = createSelector(
|
|||
analysis.targets.find(target => target.dottedName === dottedName)
|
||||
|
||||
let valeur =
|
||||
rule && !isNil(rule.nodeValue) ? rule.nodeValue : situation[dottedName]
|
||||
rule && !isNil(rule.nodeValue)
|
||||
? rule.nodeValue
|
||||
: situations[0][dottedName]
|
||||
|
||||
if (isNil(valeur)) {
|
||||
console.warn(
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'Ui/index.css'
|
|||
import Provider from '../../Provider'
|
||||
import Route404 from '../embauche.gouv.fr/pages/Route404'
|
||||
import RulesList from '../embauche.gouv.fr/pages/RulesList'
|
||||
import GenericSimulation from 'Components/GenericSimulation'
|
||||
import ComparativeSimulation from 'Components/ComparativeSimulation'
|
||||
import { simulationTargetNames } from '../../config'
|
||||
|
||||
class App extends Component {
|
||||
|
@ -18,7 +18,7 @@ class App extends Component {
|
|||
}}
|
||||
reduxMiddlewares={[]}>
|
||||
<Switch>
|
||||
<Route exact path="/" component={GenericSimulation} />
|
||||
<Route exact path="/" component={ComparativeSimulation} />
|
||||
<Route path="/règle/:name" component={RulePage} />
|
||||
<Route path="/règles" component={RulesList} />
|
||||
<Route component={Route404} />
|
||||
|
|
Loading…
Reference in New Issue