🎨 Change l'affichage de la comparaison des cotisations

pull/447/head
Johan Girod 2019-01-14 18:53:18 +01:00
parent a609358961
commit 2248ffe70c
27 changed files with 325 additions and 202 deletions

View File

@ -1,7 +1,6 @@
/* @flow */
import { resetSimulation } from 'Actions/actions'
import Montant from 'Components/Montant'
import Overlay from 'Components/Overlay'
import RuleLink from 'Components/RuleLink'
import withColours from 'Components/utils/withColours'
@ -13,6 +12,7 @@ import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import { règleAvecValeurSelector } from 'Selectors/regleSelectors'
import Montant from 'Ui/Montant'
import { softCatch } from '../utils'
import './AnswerList.css'

View File

@ -1,126 +1,203 @@
import PeriodSwitch from 'Components/PeriodSwitch'
import withColours from 'Components/utils/withColours'
import withSitePaths from 'Components/utils/withSitePaths'
import { encodeRuleName, findRuleByDottedName } from 'Engine/rules'
import { compose } from 'ramda'
/* @flow */
import React from 'react'
import emoji from 'react-easy-emoji'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import {
analysisWithDefaultsSelector,
flatRulesSelector
} from 'Selectors/analyseSelectors'
import AnimatedTargetValue from './AnimatedTargetValue'
import { config } from 'react-spring'
import { règleAvecMontantSelector } from 'Selectors/regleSelectors'
import Animate from 'Ui/animate'
import './ComparativeTargets.css'
export default compose(
connect(
state => ({
target: findRuleByDottedName(
flatRulesSelector(state),
state.simulationConfig?.objectifs[0]
),
simulationBranches: state.simulationConfig?.branches,
analyses: analysisWithDefaultsSelector(state)
}),
dispatch => ({
setSituationBranch: id => dispatch({ type: 'SET_SITUATION_BRANCH', id })
})
),
withColours,
withSitePaths
)(
class ComparativeTargets extends React.Component {
render() {
let {
colours,
analyses,
target,
setSituationBranch,
sitePaths,
simulationBranches
} = this.props
if (!simulationBranches) {
return null
}
// We retrieve the values necessary to compute the global % of taxes
// This is not elegant
let getRatioPrélèvements = analysis =>
analysis.targets.find(t => t.dottedName === 'ratio de prélèvements')
return (
<div id="comparative-targets">
<h3>{target.title}</h3>
<PeriodSwitch />
<ul>
{analyses.map((analysis, i) => {
if (!analysis.targets) return null
let { nodeValue, dottedName } = analysis.targets[0],
name = simulationBranches[i].nom
import SchemeCard from './ui/SchemeCard'
// export default compose(
// connect(
// state => ({
// target: findRuleByDottedName(
// flatRulesSelector(state),
// state.simulationConfig?.objectifs[0]
// ),
// simulationBranches: state.simulationConfig?.branches,
// analyses: analysisWithDefaultsSelector(state)
// }),
// dispatch => ({
// setSituationBranch: id => dispatch({ type: 'SET_SITUATION_BRANCH', id })
// })
// ),
// withColours,
// withSitePaths
// )(
// class ComparativeTargets extends React.Component {
// render() {
// let {
// colours,
// analyses,
// target,
// setSituationBranch,
// sitePaths,
// simulationBranches
// } = this.props
// if (!simulationBranches) {
// return null
// }
// // We retrieve the values necessary to compute the global % of taxes
// // This is not elegant
// let getRatioPrélèvements = analysis =>
// analysis.targets.find(t => t.dottedName === 'ratio de prélèvements')
// return (
// <>
// {analyses.map((analysis, i) => {
// if (!analysis.targets) return null
// let { nodeValue, dottedName } = analysis.targets[0],
// name = simulationBranches[i].nom
let microNotApplicable =
name === 'Micro-entreprise' &&
analysis.controls?.find(({ test }) =>
test.includes('base des cotisations > plafond')
)
// let microNotApplicable =
// name === 'Micro-entreprise' &&
// analysis.controls?.find(({ test }) =>
// test.includes('base des cotisations > plafond')
// )
let ratioPrélèvements = getRatioPrélèvements(analysis)
// let ratioPrélèvements = getRatioPrélèvements(analysis)
return (
<li
style={{
color: colours.textColour,
background: `linear-gradient(
60deg,
${colours.darkColour} 0%,
${colours.colour} 100%
)`
}}
className={microNotApplicable ? 'microNotApplicable' : ''}
key={name}>
<span className="title">{name}</span>
{microNotApplicable ? (
<p id="microNotApplicable">{microNotApplicable.message}</p>
) : (
<>
<span className="figure">
<span className="value">
<AnimatedTargetValue value={nodeValue} />
</span>{' '}
<Link
title="Quel est calcul ?"
style={{ color: this.props.colours.colour }}
to={
sitePaths.documentation.index +
'/' +
encodeRuleName(dottedName)
}
onClick={() => setSituationBranch(i)}
className="explanation">
{emoji('📖')}
</Link>
</span>
<small>
Soit{' '}
{Math.round((1 - ratioPrélèvements.nodeValue) * 100)} %
de{' '}
<Link
style={{ color: 'white' }}
to={
sitePaths.documentation.index +
'/' +
encodeRuleName(ratioPrélèvements.dottedName)
}>
prélèvements
</Link>
</small>
</>
)}
</li>
)
})}
</ul>
</div>
)
}
}
// return (
// <li
// style={{
// color: colours.textColour,
// background: `linear-gradient(
// 60deg,
// ${colours.darkColour} 0%,
// ${colours.colour} 100%
// )`
// }}
// className={microNotApplicable ? 'microNotApplicable' : ''}
// key={name}>
// <span className="title">{name}</span>
// {microNotApplicable ? (
// <p id="microNotApplicable">{microNotApplicable.message}</p>
// ) : (
// <>
// <span className="figure">
// <span className="value">
// <AnimatedTargetValue value={nodeValue} />
// </span>{' '}
// <Link
// title="Quel est calcul ?"
// style={{ color: this.props.colours.colour }}
// to={
// sitePaths.documentation.index +
// '/' +
// encodeRuleName(dottedName)
// }
// onClick={() => setSituationBranch(i)}
// className="explanation">
// {emoji('📖')}
// </Link>
// </span>
// <small>
// Soit{' '}
// {Math.round((1 - ratioPrélèvements.nodeValue) * 100)} %
// de{' '}
// <Link
// style={{ color: 'white' }}
// to={
// sitePaths.documentation.index +
// '/' +
// encodeRuleName(ratioPrélèvements.dottedName)
// }>
// prélèvements
// </Link>
// </small>
// </>
// )}
// </li>
// )
// })}
// </ul>
// </div>
// )
// }
// }
// )
const ComparativeTargets = () => (
<div
className="ui__ full-width"
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
alignItems: 'stretch'
}}>
<Animate.fromBottom config={config.gentle}>
<Indépendant />
<AssimiléSalarié />
<MicroEntreprise />
</Animate.fromBottom>
</div>
)
const Indépendant = connect(state => ({
revenuDisponible: règleAvecMontantSelector(state, {
situationBranchName: 'Indépendant'
})('revenu disponible')
}))(({ revenuDisponible }) => (
<SchemeCard
title="Indépendants"
subtitle="La protection à la carte"
amount={revenuDisponible.montant}
icon="👩‍🔧"
features={[
'Régime des indépendants',
'Complémentaire santé et prévoyance facultatives',
'Accidents du travail non couverts',
'Retraite faible (41% du brut en moyenne)',
'Indemnités journalières plus faibles',
'Montant minimum de cotisations',
'Comptabilité plus exigeante',
'Calcul des cotisations décalé'
]}
/>
))
const AssimiléSalarié = connect(state => ({
revenuDisponible: règleAvecMontantSelector(state, {
situationBranchName: 'Assimilé salarié'
})('revenu disponible')
}))(({ revenuDisponible }) => (
<SchemeCard
title="Assimilé salarié"
subtitle="Le régime tout compris"
amount={revenuDisponible.montant}
icon="☂"
features={[
'Régime général',
'Complémentaire santé et prévoyance incluse',
'Accidents du travail couverts',
'Retraite élevée (62 % du brut)',
'Pas de minimum de paie',
"Seuil pour l'activation des droits (4000€/an)",
'Fiche de paie mensuels',
'Prélèvement immédiat'
]}
/>
))
const MicroEntreprise = connect(state => ({
revenuDisponible: règleAvecMontantSelector(state, {
situationBranchName: 'Micro-entreprise'
})('revenu disponible')
}))(({ revenuDisponible }) => (
<SchemeCard
title="Micro-entreprise"
subtitle="Pour une petite activité"
icon="🚶‍♂️"
amount={revenuDisponible.montant}
features={[
'Régime des indépendants',
'Pas de déduction des charges',
'Pas de déduction fiscale pour la mutuelle (Madelin)',
"Seuil de chiffre d'affaire",
"Durée de l'ACCRE plus élevée",
'Pas de CFE la première année',
'Comptabilité simplifiée'
]}
/>
))
export default ComparativeTargets

