Merge pull request #856 from betagouv/revert-816-ui-indeps
Revert "Aide à la déclaration des indépendants"pull/857/head
commit
00187921a4
|
@ -8,7 +8,7 @@ import './CurrencyInput.css'
|
|||
type CurrencyInputProps = NumberFormatProps & {
|
||||
value?: string | number
|
||||
debounce?: number
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
currencySymbol?: string
|
||||
language?: Parameters<typeof currencyFormat>[0]
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ export default function CurrencyInput({
|
|||
debounce: debounceTimeout,
|
||||
currencySymbol = '€',
|
||||
onChange,
|
||||
onSubmit,
|
||||
language,
|
||||
className,
|
||||
...forwardedProps
|
||||
|
|
|
@ -180,15 +180,13 @@ const Target = ({ target, initialRender }) => {
|
|||
</div>
|
||||
{isActiveInput && (
|
||||
<Animate.fromTop>
|
||||
<div css="display: flex; justify-content: flex-end">
|
||||
<InputSuggestions
|
||||
suggestions={target.suggestions}
|
||||
onFirstClick={value => {
|
||||
dispatch(updateSituation(target.dottedName, value))
|
||||
}}
|
||||
unit={target.defaultUnit}
|
||||
/>
|
||||
</div>
|
||||
<InputSuggestions
|
||||
suggestions={target.suggestions}
|
||||
onFirstClick={value => {
|
||||
dispatch(updateSituation(target.dottedName, value))
|
||||
}}
|
||||
unit={target.defaultUnit}
|
||||
/>
|
||||
</Animate.fromTop>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -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 ? (
|
||||
<>
|
||||
<Aide />
|
||||
<div tabIndex={0} style={{ outline: 'none' }} onKeyDown={handleKeyDown}>
|
||||
{currentQuestion && (
|
||||
<React.Fragment key={currentQuestion}>
|
||||
<Animate.fadeIn>
|
||||
<DecoratedInputComponent dottedName={currentQuestion} />
|
||||
{getInputComponent(flatRules)(currentQuestion)}
|
||||
</Animate.fadeIn>
|
||||
<div className="ui__ answer-group">
|
||||
{previousAnswers.length > 0 && (
|
||||
|
|
|
@ -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 }) {
|
|||
<InputSuggestions
|
||||
suggestions={suggestions}
|
||||
onFirstClick={value => {
|
||||
onChange(normalizeDateString(value as string))
|
||||
setFormValue(normalizeDateString(value as string))
|
||||
}}
|
||||
onSecondClick={() => onSubmit('suggestion')}
|
||||
onSecondClick={() => submit('suggestion')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -118,10 +124,8 @@ export default function DateInput({ suggestions, onChange, onSubmit, value }) {
|
|||
value={date.year}
|
||||
/>
|
||||
</div>
|
||||
{onSubmit && (
|
||||
<SendButton disabled={!normalizedDate} onSubmit={onSubmit} />
|
||||
)}
|
||||
<SendButton {...{ disabled: !normalizedDate, submit }} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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 (
|
||||
<div className="step">
|
||||
<h3>
|
||||
{findRuleByDottedName(flatRules, dottedName).question}{' '}
|
||||
<Explicable dottedName={dottedName} />
|
||||
</h3>
|
||||
<div className={classNames('step', formType)}>
|
||||
<div className="unfoldedHeader">
|
||||
<h3>
|
||||
{question} {!inversion && <Explicable dottedName={fieldName} />}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<RenderField
|
||||
dottedName={dottedName}
|
||||
value={situation[dottedName]}
|
||||
onChange={setFormValue}
|
||||
onSubmit={submit}
|
||||
rules={flatRules}
|
||||
name={fieldName}
|
||||
value={situation[fieldName]}
|
||||
setFormValue={setFormValue}
|
||||
submit={submit}
|
||||
unit={serializeUnit(unit, situation[fieldName], language)}
|
||||
{...otherProps}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<div className="step input">
|
||||
<>
|
||||
<div css="width: 100%">
|
||||
<InputSuggestions
|
||||
suggestions={suggestions}
|
||||
onFirstClick={value => {
|
||||
onChange(value)
|
||||
setFormValue(value)
|
||||
}}
|
||||
onSecondClick={() => onSubmit && onSubmit('suggestion')}
|
||||
onSecondClick={() => submit('suggestion')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -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"
|
||||
/>
|
||||
<label className="suffix" htmlFor={'step-' + dottedName}>
|
||||
{serializeUnit(unit, value, language)}
|
||||
{unit}
|
||||
</label>
|
||||
{onSubmit && (
|
||||
<SendButton disabled={value === undefined} onSubmit={onSubmit} />
|
||||
)}
|
||||
<SendButton {...{ disabled: value === undefined, submit }} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -24,7 +24,7 @@ export default function InputSuggestions({
|
|||
if (!suggestions) return null
|
||||
|
||||
return (
|
||||
<div css="display: flex; align-items: baseline; ">
|
||||
<div css="display: flex; align-items: baseline; justify-content: flex-end;">
|
||||
<small>Suggestions :</small>
|
||||
|
||||
{toPairs(suggestions).map(([text, value]: [string, number]) => {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
.binaryQuestionList {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
|
@ -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 }) => (
|
||||
<RadioLabel
|
||||
key={value}
|
||||
{...{
|
||||
value,
|
||||
css: 'margin-right: 0.6rem',
|
||||
label,
|
||||
currentValue,
|
||||
onSubmit,
|
||||
colors,
|
||||
onChange: handleChange
|
||||
}}
|
||||
/>
|
||||
))
|
||||
return (
|
||||
<div className="binaryQuestionList">
|
||||
{choices.map(({ value, label }) => (
|
||||
<RadioLabel
|
||||
key={value}
|
||||
{...{ value, label, currentValue, submit, colors, onChange }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
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 (
|
||||
<ul css="width: 100%">
|
||||
|
@ -68,32 +70,32 @@ export default function Question({
|
|||
value: 'non',
|
||||
label: 'Aucun',
|
||||
currentValue,
|
||||
onSubmit,
|
||||
submit,
|
||||
colors,
|
||||
dottedName: null,
|
||||
onChange: handleChange
|
||||
onChange
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
{choices.children &&
|
||||
choices.children.map(({ fieldName, title, dottedName, children }) =>
|
||||
choices.children.map(({ name, title, dottedName, children }) =>
|
||||
children ? (
|
||||
<li key={fieldName} className="variant">
|
||||
<li key={name} className="variant">
|
||||
<div>{title}</div>
|
||||
{renderChildren({ children })}
|
||||
</li>
|
||||
) : (
|
||||
<li key={fieldName} className="variantLeaf">
|
||||
<li key={name} className="variantLeaf">
|
||||
<RadioLabel
|
||||
{...{
|
||||
value: relativeDottedName(dottedName),
|
||||
label: title,
|
||||
dottedName,
|
||||
currentValue,
|
||||
onSubmit,
|
||||
submit,
|
||||
colors,
|
||||
onChange: handleChange
|
||||
onChange
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
|
@ -108,46 +110,40 @@ export default function Question({
|
|||
: renderChildren(choices)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="step question"
|
||||
css="margin-top: 0.6rem; display: flex; align-items: center; flex-wrap: wrap;"
|
||||
>
|
||||
<div css="margin-top: 0.6rem; display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end">
|
||||
{choiceElements}
|
||||
{onSubmit && <SendButton disabled={!currentValue} onSubmit={onSubmit} />}
|
||||
<SendButton
|
||||
{...{
|
||||
disabled: !touched,
|
||||
colors,
|
||||
error: false,
|
||||
submit
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export let RadioLabel = props => (
|
||||
let RadioLabel = props => (
|
||||
<>
|
||||
<RadioLabelContent {...props} />
|
||||
<Explicable dottedName={props.dottedName} />
|
||||
</>
|
||||
)
|
||||
|
||||
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 (
|
||||
<label
|
||||
key={value}
|
||||
style={labelStyle}
|
||||
css={css}
|
||||
className={classnames('radio', 'userAnswerButton', 'ui__', 'button', {
|
||||
selected
|
||||
})}
|
||||
className={classnames('radio', 'userAnswerButton', { selected })}
|
||||
>
|
||||
<Trans>{label}</Trans>
|
||||
<input
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import FormDecorator from 'Components/conversation/FormDecorator'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default FormDecorator('rhetorical-question')(
|
||||
function RhetoricalQuestion({ value: currentValue, submit, possibleChoice }) {
|
||||
const { t } = useTranslation()
|
||||
if (!possibleChoice) return null // No action possible, don't render an answer
|
||||
let { text, value } = possibleChoice
|
||||
return (
|
||||
<span className="answer">
|
||||
<label key={value} className="radio userAnswerButton">
|
||||
<input
|
||||
type="radio"
|
||||
checked={value === currentValue}
|
||||
onClick={submit}
|
||||
value={value}
|
||||
/>
|
||||
{t(text)}
|
||||
</label>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
)
|
|
@ -3,13 +3,13 @@ import { Trans } from 'react-i18next'
|
|||
|
||||
type SendButtonProps = {
|
||||
disabled: boolean
|
||||
onSubmit: (cause: string) => void
|
||||
submit: (cause: string) => void
|
||||
}
|
||||
|
||||
export default function SendButton({ disabled, onSubmit }: SendButtonProps) {
|
||||
const getAction = useCallback(cause => (!disabled ? onSubmit(cause) : null), [
|
||||
export default function SendButton({ disabled, submit }: SendButtonProps) {
|
||||
const getAction = useCallback(cause => (!disabled ? submit(cause) : null), [
|
||||
disabled,
|
||||
onSubmit
|
||||
submit
|
||||
])
|
||||
useEffect(() => {
|
||||
const handleKeyDown = ({ key }: KeyboardEvent) => {
|
||||
|
@ -25,7 +25,7 @@ export default function SendButton({ disabled, onSubmit }: SendButtonProps) {
|
|||
|
||||
return (
|
||||
<button
|
||||
className="ui__ plain button "
|
||||
className="ui__ button plain"
|
||||
css="margin-left: 1.2rem"
|
||||
disabled={disabled}
|
||||
onClick={() => getAction('accept')}
|
||||
|
|
|
@ -1,3 +1,86 @@
|
|||
.scrollIndication {
|
||||
margin: 0.6em 0;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.scrollIndication.down {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#resultsScrollElement,
|
||||
#myScrollToElement {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#foldedSteps {
|
||||
padding: 1em 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#foldedSteps .header button {
|
||||
display: block;
|
||||
margin: 0 auto 1em;
|
||||
}
|
||||
#foldedSteps button i {
|
||||
margin-right: 0.3em;
|
||||
font-size: 110%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#foldedSteps button {
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#myScrollToElement {
|
||||
padding-top: 0.3em;
|
||||
}
|
||||
|
||||
.step {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.step:first-child {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.unfoldedHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.step.completed .edit:hover {
|
||||
background: #333350;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Our little help icon */
|
||||
.help-button {
|
||||
display: inline-block;
|
||||
margin-top: 0.5em;
|
||||
line-height: 1.1em;
|
||||
border-radius: 1em;
|
||||
font-size: 90%;
|
||||
color: #777;
|
||||
border: 1px solid;
|
||||
background: none;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
text-transform: uppercase;
|
||||
font-size: 60%;
|
||||
padding: 0.25em 0.6em;
|
||||
}
|
||||
.help-button:hover {
|
||||
color: #333;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.step fieldset {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
@ -9,9 +92,8 @@
|
|||
list-style-type: none;
|
||||
}
|
||||
|
||||
.step fieldset .step.question .variantLeaf,
|
||||
.step fieldset .step.question {
|
||||
justify-content: flex-end;
|
||||
.step fieldset > ul:not(.binaryQuestionList) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.step.question .variant {
|
||||
|
@ -37,6 +119,7 @@
|
|||
}
|
||||
.step.question .variantLeaf {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
|
@ -51,6 +134,8 @@
|
|||
|
||||
.step label.radio {
|
||||
text-align: center;
|
||||
margin-left: 1rem;
|
||||
/* margin-top: 1em; */
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border-radius: 1em;
|
||||
|
@ -59,6 +144,17 @@
|
|||
font-size: 120%;
|
||||
}
|
||||
|
||||
.resume {
|
||||
transition: 1s display;
|
||||
}
|
||||
|
||||
.answer-ignored {
|
||||
font-size: 80%;
|
||||
opacity: 0.8;
|
||||
margin-left: 0.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.step.question input[type='radio'] {
|
||||
display: none;
|
||||
}
|
||||
|
@ -75,7 +171,7 @@
|
|||
font-size: 120%;
|
||||
padding: 0;
|
||||
padding-right: 0.4em;
|
||||
width: 10rem;
|
||||
width: 8em;
|
||||
text-align: right;
|
||||
padding-left: 0.2em;
|
||||
}
|
||||
|
@ -95,6 +191,7 @@
|
|||
}
|
||||
|
||||
.step input.suffixed {
|
||||
width: 5rem;
|
||||
margin: 0.6rem 0;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
@ -110,25 +207,176 @@
|
|||
outline: none;
|
||||
}
|
||||
|
||||
.help-box {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.help-box p {
|
||||
padding: 1em;
|
||||
font-size: 90%;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.close-help {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 105%;
|
||||
}
|
||||
.close-text {
|
||||
text-transform: uppercase;
|
||||
font-size: 60%;
|
||||
}
|
||||
|
||||
.close-text .icon {
|
||||
font-size: 150%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.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 {
|
||||
margin-top: 0.6rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.foldedQuestion .answer {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.step-input-error {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -1.5em;
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
color: #c0392b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step textarea {
|
||||
vertical-align: middle;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#share-link {
|
||||
color: white;
|
||||
padding: 0.3em 0.3em;
|
||||
display: inline-block;
|
||||
margin-top: 0.3em;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
#share-icon {
|
||||
font-size: 200%;
|
||||
vertical-align: middle;
|
||||
line-height: 0em;
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
|
||||
.info-zone {
|
||||
font-size: 65%;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.input-tip {
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.input-tip p {
|
||||
margin: 0.1em;
|
||||
}
|
||||
|
||||
#show-advanced {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Positioning the animated elements absolutely + transition-delay will make it possible
|
||||
for the appearing element to appear without stacking up below the first one */
|
||||
|
||||
.input-tip {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.foldedQuestion {
|
||||
margin-left: 0em;
|
||||
}
|
||||
|
||||
.foldedQuestion .edit {
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5em;
|
||||
border: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.foldedQuestion .borderWrapper {
|
||||
padding: 0.1em 0;
|
||||
display: inline-block;
|
||||
width: calc(100% - 8em);
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.foldedQuestion:last-of-type .borderWrapper {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.conversationContainer {
|
||||
flex: 1;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.step label.userAnswerButton {
|
||||
border: 1px solid var(--color) !important;
|
||||
text-transform: none !important;
|
||||
border: 1px solid var(--color);
|
||||
background-color: white;
|
||||
color: var(--textColorOnWhite);
|
||||
}
|
||||
.step label.userAnswerButton.selected {
|
||||
background: var(--color);
|
||||
border: 1px solid var(--color);
|
||||
color: var(--textColor);
|
||||
}
|
||||
@media (hover) {
|
||||
.step label.userAnswerButton:hover:not(.selected) {
|
||||
.step label.userAnswerButton:hover {
|
||||
background: var(--color);
|
||||
border: 1px solid var(--color);
|
||||
color: var(--textColor);
|
||||
transition: all 0.05s;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { debounce } from '../../../utils'
|
||||
import { FormDecorator } from '../FormDecorator'
|
||||
|
||||
async function tauxVersementTransport(codeCommune) {
|
||||
const response = await fetch(
|
||||
|
@ -25,7 +26,10 @@ async function searchCommunes(input) {
|
|||
return json
|
||||
}
|
||||
|
||||
export default function Select({ onChange, onSubmit }) {
|
||||
export default FormDecorator('select')(function Select({
|
||||
setFormValue,
|
||||
submit
|
||||
}) {
|
||||
const [searchResults, setSearchResults] = useState()
|
||||
const [isLoading, setLoadingState] = useState(false)
|
||||
|
||||
|
@ -47,7 +51,7 @@ export default function Select({ onChange, onSubmit }) {
|
|||
tauxVersementTransport(option.code)
|
||||
.then(({ taux }) => {
|
||||
// serialize to not mix our data schema and the API response's
|
||||
onChange(
|
||||
setFormValue(
|
||||
JSON.stringify({
|
||||
...option,
|
||||
...(taux != undefined
|
||||
|
@ -57,15 +61,15 @@ export default function Select({ onChange, onSubmit }) {
|
|||
: {})
|
||||
})
|
||||
)
|
||||
onSubmit()
|
||||
submit()
|
||||
})
|
||||
.catch(error => {
|
||||
//eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Erreur dans la récupération du taux de versement transport à partir du code commune',
|
||||
error
|
||||
) || onChange(JSON.stringify({ option }))
|
||||
onSubmit() // eslint-disable-line no-console
|
||||
) || setFormValue(JSON.stringify({ option }))
|
||||
submit() // eslint-disable-line no-console
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -135,4 +139,4 @@ export default function Select({ onChange, onSubmit }) {
|
|||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Worker from 'worker-loader!./SelectTauxRisque.worker.js'
|
||||
import { FormDecorator } from '../FormDecorator'
|
||||
const worker = new Worker()
|
||||
|
||||
function SelectComponent({ onChange, onSubmit, options }) {
|
||||
function SelectComponent({ setFormValue, submit, options }) {
|
||||
const [searchResults, setSearchResults] = useState()
|
||||
let submitOnChange = option => {
|
||||
option.text = +option['Taux net'].replace(',', '.')
|
||||
onChange(option.text)
|
||||
onSubmit()
|
||||
setFormValue(option.text)
|
||||
submit()
|
||||
}
|
||||
const { t } = useTranslation()
|
||||
useEffect(() => {
|
||||
|
@ -130,7 +131,7 @@ function SelectComponent({ onChange, onSubmit, options }) {
|
|||
)
|
||||
}
|
||||
|
||||
export default function Select(props) {
|
||||
export default FormDecorator('select')(function Select(props) {
|
||||
const [options, setOptions] = useState(null)
|
||||
useEffect(() => {
|
||||
fetch(
|
||||
|
@ -153,4 +154,4 @@ export default function Select(props) {
|
|||
|
||||
if (!options) return null
|
||||
return <SelectComponent {...props} options={options} />
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
situation:
|
||||
période: année
|
||||
dirigeant: artiste-auteur
|
||||
unités par défaut: [€/an]
|
||||
objectifs:
|
||||
|
|
|
@ -69,13 +69,6 @@
|
|||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.ui__.card.plain .targetInput {
|
||||
border-color: white;
|
||||
border-color: var(--textColor);
|
||||
color: white;
|
||||
color: var(--textColor);
|
||||
}
|
||||
|
||||
.ui__.interactive.card {
|
||||
user-select: text;
|
||||
-webkit-user-drag: none;
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
import Input from 'Components/conversation/Input'
|
||||
import Question from 'Components/conversation/Question'
|
||||
import SelectGéo from 'Components/conversation/select/SelectGéo'
|
||||
import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
|
||||
import SendButton from 'Components/conversation/SendButton'
|
||||
import CurrencyInput from 'Components/CurrencyInput/CurrencyInput'
|
||||
import ToggleSwitch from 'Components/ui/ToggleSwitch'
|
||||
import { is, prop, unless } from 'ramda'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DottedName, Rule } from 'Types/rule'
|
||||
import DateInput from '../components/conversation/DateInput'
|
||||
import { findRuleByDottedName, queryRule } from './rules'
|
||||
|
||||
export const binaryOptionChoices = [
|
||||
{ value: 'non', label: 'Non' },
|
||||
{ value: 'oui', label: 'Oui' }
|
||||
]
|
||||
|
||||
type Value = string | number | object | boolean
|
||||
type Props = {
|
||||
rules: Array<Rule>
|
||||
dottedName: DottedName
|
||||
onChange: (value: Value) => void
|
||||
useSwitch?: boolean
|
||||
isTarget?: boolean
|
||||
value?: Value
|
||||
className?: string
|
||||
onSubmit?: (value: Value) => void
|
||||
}
|
||||
|
||||
// This function takes the unknown rule and finds which React component should
|
||||
// be displayed to get a user input through successive if statements
|
||||
// That's not great, but we won't invest more time until we have more diverse
|
||||
// input components and a better type system.
|
||||
export default function InputComponent({
|
||||
rules,
|
||||
dottedName,
|
||||
onChange,
|
||||
value,
|
||||
useSwitch = false,
|
||||
isTarget = false,
|
||||
className,
|
||||
onSubmit
|
||||
}: Props) {
|
||||
let rule = findRuleByDottedName(rules, dottedName)
|
||||
let unit = rule.unit || rule.defaultUnit
|
||||
let language = useTranslation().i18n.language
|
||||
|
||||
let commonProps = {
|
||||
key: dottedName,
|
||||
dottedName,
|
||||
value,
|
||||
onChange,
|
||||
onSubmit,
|
||||
className,
|
||||
title: rule.title,
|
||||
question: rule.question,
|
||||
defaultValue: rule.defaultValue,
|
||||
suggestions: rule.suggestions
|
||||
}
|
||||
|
||||
if (getVariant(rule)) {
|
||||
return (
|
||||
<Question
|
||||
{...commonProps}
|
||||
choices={buildVariantTree(rules, dottedName)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (rule.API && rule.API === 'géo')
|
||||
return <SelectGéo {...{ ...commonProps }} />
|
||||
if (rule.API) throw new Error("Le seul API implémenté est l'API géo")
|
||||
|
||||
if (rule.dottedName == 'contrat salarié . ATMP . taux collectif ATMP')
|
||||
return <SelectAtmp {...commonProps} />
|
||||
|
||||
if (rule.type === 'date') {
|
||||
return <DateInput {...commonProps} />
|
||||
}
|
||||
|
||||
if (unit == null) {
|
||||
return useSwitch ? (
|
||||
<ToggleSwitch
|
||||
defaultChecked={value === 'oui' || rule.defaultValue === 'oui'}
|
||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChange(evt.target.checked ? 'oui' : 'non')
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Question {...commonProps} choices={binaryOptionChoices} />
|
||||
)
|
||||
}
|
||||
if (unit?.numerators.includes('€') && isTarget && typeof value === 'number') {
|
||||
return (
|
||||
<>
|
||||
<CurrencyInput
|
||||
{...commonProps}
|
||||
language={language}
|
||||
debounce={600}
|
||||
value={value}
|
||||
name={dottedName}
|
||||
className="targetInput"
|
||||
onChange={evt => onChange(evt.target.value)}
|
||||
/>
|
||||
{onSubmit && <SendButton disabled={!value} onSubmit={onSubmit} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <Input {...commonProps} unit={unit} />
|
||||
}
|
||||
|
||||
let getVariant = rule => queryRule(rule)('formule . une possibilité')
|
||||
|
||||
export let buildVariantTree = (allRules, path) => {
|
||||
let rec = path => {
|
||||
let node = findRuleByDottedName(allRules, path)
|
||||
if (!node) throw new Error(`La règle ${path} est introuvable`)
|
||||
let variant = getVariant(node),
|
||||
variants = variant && unless(is(Array), prop('possibilités'))(variant),
|
||||
shouldBeExpanded = variant && true, //variants.find( v => relevantPaths.find(rp => contains(path + ' . ' + v)(rp) )),
|
||||
canGiveUp = variant && !variant['choix obligatoire']
|
||||
|
||||
return Object.assign(
|
||||
node,
|
||||
shouldBeExpanded
|
||||
? {
|
||||
canGiveUp,
|
||||
children: (variants as any).map(v => rec(path + ' . ' + v))
|
||||
}
|
||||
: null
|
||||
)
|
||||
}
|
||||
return rec(path)
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import Input from 'Components/conversation/Input'
|
||||
import Question from 'Components/conversation/Question'
|
||||
import SelectGéo from 'Components/conversation/select/SelectGéo'
|
||||
import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
|
||||
import { is, pick, prop, unless } from 'ramda'
|
||||
import React from 'react'
|
||||
import DateInput from '../components/conversation/DateInput'
|
||||
import { findRuleByDottedName, queryRule } from './rules'
|
||||
|
||||
// This function takes the unknown rule and finds which React component should be displayed to get a user input through successive if statements
|
||||
// That's not great, but we won't invest more time until we have more diverse input components and a better type system.
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export default rules => dottedName => {
|
||||
let rule = findRuleByDottedName(rules, dottedName)
|
||||
|
||||
let commonProps = {
|
||||
key: dottedName,
|
||||
fieldName: dottedName,
|
||||
...pick(
|
||||
['dottedName', 'title', 'question', 'defaultValue', 'suggestions'],
|
||||
rule
|
||||
)
|
||||
}
|
||||
|
||||
if (getVariant(rule))
|
||||
return (
|
||||
<Question
|
||||
{...commonProps}
|
||||
choices={buildVariantTree(rules, dottedName)}
|
||||
/>
|
||||
)
|
||||
if (rule.API && rule.API === 'géo')
|
||||
return <SelectGéo {...{ ...commonProps }} />
|
||||
if (rule.API) throw new Error("Le seul API implémenté est l'API géo")
|
||||
|
||||
if (rule.dottedName == 'contrat salarié . ATMP . taux collectif ATMP')
|
||||
return <SelectAtmp {...commonProps} />
|
||||
|
||||
if (rule.type === 'date') {
|
||||
return <DateInput {...commonProps} />
|
||||
}
|
||||
|
||||
if (rule.unit == null && rule.defaultUnit == null)
|
||||
return (
|
||||
<Question
|
||||
{...commonProps}
|
||||
choices={[
|
||||
{ value: 'non', label: 'Non' },
|
||||
{ value: 'oui', label: 'Oui' }
|
||||
]}
|
||||
/>
|
||||
)
|
||||
|
||||
// Now the numeric input case
|
||||
|
||||
return <Input {...commonProps} unit={rule.unit || rule.defaultUnit} />
|
||||
}
|
||||
|
||||
let getVariant = rule => queryRule(rule)('formule . une possibilité')
|
||||
|
||||
let buildVariantTree = (allRules, path) => {
|
||||
let rec = path => {
|
||||
let node = findRuleByDottedName(allRules, path)
|
||||
if (!node) throw new Error(`La règle ${path} est introuvable`)
|
||||
let variant = getVariant(node),
|
||||
variants = variant && unless(is(Array), prop('possibilités'))(variant),
|
||||
shouldBeExpanded = variant && true, //variants.find( v => relevantPaths.find(rp => contains(path + ' . ' + v)(rp) )),
|
||||
canGiveUp = variant && !variant['choix obligatoire']
|
||||
|
||||
return Object.assign(
|
||||
node,
|
||||
shouldBeExpanded
|
||||
? {
|
||||
canGiveUp,
|
||||
children: variants.map(v => rec(path + ' . ' + v))
|
||||
}
|
||||
: null
|
||||
)
|
||||
}
|
||||
return rec(path)
|
||||
}
|
|
@ -678,7 +678,6 @@ path:
|
|||
sécuritéSociale: /social-security
|
||||
simulateurs:
|
||||
index: /simulators
|
||||
dnrti: /dnrti
|
||||
assimilé-salarié: /assimile-salarie
|
||||
indépendant: /independant
|
||||
auto-entrepreneur: /auto-entrepreneur
|
||||
|
|
|
@ -152,7 +152,6 @@ function existingCompany(state: Company | null = null, action): Company | null {
|
|||
action.catégorieJuridique
|
||||
)
|
||||
return {
|
||||
...state,
|
||||
siren: state.siren,
|
||||
statutJuridique,
|
||||
dateDeCréation: action.dateDeCréation
|
||||
|
|
|
@ -199,7 +199,6 @@ function simulation(
|
|||
if (state === null) {
|
||||
return state
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case 'HIDE_CONTROL':
|
||||
return { ...state, hiddenControls: [...state.hiddenControls, action.id] }
|
||||
|
@ -248,21 +247,7 @@ function simulation(
|
|||
}
|
||||
return state
|
||||
}
|
||||
const existingCompanyReducer = (state, action: Action) => {
|
||||
if (action.type.startsWith('EXISTING_COMPANY::') && state.simulation) {
|
||||
return {
|
||||
...state,
|
||||
simulation: {
|
||||
...state.simulation,
|
||||
situation: {
|
||||
...state.simulation.situation,
|
||||
...getCompanySituation(state.inFranceApp.existingCompany)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
const mainReducer = (state, action: Action) =>
|
||||
combineReducers({
|
||||
lang,
|
||||
|
@ -285,7 +270,6 @@ const mainReducer = (state, action: Action) =>
|
|||
|
||||
export default reduceReducers<RootState>(
|
||||
mainReducer as any,
|
||||
existingCompanyReducer as any,
|
||||
storageRootReducer as any
|
||||
) as Reducer<RootState>
|
||||
|
||||
|
|
|
@ -3746,15 +3746,15 @@ dirigeant . indépendant . cotisations et contributions:
|
|||
|
||||
dirigeant . indépendant . cotisations et contributions . cotisations . déduction tabac:
|
||||
applicable si: entreprise . catégorie d'activité . débit de tabac
|
||||
unité par défaut: €/an
|
||||
question: Quel est le montant des revenus issus de la vente de tabac que vous souhaitez exonérer de cotisation vieillesse ?
|
||||
description: |
|
||||
Si vous exercez une activité de débit de tabac simultanément à une activité commerciale, vous avez la possibilité d’opter pour le calcul de votre cotisation d’assurance vieillesse sur le seul revenu tiré de votre activité commerciale (en effet, les remises pour débit de tabac sont soumises par ailleurs à un prélèvement vieillesse particulier). Nous attirons cependant votre attention sur le fait qu’en cotisant sur une base moins importante, excluant les revenus de débit de tabac, vos droits à retraite pour l’assurance vieillesse des commerçants en seront diminués.
|
||||
par défaut: 0
|
||||
par défaut: non
|
||||
|
||||
dirigeant . indépendant . cotisations et contributions . cotisations . déduction tabac . revenus déduits:
|
||||
titre: revenu professionnel (avec déduction tabac)
|
||||
applicable si: déduction tabac
|
||||
unité par défaut: €/an
|
||||
remplace:
|
||||
règle: revenu professionnel
|
||||
dans:
|
||||
|
|
|
@ -13,7 +13,6 @@ body,
|
|||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* overflow: auto; */
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
|
@ -22,7 +21,6 @@ body,
|
|||
flex: 1;
|
||||
}
|
||||
.app-container {
|
||||
/* overflow: auto; */
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ type CompanySectionProps = {
|
|||
company: Company | null
|
||||
}
|
||||
|
||||
export const CompanySection = ({ company }: CompanySectionProps) => {
|
||||
const CompanySection = ({ company }: CompanySectionProps) => {
|
||||
const [searchModal, showSearchModal] = useState(false)
|
||||
const [autoEntrepreneurModal, showAutoEntrepreneurModal] = useState(false)
|
||||
const [DirigeantMajoritaireModal, showDirigeantMajoritaireModal] = useState(
|
||||
|
|
|
@ -5,22 +5,21 @@ import SimulateurWarning from 'Components/SimulateurWarning'
|
|||
import config from 'Components/simulationConfigs/artiste-auteur.yaml'
|
||||
import 'Components/TargetSelection.css'
|
||||
import { formatValue } from 'Engine/format'
|
||||
import InputComponent from 'Engine/RuleInput'
|
||||
import { getRuleFromAnalysis } from 'Engine/rules'
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import NumberFormat from 'react-number-format'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
analysisWithDefaultsSelector,
|
||||
flatRulesSelector,
|
||||
parsedRulesSelector,
|
||||
ruleAnalysisSelector,
|
||||
situationSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import styled from 'styled-components'
|
||||
import { DottedName } from 'Types/rule'
|
||||
import Animate from 'Ui/animate'
|
||||
import ToggleSwitch from 'Ui/ToggleSwitch'
|
||||
|
||||
export function useRule(dottedName: DottedName) {
|
||||
const analysis = useSelector(analysisWithDefaultsSelector)
|
||||
|
@ -28,7 +27,6 @@ export function useRule(dottedName: DottedName) {
|
|||
return getRule(dottedName)
|
||||
}
|
||||
|
||||
const InitialRenderContext = createContext(false)
|
||||
function useInitialRender() {
|
||||
const [initialRender, setInitialRender] = useState(true)
|
||||
useEffect(() => {
|
||||
|
@ -53,14 +51,27 @@ export default function ArtisteAuteur() {
|
|||
<section className="ui__ light card">
|
||||
<div id="targetSelection">
|
||||
<ul className="targets">
|
||||
<InitialRenderContext.Provider value={initialRender}>
|
||||
<SimpleField dottedName="artiste-auteur . revenus . traitements et salaires" />
|
||||
<SimpleField dottedName="artiste-auteur . revenus . BNC . recettes" />
|
||||
<SimpleField dottedName="artiste-auteur . revenus . BNC . micro-bnc" />
|
||||
<WarningRegimeSpecial />
|
||||
<SimpleField dottedName="artiste-auteur . revenus . BNC . frais réels" />
|
||||
<SimpleField dottedName="artiste-auteur . cotisations . option surcotisation" />
|
||||
</InitialRenderContext.Provider>
|
||||
<SimpleField
|
||||
dottedName="artiste-auteur . revenus . traitements et salaires"
|
||||
initialRender={initialRender}
|
||||
/>
|
||||
<SimpleField
|
||||
dottedName="artiste-auteur . revenus . BNC . recettes"
|
||||
initialRender={initialRender}
|
||||
/>
|
||||
<SimpleField
|
||||
dottedName="artiste-auteur . revenus . BNC . micro-bnc"
|
||||
initialRender={initialRender}
|
||||
/>
|
||||
<WarningRegimeSpecial />
|
||||
<SimpleField
|
||||
dottedName="artiste-auteur . revenus . BNC . frais réels"
|
||||
initialRender={initialRender}
|
||||
/>
|
||||
<SimpleField
|
||||
dottedName="artiste-auteur . cotisations . option surcotisation"
|
||||
initialRender={initialRender}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -71,19 +82,17 @@ export default function ArtisteAuteur() {
|
|||
|
||||
type SimpleFieldProps = {
|
||||
dottedName: DottedName
|
||||
initialRender: boolean
|
||||
}
|
||||
|
||||
function SimpleField({ dottedName }: SimpleFieldProps) {
|
||||
const rule = useSelector(parsedRulesSelector)[dottedName]
|
||||
function SimpleField({ dottedName, initialRender }: SimpleFieldProps) {
|
||||
const rule = useRule(dottedName)
|
||||
const dispatch = useDispatch()
|
||||
const analysis = useSelector((state: RootState) => {
|
||||
return ruleAnalysisSelector(state, { dottedName })
|
||||
})
|
||||
const initialRender = useContext(InitialRenderContext)
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
const value = useSelector(situationSelector)[dottedName]
|
||||
|
||||
const onChange = x => dispatch(updateSituation(dottedName, x))
|
||||
const analysis = useSelector((state: RootState) =>
|
||||
ruleAnalysisSelector(state, { dottedName })
|
||||
)
|
||||
const situation = useSelector(situationSelector)
|
||||
const [value, setValue] = useState(situation[dottedName])
|
||||
|
||||
if (!analysis.isApplicable) {
|
||||
return null
|
||||
|
@ -100,15 +109,36 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
|
|||
</label>
|
||||
</div>
|
||||
<div className="targetInputOrValue">
|
||||
<InputComponent
|
||||
className="targetInput"
|
||||
isTarget
|
||||
dottedName={dottedName}
|
||||
rules={flatRules}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
useSwitch
|
||||
/>
|
||||
{/* Super hacky */}
|
||||
{analysis.unit !== undefined ? (
|
||||
<NumberFormat
|
||||
autoFocus
|
||||
id={'step-' + dottedName}
|
||||
thousandSeparator={' '}
|
||||
suffix=" €"
|
||||
allowEmptyFormatting={true}
|
||||
onValueChange={({ floatValue }) => {
|
||||
setValue(floatValue)
|
||||
dispatch(updateSituation(dottedName, floatValue || 0))
|
||||
}}
|
||||
value={value}
|
||||
autoComplete="off"
|
||||
className="targetInput"
|
||||
css={`
|
||||
padding: 10px;
|
||||
`}
|
||||
/>
|
||||
) : (
|
||||
<ToggleSwitch
|
||||
id={`step-${dottedName}`}
|
||||
defaultChecked={rule.nodeValue}
|
||||
onChange={evt =>
|
||||
dispatch(
|
||||
updateSituation(dottedName, evt.currentTarget.checked)
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Animate.appear>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<h1>
|
||||
Aide à la déclaration de revenus{' '}
|
||||
<img src="https://img.shields.io/badge/-beta-blue" />
|
||||
<br />
|
||||
<small>Travailleurs indépendants</small>
|
||||
</h1>
|
||||
<p>
|
||||
Cet outil vous permet de calculer les données à saisir dans votre
|
||||
déclaration de revenus professionnels.
|
||||
</p>
|
||||
<FormWrapper>
|
||||
<FormBlock>
|
||||
<CompanySection company={company} />
|
||||
<h2>Revenus d'activité</h2>
|
||||
<SimpleField
|
||||
dottedName="dirigeant . rémunération totale"
|
||||
question="Quel est votre résultat fiscal ?"
|
||||
/>
|
||||
<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>
|
||||
<p>
|
||||
<em>Pas encore implémenté</em>
|
||||
</p> */}
|
||||
</FormBlock>
|
||||
<Results />
|
||||
</FormWrapper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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 && <h3>{title}</h3>}
|
||||
{subQuestions.map(({ dottedName }) => (
|
||||
<SimpleField key={dottedName} dottedName={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 (
|
||||
<Animate.fromTop>
|
||||
<Question>
|
||||
<p
|
||||
css={`
|
||||
border-left: 4px solid var(--lightColor);
|
||||
border-radius: 3px;
|
||||
padding-left: 12px;
|
||||
margin-left: -12px;
|
||||
`}
|
||||
>
|
||||
{question ?? analysis.question}
|
||||
</p>
|
||||
<InputComponent
|
||||
rules={rules}
|
||||
dottedName={dottedName}
|
||||
onChange={update}
|
||||
value={currentValue}
|
||||
/>
|
||||
{/* <Field dottedName={dottedName} onChange={onChange} /> */}
|
||||
</Question>
|
||||
</Animate.fromTop>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<p className="ui__ lead">
|
||||
<RuleLink dottedName={cotisation.dottedName}>
|
||||
{cotisation.nodeValue
|
||||
? formatValue({
|
||||
value: cotisation.nodeValue,
|
||||
language: 'fr',
|
||||
unit: '€',
|
||||
maximumFractionDigits: 0
|
||||
})
|
||||
: '-'}
|
||||
</RuleLink>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
if (!cotisationsRule.nodeValue) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ResultBlock>
|
||||
<Animate.fromTop>
|
||||
<ResultSubTitle>Vos cotisations</ResultSubTitle>
|
||||
<Link cotisation={cotisationsRule} />
|
||||
<ResultSubTitle>Vos revenus net</ResultSubTitle>
|
||||
<Link cotisation={revenusNet} />
|
||||
<ResultSubTitle>Cotisations non déductibles</ResultSubTitle>
|
||||
<p className="ui__ notice">
|
||||
Ce montant doit être réintégré au revenu net dans votre déclaration
|
||||
fiscale.
|
||||
</p>
|
||||
<Link cotisation={nonDeductible} />
|
||||
</Animate.fromTop>
|
||||
</ResultBlock>
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
`
|
|
@ -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 (
|
||||
<>
|
||||
<ScrollToTop key={pathname} />
|
||||
{pathname !== sitePaths.simulateurs.index && !pathname.match('dnrti') && (
|
||||
{pathname !== sitePaths.simulateurs.index && (
|
||||
<div css="transform: translateY(2rem);">
|
||||
{lastState?.fromGérer && (
|
||||
<Link
|
||||
|
@ -46,14 +45,15 @@ export default function Simulateurs() {
|
|||
← <Trans>Retour à la création</Trans>
|
||||
</Link>
|
||||
)}
|
||||
{(!lastState || lastState?.fromSimulateurs) && (
|
||||
<Link
|
||||
to={sitePaths.gérer.index}
|
||||
className="ui__ simple small push-left button"
|
||||
>
|
||||
← <Trans>Voir les autres simulateurs</Trans>
|
||||
</Link>
|
||||
)}
|
||||
{!lastState ||
|
||||
(lastState?.fromSimulateurs && (
|
||||
<Link
|
||||
to={sitePaths.simulateurs.index}
|
||||
className="ui__ simple small push-left button"
|
||||
>
|
||||
← <Trans>Voir les autres simulateurs</Trans>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Switch>
|
||||
|
@ -79,7 +79,6 @@ export default function Simulateurs() {
|
|||
path={sitePaths.simulateurs['artiste-auteur']}
|
||||
component={ArtisteAuteur}
|
||||
/>
|
||||
<Route path={sitePaths.simulateurs.dnrti} component={DNRTI} />
|
||||
</Switch>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue