Merge pull request #706 from betagouv/graphique-repartition

Ajout d'un graphique repartiton
pull/726/head
Maxime Quandalle 2019-10-10 13:26:26 +02:00 committed by GitHub
commit b58c98d2df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 444 additions and 253 deletions

View File

@ -21,7 +21,6 @@
"dependencies": {
"@babel/polyfill": "^7.4.0",
"@babel/runtime": "^7.3.4",
"@researchgate/react-intersection-observer": "^0.7.3",
"classnames": "^2.2.5",
"color-convert": "^1.9.2",
"core-js": "^3.2.1",
@ -51,7 +50,7 @@
"react-router-dom": "^5.1.1",
"react-select": "^1.2.1",
"react-select-fast-filter-options": "^0.2.3",
"react-spring": "^5.8.0",
"react-spring": "=8.0.27",
"react-syntax-highlighter": "^10.1.1",
"react-transition-group": "^2.2.1",
"react-virtualized": "^9.20.0",

View File

@ -1,134 +1,98 @@
/* @flow */
import Observer from '@researchgate/react-intersection-observer'
import withColours from 'Components/utils/withColours'
import { ThemeColoursContext } from 'Components/utils/withColours'
import Value from 'Components/Value'
import { findRuleByDottedName } from 'Engine/rules'
import React, { useState } from 'react'
import React, { useContext } from 'react'
import emoji from 'react-easy-emoji'
import { connect } from 'react-redux'
import { config, Spring } from 'react-spring'
import { compose } from 'redux'
import { useSelector } from 'react-redux'
import { config, animated, useSpring } from 'react-spring'
import { flatRulesSelector } from 'Selectors/analyseSelectors'
import répartitionSelector from 'Selectors/repartitionSelectors'
import { isIE } from '../utils'
import './Distribution.css'
import './PaySlip'
import RuleLink from './RuleLink'
import type { ThemeColours } from 'Components/utils/withColours'
import type { Répartition } from 'Types/ResultViewTypes.js'
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
type Props = ?Répartition & {
colours: ThemeColours
}
const ANIMATION_SPRING = config.gentle
function Distribution({
colours: { colour },
rules,
// $FlowFixMe
...distribution
}: Props) {
const [branchesInViewport, setBranchesInViewport] = useState([])
const handleBrancheInViewport = branche => (event, unobserve) => {
if (!event.isIntersecting) {
return
}
unobserve()
setBranchesInViewport(branchesInViewport => [
branche,
...branchesInViewport
])
}
export default function Distribution() {
const distribution = useSelector(répartitionSelector)
if (!Object.values(distribution).length) {
return null
}
const {
répartition,
cotisationMaximum,
total,
cotisations,
salaireChargé,
salaireNet
} = distribution
return (
<>
<div className="distribution-chart__container">
{répartition.map(
([brancheDottedName, { partPatronale, partSalariale }]) => {
const branche = findRuleByDottedName(rules, brancheDottedName),
brancheInViewport =
branchesInViewport.indexOf(brancheDottedName) !== -1
const montant = brancheInViewport
? partPatronale + partSalariale
: 0
return (
<Observer
key={brancheDottedName}
threshold={[0.5]}
onChange={handleBrancheInViewport(brancheDottedName)}>
<Spring
config={ANIMATION_SPRING}
to={{
flex: montant / cotisationMaximum,
opacity: montant ? 1 : 0
}}>
{styles => (
<div
className="distribution-chart__item"
style={{
opacity: styles.opacity
}}>
<BranchIcône icône={branche.icons} />
<div className="distribution-chart__item-content">
<p className="distribution-chart__counterparts">
<span className="distribution-chart__branche-name">
<RuleLink {...branche} />
</span>
<br />
<small>{branche.summary}</small>
</p>
<ChartItemBar {...{ styles, colour, montant, total }} />
</div>
</div>
)}
</Spring>
</Observer>
)
}
{distribution.répartition.map(
([brancheDottedName, { partPatronale, partSalariale }]) => (
<DistributionBranch
key={brancheDottedName}
{...{
brancheDottedName,
partPatronale,
partSalariale,
distribution
}}
/>
)
)}
</div>
<div className="distribution-chart__total">
<span />
<RuleLink {...salaireNet} />
<Value {...salaireNet} unit="€" maximumFractionDigits={0} />
<span>+</span>
<RuleLink {...cotisations} />
<Value {...cotisations} unit="€" maximumFractionDigits={0} />
<span />
<div className="distribution-chart__total-border" />
<span>=</span>
<RuleLink {...salaireChargé} />
<Value {...salaireChargé} unit="€" maximumFractionDigits={0} />
</div>
</>
)
}
export default compose(
withColours,
connect(state => ({
...répartitionSelector(state),
rules: flatRulesSelector(state)
}))
)(Distribution)
let ChartItemBar = ({ styles, colour, montant, total }) => (
const ANIMATION_SPRING = config.gentle
function DistributionBranch({
brancheDottedName,
partPatronale,
partSalariale,
distribution
}) {
const rules = useSelector(flatRulesSelector)
const [intersectionRef, brancheInViewport] = useDisplayOnIntersecting({
threshold: 0.5
})
const colours = useContext(ThemeColoursContext)
const branche = findRuleByDottedName(rules, brancheDottedName)
const montant = brancheInViewport ? partPatronale + partSalariale : 0
const styles = useSpring({
config: ANIMATION_SPRING,
to: {
flex: montant / distribution.cotisationMaximum,
opacity: montant ? 1 : 0
}
})
return (
<animated.div
ref={intersectionRef}
className="distribution-chart__item"
style={{ opacity: styles.opacity }}>
<BranchIcône icône={branche.icons} />
<div className="distribution-chart__item-content">
<p className="distribution-chart__counterparts">
<span className="distribution-chart__branche-name">
<RuleLink {...branche} />
</span>
<br />
<small>{branche.summary}</small>
</p>
<ChartItemBar
{...{
styles,
colour: colours.colour,
montant,
total: distribution.total
}}
/>
</div>
</animated.div>
)
}
let ChartItemBar = ({ styles, colour, montant }) => (
<div className="distribution-chart__bar-container">
<div
<animated.div
className="distribution-chart__bar"
style={{
backgroundColor: colour,

View File

@ -1,12 +1,18 @@
import { T } from 'Components'
import Distribution from 'Components/Distribution'
import PaySlip from 'Components/PaySlip'
import { compose } from 'ramda'
import React, { useRef } from 'react'
import StackedBarChart from 'Components/StackedBarChart'
import { ThemeColoursContext } from 'Components/utils/withColours'
import { getRuleFromAnalysis } from 'Engine/rules'
import React, { useRef, useContext } from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import { usePeriod } from 'Selectors/analyseSelectors'
import { useSelector } from 'react-redux'
import { useTranslation } from 'react-i18next'
import {
analysisWithDefaultsSelector,
usePeriod
} from 'Selectors/analyseSelectors'
import * as Animate from 'Ui/animate'
class ErrorBoundary extends React.Component {
@ -24,22 +30,23 @@ class ErrorBoundary extends React.Component {
}
}
export default compose(
connect(state => ({
showDistributionFirst: !state.conversationSteps.foldedSteps.length
}))
)(function SalaryExplanation({ showDistributionFirst }) {
export default function SalaryExplanation() {
const showDistributionFirst = useSelector(
state => !state.conversationSteps.foldedSteps.length
)
const distributionRef = useRef({})
return (
<ErrorBoundary>
<Animate.fromTop key={showDistributionFirst}>
{showDistributionFirst ? (
<>
<RevenueRepatitionSection />
<DistributionSection />
<PaySlipSection />
</>
) : (
<>
<RevenueRepatitionSection />
<div css="text-align: center">
<button
className="ui__ small simple button"
@ -87,7 +94,34 @@ export default compose(
</Animate.fromTop>
</ErrorBoundary>
)
})
}
function RevenueRepatitionSection() {
const analysis = useSelector(analysisWithDefaultsSelector)
const getRule = getRuleFromAnalysis(analysis)
const { t } = useTranslation()
const { palettes } = useContext(ThemeColoursContext)
return (
<section>
<h2>Répartition du total chargé</h2>
<StackedBarChart
data={[
{
...getRule('contrat salarié . rémunération . net après impôt'),
name: t('Revenu disponible'),
color: palettes[0][0]
},
{ ...getRule('impôt'), name: t('Impôts'), color: palettes[1][0] },
{
...getRule('contrat salarié . cotisations'),
color: palettes[1][1]
}
]}
/>
</section>
)
}
function PaySlipSection() {
const period = usePeriod()

View File

@ -6,21 +6,15 @@ import PageFeedback from 'Components/Feedback/PageFeedback'
import SearchButton from 'Components/SearchButton'
import TargetSelection from 'Components/TargetSelection'
import React from 'react'
import { connect } from 'react-redux'
import { useSelector } from 'react-redux'
import { firstStepCompletedSelector } from 'Selectors/analyseSelectors'
import { simulationProgressSelector } from 'Selectors/progressSelectors'
import * as Animate from 'Ui/animate'
import Progress from 'Ui/Progress'
export default connect(state => ({
firstStepCompleted: firstStepCompletedSelector(state),
progress: simulationProgressSelector(state)
}))(function Simulation({
firstStepCompleted,
explanations,
customEndMessages,
progress
}) {
export default function Simulation({ explanations, customEndMessages }) {
const firstStepCompleted = useSelector(firstStepCompletedSelector)
const progress = useSelector(simulationProgressSelector)
return (
<>
<TargetSelection />
@ -71,4 +65,4 @@ export default connect(state => ({
)}
</>
)
})
}

View File

@ -0,0 +1,117 @@
import React from 'react'
import styled from 'styled-components'
import RuleLink from 'Components/RuleLink'
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
import { animated, useSpring } from 'react-spring'
import { capitalise0 } from '../utils'
const BarStack = styled.div`
display: flex;
border-radius: 0.4em;
overflow: hidden;
`
const BarItem = styled.div`
height: 26px;
border-right: 2px solid white;
transition: width 0.3s ease-out;
&:last-child {
border-right: none;
}
`
const BarStackLegend = styled.div`
display: flex;
margin-top: 10px;
flex-direction: column;
@media (min-width: 800px) {
flex-direction: row;
text-align: center;
}
`
const BarStackLegendItem = styled.div`
flex: 1 1 0px;
color: #555;
strong {
display: inline-block;
color: #111;
margin-left: 8px;
}
`
const SmallCircle = styled.span`
display: inline-block;
height: 11px;
width: 11px;
margin-right: 10px;
border-radius: 100%;
`
function integerAndDecimalParts(value) {
const integer = Math.floor(value)
const decimal = value - integer
return { integer, decimal }
}
// This function calculates rounded percentages so that the sum of all
// returned values is always 100. For instance: [60, 30, 10].
export function roundedPercentages(values) {
const sum = (a = 0, b) => a + b
const total = values.reduce(sum)
const percentages = values.map(value =>
integerAndDecimalParts((value / total) * 100)
)
const totalRoundedPercentage = percentages.map(v => v.integer).reduce(sum)
const indexesToIncrement = percentages
.map((percentage, index) => ({ ...percentage, index }))
.sort((a, b) => b.decimal - a.decimal)
.map(({ index }) => index)
.splice(0, 100 - totalRoundedPercentage)
return percentages.map(
({ integer }, index) =>
integer + (indexesToIncrement.includes(index) ? 1 : 0)
)
}
export default function StackedBarChart({ data }) {
const [intersectionRef, displayChart] = useDisplayOnIntersecting({
threshold: 0.5
})
const percentages = roundedPercentages(data.map(d => d.nodeValue))
const dataWithPercentage = data.map((data, index) => ({
...data,
percentage: percentages[index]
}))
const styles = useSpring({ opacity: displayChart ? 1 : 0 })
return (
<animated.div ref={intersectionRef} style={styles}>
<BarStack>
{dataWithPercentage.map(({ dottedName, color, percentage }) => (
<BarItem
style={{
width: `${percentage}%`,
backgroundColor: color || 'green'
}}
key={dottedName}
/>
))}
</BarStack>
<BarStackLegend>
{dataWithPercentage.map(({ percentage, color, ...rule }) => (
<BarStackLegendItem key={rule.dottedName}>
<SmallCircle style={{ backgroundColor: color }} />
<RuleLink {...rule}>{capitalise0(rule.name)}</RuleLink>
<strong>{percentage} %</strong>
</BarStackLegendItem>
))}
</BarStackLegend>
</animated.div>
)
}

View File

@ -0,0 +1,12 @@
import { expect } from 'chai'
import { roundedPercentages } from './StackedBarChart'
describe('roundedPercentages', () => {
it('rounds correctly', () => {
expect(roundedPercentages([500, 250, 250])).to.deep.equal([50, 25, 25])
expect(roundedPercentages([501, 251, 248])).to.deep.equal([50, 25, 25])
expect(roundedPercentages([506, 257, 237])).to.deep.equal([50, 26, 24])
expect(roundedPercentages([509, 259, 232])).to.deep.equal([51, 26, 23])
expect(roundedPercentages([503, 253, 244])).to.deep.equal([50, 25, 25])
})
})

View File

@ -6,8 +6,8 @@ import {
Spring,
Trail,
Transition
} from 'react-spring'
import type { SpringConfig } from 'react-spring'
} from 'react-spring/renderprops'
import type { SpringConfig } from 'react-spring/renderprops'
import type { Node } from 'react'
type Props = {
@ -32,11 +32,11 @@ export const fromBottom = ({
config={config}
from={{ opacity: 0, y: 20 }}
leave={{ opacity: 0, y: -20 }}
to={{ opacity: 1, y: 0 }}>
to={{ opacity: 1, y: 0 }}
items={children}>
{/* eslint-disable-next-line react/display-name */}
{React.Children.map(children, (item, i) => ({ y, ...style }) => (
{item => ({ y, ...style }) => (
<animated.div
key={i}
style={{
transform: y.interpolate(y =>
y !== 0 ? `translate3d(0, ${y}px,0)` : 'none'
@ -46,7 +46,7 @@ export const fromBottom = ({
}}>
{item}
</animated.div>
))}
)}
</Trail>
)
export const fromTop = ({
@ -62,11 +62,11 @@ export const fromTop = ({
config={config}
leave={{ opacity: 0, y: 20 }}
from={{ opacity: 0, y: -20 }}
to={{ opacity: 1, y: 0 }}>
to={{ opacity: 1, y: 0 }}
items={children}>
{/* eslint-disable-next-line react/display-name */}
{React.Children.map(children, (item, i) => ({ y, ...style }) => (
{item => ({ y, ...style }) => (
<animated.div
key={i}
style={{
transform: y.interpolate(y =>
y ? `translate3d(0, ${y}px,0)` : 'none'
@ -76,7 +76,7 @@ export const fromTop = ({
}}>
{item}
</animated.div>
))}
)}
</Trail>
)

View File

@ -0,0 +1,31 @@
import { useEffect, useRef, useState } from 'react'
export default function({ root = null, rootMargin, threshold = 0 }) {
const ref = useRef()
const [wasOnScreen, setWasOnScreen] = useState(false)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setWasOnScreen(entry.isIntersecting)
observer.unobserve(ref.current)
}
},
{
root,
rootMargin,
threshold
}
)
const node = ref.current
if (ref.current) {
observer.observe(node)
}
return () => {
observer.unobserve(node)
}
}, [root, rootMargin, threshold]) // Empty array ensures that effect is only run on mount and unmount
return [ref, wasOnScreen]
}

View File

@ -53,6 +53,21 @@ const lightenColour = (hex, x) => {
const [h, s, l] = convert.hex.hsl(hex.split('#')[1])
return '#' + convert.hsl.hex([h, s, Math.max(2, Math.min(l + x, 98))])
}
const generateDarkenVariations = (numberOfVariation, [h, s, l]) => {
return [...Array(numberOfVariation).keys()].map(
i => '#' + convert.hsl.hex([h, s, l * 0.8 ** i])
)
}
const deriveAnalogousPalettes = hex => {
const [h, s, l] = convert.hex.hsl(hex.split('#')[1])
return [
generateDarkenVariations(4, [(h - 45) % 360, 0.75 * s, l]),
generateDarkenVariations(4, [(h + 45) % 360, 0.75 * s, l])
]
}
const generateTheme = (themeColour?: ?string): ThemeColours => {
let // Use the default theme colour if the host page hasn't made a choice
colour = themeColour || '#2975D1',
@ -70,7 +85,8 @@ const generateTheme = (themeColour?: ?string): ThemeColours => {
: 'rgba(0, 0, 0, .6)',
lighterTextColour = darkColour + '99',
lighterInverseTextColour = lightenTextColour(inverseTextColour),
textColourOnWhite = textColour === '#ffffff' ? colour : '#333'
textColourOnWhite = textColour === '#ffffff' ? colour : '#333',
palettes = deriveAnalogousPalettes(colour)
return {
colour,
@ -84,7 +100,8 @@ const generateTheme = (themeColour?: ?string): ThemeColours => {
lightColour,
lighterColour,
lightestColour,
darkestColour
darkestColour,
palettes
}
}

View File

@ -103,6 +103,8 @@ Fiche de paie: Payslip
Détail annuel des cotisations: Annual detail of my contributions
Voir la répartition des cotisations: View contribution breakdown
Cotisations: Contributions
Revenu disponible: Disposable income
Impôts: Taxes
payslip:
notice: This simulation helps you understand your French payslip, but it should not be used as one. For further details, check <1>service-public.fr (French)</1>.
heures: 'Hours worked per month: '

View File

@ -3181,7 +3181,7 @@
- nom: impôt
icônes: 🏛️
description: Cet ensemble de formules est un modèle ultra-simplifié de l'impôt sur le revenu, qui ne prend en compte que l'abattement 10%, le barème et la décôte.
titre: impôt sur le revenu
titre: impôts sur le revenu
période: flexible
unité:
formule:

View File

@ -1,41 +1,72 @@
import { T } from 'Components'
import Warning from 'Components/SimulateurWarning'
import Simulation from 'Components/Simulation'
import StackedBarChart from 'Components/StackedBarChart'
import indépendantConfig from 'Components/simulationConfigs/auto-entrepreneur.yaml'
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
import { compose } from 'ramda'
import React from 'react'
import { ThemeColoursContext } from 'Components/utils/withColours'
import { getRuleFromAnalysis } from 'Engine/rules'
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
import { useSelector } from 'react-redux'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { AvertissementProtectionSocialeIndépendants } from './Indépendant'
const AutoEntrepreneur = ({ t }) => (
<>
<Helmet>
<title>
{t(
'simulateurs.auto-entrepreneur.page.titre',
'Auto-entrepreneur : simulateur officiel de revenus et de cotisations'
)}
</title>
<meta
name="description"
content={t(
'simulateurs.auto-entrepreneur.page.description',
"Estimez vos revenus en tant qu'auto-entrepreneur à partir de votre chiffre d'affaire. Prise en compte de toutes les cotisations et de l'impôt sur le revenu. Simulateur officiel de l'Urssaf"
)}
const AutoEntrepreneur = () => {
const { t } = useTranslation()
return (
<>
<Helmet>
<title>
{t(
'simulateurs.auto-entrepreneur.page.titre',
'Auto-entrepreneur : simulateur officiel de revenus et de cotisations'
)}
</title>
<meta
name="description"
content={t(
'simulateurs.auto-entrepreneur.page.description',
"Estimez vos revenus en tant qu'auto-entrepreneur à partir de votre chiffre d'affaire. Prise en compte de toutes les cotisations et de l'impôt sur le revenu. Simulateur officiel de l'Urssaf"
)}
/>
</Helmet>
<h1>
<T k="simulateurs.auto-entrepreneur.titre">
Simulateur de revenus auto-entrepreneur
</T>
</h1>
<Warning simulateur="auto-entreprise" />
<Simulation explanations={<ExplanationSection />} />
</>
)
}
export default withSimulationConfig(indépendantConfig)(AutoEntrepreneur)
function ExplanationSection() {
const analysis = useSelector(analysisWithDefaultsSelector)
const getRule = getRuleFromAnalysis(analysis)
const { t } = useTranslation()
const { palettes } = useContext(ThemeColoursContext)
return (
<section>
<h2>Répartition du chiffre d'affaire</h2>
<StackedBarChart
data={[
{
...getRule('revenu net après impôt'),
color: palettes[0][0]
},
{ ...getRule('impôt'), color: palettes[1][0] },
{
...getRule('auto-entrepreneur . cotisations et contributions'),
name: t('Cotisations'),
color: palettes[1][1]
}
]}
/>
</Helmet>
<h1>
<T k="simulateurs.auto-entrepreneur.titre">
Simulateur de revenus auto-entrepreneur
</T>
</h1>
<Warning simulateur="auto-entreprise" />
<Simulation explanation={<AvertissementProtectionSocialeIndépendants />} />
</>
)
export default compose(
withTranslation(),
withSimulationConfig(indépendantConfig)
)(AutoEntrepreneur)
</section>
)
}

View File

@ -1,73 +1,71 @@
import { React, T } from 'Components'
import { ThemeColoursContext } from 'Components/utils/withColours'
import { T } from 'Components'
import { getRuleFromAnalysis } from 'Engine/rules'
import Warning from 'Components/SimulateurWarning'
import Simulation from 'Components/Simulation'
import StackedBarChart from 'Components/StackedBarChart'
import indépendantConfig from 'Components/simulationConfigs/indépendant.yaml'
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
import { compose } from 'ramda'
import emoji from 'react-easy-emoji'
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
import React, { useContext } from 'react'
import { Helmet } from 'react-helmet'
import { useSelector } from 'react-redux'
import { useTranslation } from 'react-i18next'
export default compose(withSimulationConfig(indépendantConfig))(
function Indépendant() {
const { t } = useTranslation()
return (
<>
<Helmet>
<title>
{t(
'simulateurs.indépendant.page.titre',
'Indépendant : simulateur officiel de revenus et de cotisations'
)}
</title>
<meta
name="description"
content={t(
'simulateurs.indépendant.page.description',
"Estimez vos revenus en tant qu'indépendant à partir de votre chiffre d'affaire (pour les EI et les gérants EURL et SARL majoritaires). Prise en compte de toutes les cotisations et de l'impôt sur le revenu. Simulateur officiel de l'Urssaf"
)}
/>
</Helmet>
<h1>
<T k="simulateurs.indépendant.titre">
Simulateur de revenus pour indépendants
</T>
</h1>
<Warning />
<Simulation
explanation={
<>
<AvertissementForfaitIndépendants />
<AvertissementProtectionSocialeIndépendants />
</>
}
export default withSimulationConfig(indépendantConfig)(function Indépendant() {
const { t } = useTranslation()
return (
<>
<Helmet>
<title>
{t(
'simulateurs.indépendant.page.titre',
'Indépendant : simulateur officiel de revenus et de cotisations'
)}
</title>
<meta
name="description"
content={t(
'simulateurs.indépendant.page.description',
"Estimez vos revenus en tant qu'indépendant à partir de votre chiffre d'affaire (pour les EI et les gérants EURL et SARL majoritaires). Prise en compte de toutes les cotisations et de l'impôt sur le revenu. Simulateur officiel de l'Urssaf"
)}
/>
</>
)
}
)
</Helmet>
<h1>
<T k="simulateurs.indépendant.titre">
Simulateur de revenus pour indépendants
</T>
</h1>
<Warning />
<Simulation explanations={<ExplanationSection />} />
</>
)
})
let AvertissementForfaitIndépendants = () => (
<p className="ui__ notice">
{emoji('💶')}{' '}
<T k="simulateurs.indépendant.explication1">
Notre estimation prend en compte les <em>cotisations réelles</em> dues par
le travailleur indépendant. Pendant la première année de son activité, il
paiera un forfait réduit (une somme de l'ordre de 1300 / an pour un
artisan bénéficiant de l'ACRE)... mais il sera régularisé l'année suivante
selon ce montant réel.
</T>
</p>
)
function ExplanationSection() {
const analysis = useSelector(analysisWithDefaultsSelector)
const getRule = getRuleFromAnalysis(analysis)
const { t } = useTranslation()
const { palettes } = useContext(ThemeColoursContext)
export let AvertissementProtectionSocialeIndépendants = () => (
<p className="ui__ notice">
{emoji('☂️')}{' '}
<T k="simulateurs.indépendant.explication1">
Les assurances chômage et accidents du travail ne sont pas prises en
charge au sein de la Sécurité sociale des indépendants. La retraite basée
sur le revenu professionnel est généralement plus faible. Pour être
couvert le professionnel peut souscrire des assurances complémentaires.
</T>
</p>
)
return (
<section>
<h2>Répartition du chiffre d'affaire</h2>
<StackedBarChart
data={[
{
...getRule('revenu net après impôt'),
name: t('Revenu disponible'),
color: palettes[0][0]
},
{ ...getRule('impôt'), color: palettes[1][0] },
{
...getRule('indépendant . cotisations et contributions'),
name: t('Cotisations'),
color: palettes[1][1]
}
]}
/>
</section>
)
}

View File

@ -744,7 +744,7 @@
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0":
version "7.4.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
@ -948,15 +948,6 @@
promise-limit "^2.5.0"
puppeteer "^1.7.0"
"@researchgate/react-intersection-observer@^0.7.3":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@researchgate/react-intersection-observer/-/react-intersection-observer-0.7.4.tgz#9360274611beebd801e3c068294ddf2ab0d4b163"
integrity sha512-4F291saKAP9I25Qe1ePflvm1DLLA43GlBIZfpMFYWofph7CAm+19nT8xwkQqSszg4PwZa5BpkaI4tAEJHtlj3w==
dependencies:
invariant "^2.2.2"
prop-types "^15.6.0"
warning "^3.0.0"
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
@ -8196,12 +8187,13 @@ react-side-effect@^1.1.0:
exenv "^1.2.1"
shallowequal "^1.0.1"
react-spring@^5.8.0:
version "5.9.2"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-5.9.2.tgz#4c4e048ffce24755eaa60acd2f3fe4a3d686ef16"
integrity sha512-ZDCBm4OaYuuSER3k7EELn9AKGs98HI23qMRL93zUUw6AlyBmqoU+eAfouGRx47gVkZkH+hArJXBHf4UGYpH9Og==
react-spring@=8.0.27:
version "8.0.27"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a"
integrity sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==
dependencies:
"@babel/runtime" "^7.0.0"
"@babel/runtime" "^7.3.1"
prop-types "^15.5.8"
react-syntax-highlighter@^10.1.1:
version "10.3.5"