View File

@ -9,9 +9,9 @@ import { connect } from 'react-redux'
import { config, Spring } from 'react-spring'
import { compose } from 'redux'
import répartitionSelector from 'Selectors/repartitionSelectors'
import Montant from 'Ui/Montant'
import { isIE } from '../utils'
import './Distribution.css'
import Montant from './Montant'
import './PaySlip'
import RuleLink from './RuleLink'
import type { Répartition } from 'Types/ResultViewTypes.js'

View File

@ -6,7 +6,7 @@ import React, { Fragment } from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import FicheDePaieSelectors from 'Selectors/ficheDePaieSelectors'
import Montant from './Montant'
import Montant from 'Ui/Montant'
import './PaySlip.css'
import RuleLink from './RuleLink'
@ -166,8 +166,11 @@ const PaySlip = ({
</p>
<p className="ui__ notice">
<Trans i18nKey="payslip.disclaimer">
Il ne prend pour l'instant pas en compte les accords et
conventions collectives, ni la myriade d'aides aux entreprises. Trouvez votre convention collective <a href="https://socialgouv.github.io/conventions-collectives">ici</a>, et explorez les aides sur&nbsp;
Il ne prend pour l'instant pas en compte les accords et conventions
collectives, ni la myriade d'aides aux entreprises. Trouvez votre
convention collective{' '}
<a href="https://socialgouv.github.io/conventions-collectives">ici</a>
, et explorez les aides sur&nbsp;
<a href="https://www.aides-entreprises.fr">aides-entreprises.fr</a>.
</Trans>
</p>

View File

@ -9,8 +9,8 @@ import React, { Component } from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import ficheDePaieSelectors from 'Selectors/ficheDePaieSelectors'
import Card from 'Ui/Card'
import './ResultView.css'
import type { Tracker } from 'Components/utils/withTracker'
type State = {
@ -55,13 +55,13 @@ class ResultView extends Component<Props, State> {
</div>
<SearchButton rulePageBasePath="./règle" />
</div>
<Card className="result-view__body">
<div className="ui__ card result-view__body">
{this.state.resultView === 'payslip' ? (
<PaySlip />
) : (
<Distribution />
)}
</Card>
</div>
</>
)
)

View File

@ -57,7 +57,6 @@ export default compose(
}
renderRule(dottedName) {
let { brancheName, sitePaths } = this.props
console.log(brancheName)
return (
<div id="RulePage">
<ScrollToTop key={brancheName + dottedName} />

View File

@ -64,6 +64,7 @@ export default compose(
displayHiringProcedures,
match,
validInputEntered,
period,
location,
sitePaths,

View File

@ -1,5 +1,6 @@
import Answers from 'Components/AnswerList'
import Conversation from 'Components/conversation/Conversation'
import { ScrollToElement } from 'Components/utils/Scroll'
import withColours from 'Components/utils/withColours'
import { compose, isEmpty } from 'ramda'
import React from 'react'
@ -42,18 +43,20 @@ export default compose(
Mes réponses
</button>
)}
<Conversation
textColourOnWhite={this.props.colours.textColourOnWhite}
/>
{noNextSteps && (
<>
<h2>Plus de questions ! </h2>
<p>Vous avez atteint l'estimation la plus précise.</p>
</>
)}
{(!hideUntilUserInput || !noUserInput) && (
<Animate.fromBottom>{children}</Animate.fromBottom>
)}
<ScrollToElement>
<Conversation
textColourOnWhite={this.props.colours.textColourOnWhite}
/>
{noNextSteps && (
<>
<h2>Plus de questions ! </h2>
<p>Vous avez atteint l'estimation la plus précise.</p>
</>
)}
{(!hideUntilUserInput || !noUserInput) && (
<Animate.fromBottom>{children}</Animate.fromBottom>
)}
</ScrollToElement>
</>
)
}

View File

@ -19,7 +19,7 @@ import {
flatRulesSelector,
noUserInputSelector
} from 'Selectors/analyseSelectors'
import AnimatedTargetValue from './AnimatedTargetValue'
import AnimatedTargetValue from 'Ui/AnimatedTargetValue'
import CurrencyInput from './CurrencyInput/CurrencyInput'
import ProgressCircle from './ProgressCircle'
import './TargetSelection.css'

View File

@ -1,13 +1,11 @@
import HoverDecorator from 'Components/utils/HoverDecorator'
import withColours from 'Components/utils/withColours'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, withNamespaces } from 'react-i18next'
export default compose(
HoverDecorator,
withNamespaces(),
withColours
withNamespaces()
)(
class SendButton extends Component {
getAction() {
@ -28,16 +26,12 @@ export default compose(
this.getAction()('enter')
}
render() {
let { disabled, colours, hover } = this.props
let { disabled, hover } = this.props
return (
<span className="sendWrapper">
<button
className="send"
className="ui__ button plain"
disabled={disabled}
style={{
color: colours.textColour,
background: colours.colour
}}
onClick={() => this.getAction()('accept')}>
<span className="text">
<Trans>valider</Trans>

View File

@ -36,7 +36,7 @@ export default compose(
}
/>
<button
className="send"
className="ui__ button plain"
style={{
visibility: sendButtonDisabled ? 'hidden' : 'visible',
color: themeColours.textColour,

View File

@ -282,30 +282,6 @@
position: relative;
}
.step .send {
padding: 0.1em 0.4em 0em 1em;
background: none;
cursor: pointer;
border: 1px solid;
border-radius: 0.4em;
line-height: 0em;
}
.step .send:disabled {
opacity: 0.2;
}
.step .send i {
margin: 0 0.3em;
font-size: 160%;
}
.step .send .text {
text-transform: uppercase;
font-size: 135%;
line-height: 2em;
}
.answer {
display: flex;
justify-content: flex-end;
@ -410,7 +386,7 @@ for the appearing element to appear without stacking up below the first one */
font-weight: bold;
background: #ddd;
color: white;
margin-left: 0.6em;
top: -1rem;
padding: 0.5em 0.4em;
border-radius: 0.2em;
transition: 0.2s opacity;

View File

@ -1,5 +1,5 @@
.ui__.inverted-button:active,
.ui__.button:active {
.ui__.inverted-button:not(:disabled):active,
.ui__.button:not(:disabled):active {
animation: push-button-down 0.1s ease-out alternate-reverse 2;
}
@ -14,9 +14,9 @@
/* outline: none; */
line-height: initial;
display: inline-block;
border-radius: 0.6rem;
border-radius: 0.3rem;
transition: all 0.15s;
margin: 0.8rem 0;
margin: 0.6rem 0;
margin-right: 1rem;
text-align: center;
text-transform: uppercase;
@ -34,7 +34,7 @@
.ui__.button:disabled,
.ui__.inverted-button:disabled,
.ui__.skip-button:disabled {
opacity: 0.7;
opacity: 0.5;
cursor: not-allowed;
}
.ui__.button {

View File

@ -1,5 +1,6 @@
.card {
.ui__.card {
box-shadow: 0 1px 3px 0 #d4d4d5, 0 0 0 1px #d4d4d5;
padding: 1em;
border-radius: 0.6em;
border-radius: 0.3rem;
height: 100%;
}

View File

@ -1,6 +0,0 @@
import React from 'react'
import './Card.css'
const Card = ({ children, className = '' }) => (
<div className={'card ' + className}> {children}</div>
)
export default Card

View File

@ -0,0 +1,23 @@
.scheme-card__container {
min-width: 25rem;
margin: 1rem;
display: flex;
flex-direction: column;
}
.scheme-card__header {
text-align: center;
}
.scheme-card__icon {
font-size: 2rem;
}
.scheme-card__title {
margin: 0.5rem 0;
}
.scheme-card__subtitle {
font-size: 120%;
color: gray;
font-weight: 300;
}
.scheme-card__content {
flex: 1;
}

View File

@ -0,0 +1,40 @@
/* @flow */
import React from 'react'
import emoji from 'react-easy-emoji'
import AnimatedTargetValue from './AnimatedTargetValue'
import './SchemeCard.css'
import type { Node } from 'react'
type Props = {
title: Node,
subtitle: Node,
amount: number,
modifier?: {
stared?: boolean,
inactive?: boolean
},
features: Array<Node>,
icon: string
}
const SchemeCard = ({ title, subtitle, amount, icon, features }: Props) => (
<div className="ui__ card scheme-card__container">
<header className="scheme-card__header">
<span className="scheme-card__icon">{emoji(icon)} </span>
<h3 className="scheme-card__title">{title}</h3>
<p className="scheme-card__subtitle">{subtitle}</p>
<p className="ui__ lead">
<AnimatedTargetValue value={amount} />
</p>
</header>
<ul className="scheme-card__content">
{features.map((feature, index) => (
<li key={index}>{feature}</li>
))}
</ul>
<p style={{ textAlign: 'center' }}>
<button className="ui__ button plain">Choisir ce régime</button>
</p>
</div>
)
export default SchemeCard

View File

@ -74,8 +74,8 @@ ul {
margin: 0 0 1rem;
}
p.ui__.lead {
font-size: 1.7rem;
line-height: 2.5rem;
font-size: 150%;
line-height: 150%;
font-weight: 300;
}
ul {

View File

@ -1,5 +1,6 @@
@import './Button/button.css';
@import './Typography.css';
@import './Card.css';
@import './reset.css';
:root {
@ -40,6 +41,9 @@ button {
margin: auto;
padding: 0 0.6rem;
}
.ui__.container .ui__.full-width {
margin: 0 calc((800px - 100vw) / 2);
}
.ui__.notice {
font-size: 90%;

View File

@ -2551,6 +2551,7 @@
- nom: revenu disponible
titre: Revenu disponible
format: euros
résumé: Après déductions des cotisations et de l'impôt
période: flexible
question: Quel revenu voulez-vous toucher ?

View File

@ -171,15 +171,28 @@ export let exampleAnalysisSelector = createSelector(
let makeAnalysisSelector = situationSelector =>
createDeepEqualSelector(
[parsedRulesSelector, targetNamesSelector, situationSelector],
(parsedRules, targetNames, situations) => {
return mapOrApply(
[
parsedRulesSelector,
targetNamesSelector,
situationSelector,
(_, props) => props?.situationBranchName,
branchesSelector
],
(parsedRules, targetNames, situations, branchName, branches) => {
const analysedSituations = mapOrApply(
situation =>
analyseMany(parsedRules, targetNames)(dottedName => {
return situation[dottedName]
}),
situations
)
if (!Array.isArray(analysedSituations) || !branchName || !branches) {
return analysedSituations
}
const branchIndex = branches.findIndex(
branch => branch.nom === branchName
)
return analysedSituations[branchIndex]
}
)

View File

@ -69,13 +69,7 @@ export const règleValeurSelector = createSelector(
: Array.isArray(situation)
? situation[0][dottedName]
: situation[dottedName]
console.log(
dottedName,
valeur,
situation,
analysis.cache,
analysis.cache[dottedName]
)
if (isNil(valeur)) {
console.warn(
`[règleValeurSelector] Impossible de trouver la valeur associée à la règle "${dottedName}". Pensez à vérifier l'orthographe et que l'écriture est bien sous forme dottedName. Vérifiez aussi qu'il ne manque pas une valeur par défaut à une règle nécessaire au calcul.`

View File

@ -71,7 +71,7 @@
border: 1px solid;
border-color: rgba(41, 117, 209);
opacity: 0.8 !important;
border-radius: 0.6rem;
border-radius: 0.3rem;
text-decoration: none;
font-size: 1.2rem;
transition: opacity, box-shadow 0.2s;