🐎 améliore la performance du formulaire d'aide à la déclaration pour les indépendants
parent
913051ac9b
commit
ffb3d67eed
|
@ -18,14 +18,14 @@ const rules = {
|
|||
// TODO: rule order shouldn't matter but there is a bug if "impot" is after
|
||||
// "dirigeant".
|
||||
...impot,
|
||||
...déclarationIndépendant,
|
||||
...artisteAuteur,
|
||||
...dirigeant,
|
||||
...entrepriseEtablissement,
|
||||
...protectionSociale,
|
||||
...salarié,
|
||||
...conventionsCollectives,
|
||||
...situationPersonnelle,
|
||||
...déclarationIndépendant
|
||||
...situationPersonnelle
|
||||
}
|
||||
|
||||
export default rules
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
aide déclaration revenu indépendant 2019:
|
||||
description: Ces règles sont écrites pour aider à remplir les déclarations sociale et
|
||||
fiscale des indépendant de 2020 sur les revenus 2019
|
||||
par défaut: non
|
||||
|
||||
aide déclaration revenu indépendant 2019 . professions libérale:
|
||||
remplace:
|
||||
- règle: dirigeant
|
||||
par: "'indépendant'"
|
||||
- règle: entreprise . catégorie d'activité . libérale règlementée
|
||||
par: non
|
||||
formule: non
|
||||
contrôles:
|
||||
- si: entreprise . date de création < 01/01/2019
|
||||
avertissement: >-
|
||||
Cette aide à la déclaration ne prends pas en compte les professions
|
||||
libérales affiliées à la CIPAV.
|
||||
|
||||
aide déclaration revenu indépendant 2019 . plafond sécurité sociale 2019:
|
||||
remplace: plafond sécurité sociale temps plein
|
||||
|
|
|
@ -3,8 +3,12 @@ import { useEffect, useRef, useState } from 'react'
|
|||
export default function({
|
||||
root = null,
|
||||
rootMargin,
|
||||
threshold = 0
|
||||
}: IntersectionObserverInit): [React.RefObject<HTMLDivElement>, boolean] {
|
||||
threshold = 0,
|
||||
unobserve = true
|
||||
}: IntersectionObserverInit & { unobserve?: boolean }): [
|
||||
React.RefObject<HTMLDivElement>,
|
||||
boolean
|
||||
] {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [wasOnScreen, setWasOnScreen] = useState(false)
|
||||
|
||||
|
@ -13,7 +17,10 @@ export default function({
|
|||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setWasOnScreen(entry.isIntersecting)
|
||||
ref.current && observer.unobserve(ref.current)
|
||||
ref.current && unobserve && observer.unobserve(ref.current)
|
||||
}
|
||||
if (!entry.isIntersecting && !unobserve) {
|
||||
setWasOnScreen(entry.isIntersecting)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -27,9 +34,9 @@ export default function({
|
|||
observer.observe(node)
|
||||
}
|
||||
return () => {
|
||||
node && observer.unobserve(node)
|
||||
node && unobserve && observer.unobserve(node)
|
||||
}
|
||||
}, [root, rootMargin, threshold])
|
||||
}, [root, rootMargin, threshold, ref.current])
|
||||
|
||||
return [ref, wasOnScreen]
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ type Props = {
|
|||
onChange: (value: Value) => void
|
||||
useSwitch?: boolean
|
||||
isTarget?: boolean
|
||||
autoFocus?: boolean
|
||||
value?: Value
|
||||
className?: string
|
||||
onSubmit?: (value: Value) => void
|
||||
|
@ -40,6 +41,7 @@ export default function InputComponent({
|
|||
value,
|
||||
useSwitch = false,
|
||||
isTarget = false,
|
||||
autoFocus = false,
|
||||
className,
|
||||
onSubmit
|
||||
}: Props) {
|
||||
|
@ -53,6 +55,7 @@ export default function InputComponent({
|
|||
value,
|
||||
onChange,
|
||||
onSubmit,
|
||||
autoFocus,
|
||||
className,
|
||||
title: rule.title,
|
||||
question: rule.question,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { setSimulationConfig, updateSituation } from 'Actions/actions'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import 'Components/TargetSelection.css'
|
||||
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
|
||||
import { formatValue } from 'Engine/format'
|
||||
import InputComponent from 'Engine/RuleInput'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import Skeleton from 'react-loading-skeleton'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
|
@ -28,19 +30,52 @@ const simulationConfig = {
|
|||
'aide déclaration revenu indépendant 2019 . assiette sociale'
|
||||
],
|
||||
situation: {
|
||||
dirigeant: 'indépendant',
|
||||
'aide déclaration revenu indépendant 2019': 'oui'
|
||||
},
|
||||
'unités par défaut': ['€/an']
|
||||
}
|
||||
const lauchComputationWhenResultsInViewport = () => {
|
||||
const [resultsRef, resultsInViewPort] = useDisplayOnIntersecting({
|
||||
threshold: 0,
|
||||
unobserve: false
|
||||
})
|
||||
const [currentIncome, setCurrentIncome] = useState(null)
|
||||
const [displayForm, setDisplayForm] = useState(false)
|
||||
const updateIncome = useCallback(
|
||||
income => {
|
||||
setDisplayForm(income != null)
|
||||
setCurrentIncome(income)
|
||||
},
|
||||
[setDisplayForm, setCurrentIncome]
|
||||
)
|
||||
const dispatch = useDispatch()
|
||||
useEffect(() => {
|
||||
if (resultsInViewPort && displayForm) {
|
||||
dispatch(
|
||||
updateSituation('dirigeant . rémunération totale', currentIncome)
|
||||
)
|
||||
} else {
|
||||
dispatch(updateSituation('dirigeant . rémunération totale', null))
|
||||
}
|
||||
}, [resultsInViewPort, displayForm, currentIncome])
|
||||
|
||||
return { updateIncome, resultsRef, displayForm }
|
||||
}
|
||||
|
||||
export default function DNRTI() {
|
||||
const dispatch = useDispatch()
|
||||
const analysis = useSelector(analysisWithDefaultsSelector)
|
||||
const rules = useSelector(flatRulesSelector)
|
||||
const company = useSelector(
|
||||
(state: RootState) => state.inFranceApp.existingCompany
|
||||
)
|
||||
dispatch(setSimulationConfig(simulationConfig, true))
|
||||
|
||||
const {
|
||||
resultsRef,
|
||||
displayForm,
|
||||
updateIncome
|
||||
} = lauchComputationWhenResultsInViewport()
|
||||
return (
|
||||
<>
|
||||
<h1>
|
||||
|
@ -65,48 +100,63 @@ export default function DNRTI() {
|
|||
d'engagement
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<FormBlock>
|
||||
<CompanySection company={company} />
|
||||
<h2>Revenus d'activité</h2>
|
||||
<SimpleField
|
||||
dottedName="dirigeant . indépendant . rémunération totale"
|
||||
question="Quel est votre revenu professionnel en 2019 ?"
|
||||
summary="Indiquez votre résultat net fiscal avant déduction des charges sociales et exonérations fiscales."
|
||||
<h2>Quel est votre revenu professionnel en 2019 ?</h2>
|
||||
<p>
|
||||
Indiquez votre résultat net fiscal avant déduction des charges sociales
|
||||
et exonérations fiscales.
|
||||
</p>
|
||||
<BigInput>
|
||||
<InputComponent
|
||||
rules={rules}
|
||||
dottedName="dirigeant . rémunération totale"
|
||||
onChange={updateIncome}
|
||||
autoFocus
|
||||
/>
|
||||
<SimpleField dottedName="entreprise . date de création" />
|
||||
|
||||
<SubSection dottedName="entreprise . catégorie d'activité" />
|
||||
{/* PLNR */}
|
||||
<SimpleField dottedName="dirigeant . indépendant . PLNR régime général" />
|
||||
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . retraite complémentaire . taux spécifique PLNR" />
|
||||
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . déduction tabac" />
|
||||
|
||||
<h3>Situation personnelle</h3>
|
||||
<SimpleField dottedName="situation personnelle . RSA" />
|
||||
<SubSection dottedName="situation personnelle . IJSS" />
|
||||
<SubSection dottedName="dirigeant . indépendant . conjoint collaborateur" />
|
||||
|
||||
<h3>Exonérations</h3>
|
||||
<SimpleField dottedName="entreprise . ACRE" />
|
||||
<SimpleField dottedName="établissement . ZFU" />
|
||||
<SubSection
|
||||
dottedName="dirigeant . indépendant . cotisations et contributions . exonérations"
|
||||
hideTitle
|
||||
/>
|
||||
|
||||
<h3>International</h3>
|
||||
<SimpleField dottedName="situation personnelle . domiciliation fiscale à l'étranger" />
|
||||
<SubSection
|
||||
dottedName="dirigeant . indépendant . revenus étrangers"
|
||||
hideTitle
|
||||
/>
|
||||
{/* <h3>DOM - Départements d'Outre-Mer</h3>
|
||||
</BigInput>
|
||||
{displayForm && (
|
||||
<>
|
||||
<Animate.fromTop>
|
||||
<h3>Votre entreprise</h3>
|
||||
<p>
|
||||
<em>Pas encore implémenté</em>
|
||||
</p> */}
|
||||
</FormBlock>
|
||||
<Results />
|
||||
Vous pouvez renseigner votre entreprise pour pré-remplir le
|
||||
formulaire
|
||||
</p>
|
||||
<CompanySection company={company} />
|
||||
|
||||
<FormBlock>
|
||||
<SimpleField dottedName="entreprise . date de création" />
|
||||
<SubSection dottedName="entreprise . catégorie d'activité" />
|
||||
{/* PLNR */}
|
||||
<SimpleField dottedName="dirigeant . indépendant . PLNR régime général" />
|
||||
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . retraite complémentaire . taux spécifique PLNR" />
|
||||
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . cotisations . déduction tabac" />
|
||||
|
||||
<h3>Situation personnelle</h3>
|
||||
<SimpleField dottedName="situation personnelle . RSA" />
|
||||
<SubSection dottedName="situation personnelle . IJSS" />
|
||||
<SubSection dottedName="dirigeant . indépendant . conjoint collaborateur" />
|
||||
|
||||
<h3>Exonérations</h3>
|
||||
<SimpleField dottedName="entreprise . ACRE" />
|
||||
<SimpleField dottedName="établissement . ZFU" />
|
||||
<SubSection
|
||||
dottedName="dirigeant . indépendant . cotisations et contributions . exonérations"
|
||||
hideTitle
|
||||
/>
|
||||
|
||||
<h3>International</h3>
|
||||
<SimpleField dottedName="situation personnelle . domiciliation fiscale à l'étranger" />
|
||||
<SubSection
|
||||
dottedName="dirigeant . indépendant . revenus étrangers"
|
||||
hideTitle
|
||||
/>
|
||||
</FormBlock>
|
||||
</Animate.fromTop>
|
||||
<div ref={resultsRef}>
|
||||
<Results />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -161,13 +211,19 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
|
|||
const rules = useSelector((state: RootState) => state.rules)
|
||||
const value = useSelector(situationSelector)[dottedName]
|
||||
const [currentValue, setCurrentValue] = useState(value)
|
||||
const dispatchValue = useCallback(
|
||||
value => {
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
dispatch({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: dottedName
|
||||
})
|
||||
},
|
||||
[dispatch, dottedName]
|
||||
)
|
||||
const update = (value: unknown) => {
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
dispatch({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: dottedName
|
||||
})
|
||||
dispatchValue(value)
|
||||
setCurrentValue(value)
|
||||
}
|
||||
useEffect(() => {
|
||||
|
@ -203,12 +259,13 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
|
|||
}
|
||||
|
||||
function Results() {
|
||||
const results = simulationConfig.objectifs
|
||||
.map(objectif => useRule(objectif))
|
||||
.filter(r => r.nodeValue)
|
||||
if (!results.length) {
|
||||
return null
|
||||
}
|
||||
const results = simulationConfig.objectifs.map(dottedName =>
|
||||
useSelector((state: RootState) => {
|
||||
return ruleAnalysisSelector(state, { dottedName })
|
||||
})
|
||||
)
|
||||
const onGoingComputation = !results.filter(node => node.nodeValue != null)
|
||||
.length
|
||||
return (
|
||||
<div
|
||||
className="ui__ card lighter-bg"
|
||||
|
@ -217,32 +274,41 @@ function Results() {
|
|||
<h1 css="text-align: center; margin-bottom: 2rem">
|
||||
Aide à la déclaration 📄
|
||||
</h1>
|
||||
<Animate.fromTop>
|
||||
{results.map(r => (
|
||||
<>
|
||||
<h4>
|
||||
{r.title} <small>{r.summary}</small>
|
||||
</h4>
|
||||
{r.description && <p className="ui__ notice">{r.description}</p>}
|
||||
<p className="ui__ lead" css="margin-bottom: 1rem;">
|
||||
<RuleLink dottedName={r.dottedName}>
|
||||
{r.nodeValue
|
||||
? formatValue({
|
||||
{onGoingComputation && (
|
||||
<h2>
|
||||
<small>Calcul en cours...</small>
|
||||
</h2>
|
||||
)}
|
||||
<>
|
||||
<Animate.fromTop>
|
||||
{results.map(r => (
|
||||
<React.Fragment key={r.title}>
|
||||
<h4>
|
||||
{r.title} <small>{r.summary}</small>
|
||||
</h4>
|
||||
{r.description && <p className="ui__ notice">{r.description}</p>}
|
||||
<p className="ui__ lead" css="margin-bottom: 1rem;">
|
||||
<RuleLink dottedName={r.dottedName}>
|
||||
{r.nodeValue ? (
|
||||
formatValue({
|
||||
value: r.nodeValue,
|
||||
language: 'fr',
|
||||
unit: '€',
|
||||
maximumFractionDigits: 0
|
||||
})
|
||||
: '-'}
|
||||
</RuleLink>
|
||||
</p>
|
||||
</>
|
||||
))}
|
||||
</Animate.fromTop>
|
||||
<div css="text-align: center">
|
||||
<button className="ui__ simple button">🔗 Obtenir le lien</button>
|
||||
<button className="ui__ simple button"> 🖨 Imprimer</button>
|
||||
</div>
|
||||
})
|
||||
) : (
|
||||
<Skeleton width={80} />
|
||||
)}
|
||||
</RuleLink>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Animate.fromTop>
|
||||
<div css="text-align: center">
|
||||
<button className="ui__ simple button">🔗 Obtenir le lien</button>
|
||||
<button className="ui__ simple button"> 🖨 Imprimer</button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -268,3 +334,6 @@ const FormBlock = styled.section`
|
|||
const Question = styled.div`
|
||||
margin-top: 1em;
|
||||
`
|
||||
const BigInput = styled.div`
|
||||
font-size: 130%;
|
||||
`
|
||||
|
|
Loading…
Reference in New Issue