diff --git a/source/components/CurrencyInput/CurrencyInput.tsx b/source/components/CurrencyInput/CurrencyInput.tsx index 2cd028007..1c515e5f7 100644 --- a/source/components/CurrencyInput/CurrencyInput.tsx +++ b/source/components/CurrencyInput/CurrencyInput.tsx @@ -8,7 +8,7 @@ import './CurrencyInput.css' type CurrencyInputProps = NumberFormatProps & { value?: string | number debounce?: number - onChange: (event: React.ChangeEvent) => void + onChange?: (event: React.ChangeEvent) => void currencySymbol?: string language?: Parameters[0] } @@ -18,7 +18,6 @@ export default function CurrencyInput({ debounce: debounceTimeout, currencySymbol = '€', onChange, - onSubmit, language, className, ...forwardedProps diff --git a/source/components/TargetSelection.tsx b/source/components/TargetSelection.tsx index f47a8b611..1035b5da4 100644 --- a/source/components/TargetSelection.tsx +++ b/source/components/TargetSelection.tsx @@ -180,15 +180,13 @@ const Target = ({ target, initialRender }) => { {isActiveInput && ( -
- { - dispatch(updateSituation(target.dottedName, value)) - }} - unit={target.defaultUnit} - /> -
+ { + dispatch(updateSituation(target.dottedName, value)) + }} + unit={target.defaultUnit} + />
)} diff --git a/source/components/conversation/Conversation.tsx b/source/components/conversation/Conversation.tsx index b691d5d5d..defec9254 100644 --- a/source/components/conversation/Conversation.tsx +++ b/source/components/conversation/Conversation.tsx @@ -1,21 +1,16 @@ import { goToQuestion, validateStepWithValue } from 'Actions/actions' import QuickLinks from 'Components/QuickLinks' -import InputComponent from 'Engine/RuleInput' +import getInputComponent from 'Engine/getInputComponent' import { findRuleByDottedName } from 'Engine/rules' import React from 'react' import emoji from 'react-easy-emoji' import { Trans } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { RootState } from 'Reducers/rootReducer' -import { - currentQuestionSelector, - flatRulesSelector, - nextStepsSelector -} from 'Selectors/analyseSelectors' +import { currentQuestionSelector, flatRulesSelector, nextStepsSelector } from 'Selectors/analyseSelectors' import * as Animate from 'Ui/animate' import Aide from './Aide' import './conversation.css' -import FormDecorator from './FormDecorator' export type ConversationProps = { customEndMessages?: React.ReactNode @@ -44,16 +39,15 @@ export default function Conversation({ customEndMessages }: ConversationProps) { setDefault() } } - const DecoratedInputComponent = FormDecorator(InputComponent) - return flatRules && nextSteps.length ? ( + return nextSteps.length ? ( <>
{currentQuestion && ( - + {getInputComponent(flatRules)(currentQuestion)}
{previousAnswers.length > 0 && ( diff --git a/source/components/conversation/DateInput.tsx b/source/components/conversation/DateInput.tsx index 3a58ee5c2..2c232990a 100644 --- a/source/components/conversation/DateInput.tsx +++ b/source/components/conversation/DateInput.tsx @@ -1,3 +1,4 @@ +import { FormDecorator } from 'Components/conversation/FormDecorator' import { normalizeDate, normalizeDateString } from 'Engine/date' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -17,7 +18,12 @@ const DateField = styled.input` `} ` -export default function DateInput({ suggestions, onChange, onSubmit, value }) { +export default FormDecorator('input')(function DateInput({ + suggestions, + setFormValue, + submit, + value +}) { const { language } = useTranslation().i18n // Refs for focus handling @@ -47,7 +53,7 @@ export default function DateInput({ suggestions, onChange, onSubmit, value }) { if (!normalizedDate) { return } - onChange(normalizedDate) + setFormValue(normalizedDate) }, [normalizedDate]) // If value change, replace state @@ -65,9 +71,9 @@ export default function DateInput({ suggestions, onChange, onSubmit, value }) { { - onChange(normalizeDateString(value as string)) + setFormValue(normalizeDateString(value as string)) }} - onSecondClick={() => onSubmit('suggestion')} + onSecondClick={() => submit('suggestion')} />
@@ -118,10 +124,8 @@ export default function DateInput({ suggestions, onChange, onSubmit, value }) { value={date.year} />
- {onSubmit && ( - - )} + ) -} +}) diff --git a/source/components/conversation/FormDecorator.tsx b/source/components/conversation/FormDecorator.tsx index 73a0d434f..11ef10dd0 100644 --- a/source/components/conversation/FormDecorator.tsx +++ b/source/components/conversation/FormDecorator.tsx @@ -1,13 +1,11 @@ import { updateSituation } from 'Actions/actions' +import classNames from 'classnames' import Explicable from 'Components/conversation/Explicable' -import { findRuleByDottedName } from 'Engine/rules' +import { serializeUnit } from 'Engine/units' import React from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' -import { - flatRulesSelector, - situationSelector -} from 'Selectors/analyseSelectors' +import { situationSelector } from 'Selectors/analyseSelectors' /* This higher order component wraps "Form" components (e.g. Question.js), that represent user inputs, @@ -17,41 +15,40 @@ Read https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-comp to understand those precious higher order components. */ -export default function FormDecorator(RenderField) { - return function FormStep({ dottedName }) { +export const FormDecorator = formType => RenderField => + function FormStep({ fieldName, question, inversion, unit, ...otherProps }) { const dispatch = useDispatch() const situation = useSelector(situationSelector) - const flatRules = useSelector(flatRulesSelector) - const language = useTranslation().i18n.language const submit = source => dispatch({ type: 'STEP_ACTION', name: 'fold', - step: dottedName, + step: fieldName, source }) const setFormValue = value => { - dispatch(updateSituation(dottedName, value)) + dispatch(updateSituation(fieldName, value)) } return ( -
-

- {findRuleByDottedName(flatRules, dottedName).question}{' '} - -

+
+
+

+ {question} {!inversion && } +

+
) } -} diff --git a/source/components/conversation/Input.js b/source/components/conversation/Input.js index f1bf48bc5..727cd6bed 100644 --- a/source/components/conversation/Input.js +++ b/source/components/conversation/Input.js @@ -1,36 +1,37 @@ import { ThemeColorsContext } from 'Components/utils/colors' import { currencyFormat } from 'Engine/format' -import { serializeUnit } from 'Engine/units' +import { compose } from 'ramda' import React, { useCallback, useContext } from 'react' import { useTranslation } from 'react-i18next' import NumberFormat from 'react-number-format' import { debounce } from '../../utils' +import { FormDecorator } from './FormDecorator' import InputSuggestions from './InputSuggestions' import SendButton from './SendButton' // TODO: fusionner Input.js et CurrencyInput.js -export default function Input({ +export default compose(FormDecorator('input'))(function Input({ suggestions, - onChange, - onSubmit, + setFormValue, + submit, dottedName, value, unit }) { const colors = useContext(ThemeColorsContext) - const debouncedOnChange = useCallback(debounce(750, onChange), []) + const debouncedSetFormValue = useCallback(debounce(750, setFormValue), []) const { language } = useTranslation().i18n const { thousandSeparator, decimalSeparator } = currencyFormat(language) return ( -
+ <>
{ - onChange(value) + setFormValue(value) }} - onSecondClick={() => onSubmit && onSubmit('suggestion')} + onSecondClick={() => submit('suggestion')} />
@@ -44,18 +45,16 @@ export default function Input({ allowEmptyFormatting={true} style={{ border: `1px solid ${colors.textColorOnWhite}` }} onValueChange={({ floatValue }) => { - debouncedOnChange(floatValue) + debouncedSetFormValue(floatValue) }} value={value} autoComplete="off" /> - {onSubmit && ( - - )} +
-
+ ) -} +}) diff --git a/source/components/conversation/InputSuggestions.tsx b/source/components/conversation/InputSuggestions.tsx index 02f3e3af7..f4dfda6ef 100644 --- a/source/components/conversation/InputSuggestions.tsx +++ b/source/components/conversation/InputSuggestions.tsx @@ -24,7 +24,7 @@ export default function InputSuggestions({ if (!suggestions) return null return ( -
+
Suggestions : {toPairs(suggestions).map(([text, value]: [string, number]) => { diff --git a/source/components/conversation/Question.css b/source/components/conversation/Question.css new file mode 100644 index 000000000..e4fc34dcb --- /dev/null +++ b/source/components/conversation/Question.css @@ -0,0 +1,5 @@ +.binaryQuestionList { + display: flex; + align-items: center; + justify-content: flex-end; +} diff --git a/source/components/conversation/Question.js b/source/components/conversation/Question.js index 97d36421d..3b58d0ab9 100644 --- a/source/components/conversation/Question.js +++ b/source/components/conversation/Question.js @@ -1,9 +1,11 @@ import classnames from 'classnames' import { ThemeColorsContext } from 'Components/utils/colors' -import { is } from 'ramda' -import React, { useCallback, useContext } from 'react' +import { compose, is } from 'ramda' +import React, { useCallback, useContext, useState } from 'react' import { Trans } from 'react-i18next' import Explicable from './Explicable' +import { FormDecorator } from './FormDecorator' +import './Question.css' import SendButton from './SendButton' /* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans une liste, ou une liste de listes. @@ -22,42 +24,42 @@ import SendButton from './SendButton' */ -export default function Question({ +// FormDecorator permet de factoriser du code partagé par les différents types de saisie, +// dont Question est un example +export default compose(FormDecorator('question'))(function Question({ choices, - onSubmit, - dottedName, - onChange, + submit, + name, + setFormValue, value: currentValue }) { const colors = useContext(ThemeColorsContext) - const handleChange = useCallback( + const [touched, setTouched] = useState(false) + const onChange = useCallback( value => { - onChange(value) + setFormValue(value) + setTouched(true) }, - [onChange] + [setFormValue] ) const renderBinaryQuestion = () => { - return choices.map(({ value, label }) => ( - - )) + return ( +
+ {choices.map(({ value, label }) => ( + + ))} +
+ ) } const renderChildren = choices => { // seront stockées ainsi dans le state : - // [parent object path]: dotted fieldName relative to parent + // [parent object path]: dotted name relative to parent const relativeDottedName = radioDottedName => - radioDottedName.split(dottedName + ' . ')[1] + radioDottedName.split(name + ' . ')[1] return (
    @@ -68,32 +70,32 @@ export default function Question({ value: 'non', label: 'Aucun', currentValue, - onSubmit, + submit, colors, dottedName: null, - onChange: handleChange + onChange }} /> )} {choices.children && - choices.children.map(({ fieldName, title, dottedName, children }) => + choices.children.map(({ name, title, dottedName, children }) => children ? ( -
  • +
  • {title}
    {renderChildren({ children })}
  • ) : ( -
  • +
  • @@ -108,46 +110,40 @@ export default function Question({ : renderChildren(choices) return ( -
    +
    {choiceElements} - {onSubmit && } +
    ) -} +}) -export let RadioLabel = props => ( +let RadioLabel = props => ( <> ) -function RadioLabelContent({ - value, - label, - currentValue, - onChange, - onSubmit, - css -}) { +function RadioLabelContent({ value, label, currentValue, onChange, submit }) { let labelStyle = value === '_' ? { fontWeight: 'bold' } : null, selected = value === currentValue const click = value => () => { - if (currentValue == value && onSubmit) onSubmit('dblClick') + if (currentValue == value) submit('dblClick') } return (
    - + {/* Super hacky */} + {analysis.unit !== undefined ? ( + { + setValue(floatValue) + dispatch(updateSituation(dottedName, floatValue || 0)) + }} + value={value} + autoComplete="off" + className="targetInput" + css={` + padding: 10px; + `} + /> + ) : ( + + dispatch( + updateSituation(dottedName, evt.currentTarget.checked) + ) + } + /> + )}
diff --git a/source/sites/mon-entreprise.fr/pages/Simulateurs/dnrti.tsx b/source/sites/mon-entreprise.fr/pages/Simulateurs/dnrti.tsx deleted file mode 100644 index cb059f265..000000000 --- a/source/sites/mon-entreprise.fr/pages/Simulateurs/dnrti.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import { setSimulationConfig, updateSituation } from 'Actions/actions' -import RuleLink from 'Components/RuleLink' -import 'Components/TargetSelection.css' -import { formatValue } from 'Engine/format' -import InputComponent from 'Engine/RuleInput' -import React, { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from 'Reducers/rootReducer' -import { - analysisWithDefaultsSelector, - flatRulesSelector, - nextStepsSelector, - ruleAnalysisSelector, - situationSelector -} from 'Selectors/analyseSelectors' -import styled from 'styled-components' -import { DottedName, Rule } from 'Types/rule' -import Animate from 'Ui/animate' -import { CompanySection } from '../Gérer/Home' -import { useRule } from './ArtisteAuteur' - -const simulationConfig = { - objectifs: [ - 'dirigeant . indépendant . cotisations et contributions', - 'dirigeant . rémunération totale' - ], - situation: { - dirigeant: 'indépendant' - }, - 'unités par défaut': ['€/an'] -} - -export default function DNRTI() { - const dispatch = useDispatch() - const analysis = useSelector(analysisWithDefaultsSelector) - const company = useSelector( - (state: RootState) => state.inFranceApp.existingCompany - ) - dispatch(setSimulationConfig(simulationConfig, true)) - - return ( - <> -

- Aide à la déclaration de revenus{' '} - -
- Travailleurs indépendants -

-

- Cet outil vous permet de calculer les données à saisir dans votre - déclaration de revenus professionnels. -

- - - -

Revenus d'activité

- - - - - {/* PLNR */} - - - - -

Situation personnelle

- - - - -

Exonérations

- - - - -

International

- - - {/*

DOM - Départements d'Outre-Mer

-

- Pas encore implémenté -

*/} -
- -
- - ) -} - -type SubSectionProp = { - dottedName: DottedName - hideTitle?: boolean -} -function SubSection({ - dottedName: sectionDottedName, - hideTitle = false -}: SubSectionProp) { - const flatRules = useSelector(flatRulesSelector) - const ruleTitle = useRule(sectionDottedName)?.title - const nextSteps = useSelector(nextStepsSelector) - const situation = useSelector(situationSelector) - const title = hideTitle ? null : ruleTitle - - const subQuestions = flatRules - .filter( - ({ dottedName, question }) => - Boolean(question) && - dottedName.startsWith(sectionDottedName) && - (Object.keys(situation).includes(dottedName) || - nextSteps.includes(dottedName)) - ) - .sort( - (rule1, rule2) => - nextSteps.indexOf(rule1.dottedName) - - nextSteps.indexOf(rule2.dottedName) - ) - return ( - <> - {!!subQuestions.length && title &&

{title}

} - {subQuestions.map(({ dottedName }) => ( - - ))} - - ) -} - -type SimpleFieldProps = { - dottedName: DottedName - question?: Rule['question'] -} -function SimpleField({ dottedName, question }: SimpleFieldProps) { - const dispatch = useDispatch() - const analysis = useSelector((state: RootState) => { - return ruleAnalysisSelector(state, { dottedName }) - }) - const rules = useSelector((state: RootState) => state.rules) - const value = useSelector(situationSelector)[dottedName] - const [currentValue, setCurrentValue] = useState(value) - const update = (value: unknown) => { - dispatch(updateSituation(dottedName, value)) - dispatch({ - type: 'STEP_ACTION', - name: 'fold', - step: dottedName - }) - setCurrentValue(value) - } - useEffect(() => { - setCurrentValue(value) - }, [value]) - - if (!analysis.isApplicable) { - return null - } - return ( - - -

- {question ?? analysis.question} -

- - {/* */} -
-
- ) -} - -function Results() { - const cotisationsRule = useRule( - 'dirigeant . indépendant . cotisations et contributions' - ) - const revenusNet = useRule( - 'dirigeant . indépendant . revenu net de cotisations' - ) - const nonDeductible = useRule( - 'dirigeant . indépendant . cotisations et contributions . CSG et CRDS' - ) - - function Link({ cotisation }) { - return ( -

- - {cotisation.nodeValue - ? formatValue({ - value: cotisation.nodeValue, - language: 'fr', - unit: '€', - maximumFractionDigits: 0 - }) - : '-'} - -

- ) - } - if (!cotisationsRule.nodeValue) { - return null - } - return ( - - - Vos cotisations - - Vos revenus net - - Cotisations non déductibles -

- Ce montant doit être réintégré au revenu net dans votre déclaration - fiscale. -

- -
-
- ) -} - -const FormWrapper = styled.div` - display: flex; - justify-content: space-between; - align-items: flex-start; - - ul { - padding: 0; - } -` - -const FormBlock = styled.section` - width: 63%; - padding: 0; - - h3 { - margin-top: 50px; - } - - select, - input[type='text'] { - font-size: 1.05em; - padding: 5px 10px; - } -` - -const Question = styled.div` - margin-top: 1em; -` - -const ResultBlock = styled.section` - position: sticky; - top: 3%; - padding: 3%; - width: 34%; - background: var(--lightestColor); -` - -const ResultSubTitle = styled.h4` - &:not(:first-child) { - margin-top: 2em; - } -` - -const ResultNumber = styled.strong` - display: block; - text-align: right; -` diff --git a/source/sites/mon-entreprise.fr/pages/Simulateurs/index.tsx b/source/sites/mon-entreprise.fr/pages/Simulateurs/index.tsx index 040be7978..a1bc97d6b 100644 --- a/source/sites/mon-entreprise.fr/pages/Simulateurs/index.tsx +++ b/source/sites/mon-entreprise.fr/pages/Simulateurs/index.tsx @@ -8,7 +8,6 @@ import { Link, useLocation } from 'react-router-dom' import ArtisteAuteur from './ArtisteAuteur' import AssimiléSalarié from './AssimiléSalarié' import AutoEntrepreneur from './AutoEntrepreneur' -import DNRTI from './dnrti' import Home from './Home' import Indépendant from './Indépendant' import Salarié from './Salarié' @@ -28,7 +27,7 @@ export default function Simulateurs() { return ( <> - {pathname !== sitePaths.simulateurs.index && !pathname.match('dnrti') && ( + {pathname !== sitePaths.simulateurs.index && (
{lastState?.fromGérer && ( Retour à la création )} - {(!lastState || lastState?.fromSimulateurs) && ( - - ← Voir les autres simulateurs - - )} + {!lastState || + (lastState?.fromSimulateurs && ( + + ← Voir les autres simulateurs + + ))}
)} @@ -79,7 +79,6 @@ export default function Simulateurs() { path={sitePaths.simulateurs['artiste-auteur']} component={ArtisteAuteur} /> - ) diff --git a/source/sites/mon-entreprise.fr/sitePaths.ts b/source/sites/mon-entreprise.fr/sitePaths.ts index 8c415e678..e7920b1d5 100644 --- a/source/sites/mon-entreprise.fr/sitePaths.ts +++ b/source/sites/mon-entreprise.fr/sitePaths.ts @@ -102,8 +102,7 @@ export const constructLocalizedSitePath = (language: string) => { '/comparaison-régimes-sociaux' ), salarié: t('path.simulateurs.salarié', '/salarié'), - 'artiste-auteur': t('path.simulateurs.artiste-auteur', '/artiste-auteur'), - dnrti: t('path.simulateurs.dnrti', '/dnrti') + 'artiste-auteur': t('path.simulateurs.artiste-auteur', '/artiste-auteur') }, économieCollaborative: { index: t('path.économieCollaborative.index', '/économie-collaborative'), diff --git a/test/mécanismes/durée.yaml b/test/mécanismes/durée.yaml deleted file mode 100644 index 80c08b8fd..000000000 --- a/test/mécanismes/durée.yaml +++ /dev/null @@ -1,23 +0,0 @@ -date de création: - type: date - question: quelle est la date de création ? - -durée entre deux dates: - unité: jours - formule: - durée: - depuis: date de création - jusqu'à: 01/01/2020 - exemples: - - nom: Un an - situation: - date de création: 01/01/2019 - valeur attendue: 365 - - nom: Longtemps - situation: - date de création: 06/11/2012 - valeur attendue: 2612 - - nom: Un mois - situation: - date de création: 01/12/2019 - valeur attendue: 31 diff --git a/yarn.lock b/yarn.lock index dd18b0c4c..28b64c2db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1135,13 +1135,6 @@ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.9.tgz#d868b6febb02666330410fe7f58f3c4b8258be7b" integrity sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A== -"@types/cleave.js@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/cleave.js/-/cleave.js-1.4.1.tgz#1eaa12dcbd1f2187fc580f0bf84dbc9f8834e333" - integrity sha512-53CbLKtK58uTfOCFOT1FMVKF5Zt1Onpk5sm37hY5cO3Uy4DfXzhQaU1PNMf53S7DJpEq1hWxZ+d2uN/W6YZh5w== - dependencies: - "@types/react" "*" - "@types/color-convert@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d"