WIP Fusion des composants de simulation

Travail fait * pour :
- la simu indépendant
- la simu salariés

Les simus assimilé et micro devraient pouvoir être converties super
facilement.

La simu de comparation requiert au moins de rétablir un paramètre
du genre displayTargetsOnlyAfterInput

* aux bugs et animations près
indeps-refactor-simulations
Mael 2019-01-17 15:34:44 +01:00
parent b42d1d2d68
commit 22c3d910aa
16 changed files with 301 additions and 306 deletions

View File

@ -16,20 +16,15 @@ type Props = {
validInputEntered: boolean
}
let quickLinks = {
CDD: 'contrat salarié . CDD',
Cadre: 'contrat salarié . statut cadre',
'Temps partiel': 'contrat salarié . temps partiel',
Localisation: 'établissement . localisation',
Autres: null
}
const QuickLink = ({
const QuickLinks = ({
startConversation,
location,
validInputEntered
validInputEntered,
quickLinks,
conversationStarted
}: Props) => {
const show = !location.pathname.endsWith('/simulation') && validInputEntered
const show = validInputEntered && !conversationStarted
console.log(quickLinks)
return (
<Spring
to={{
@ -69,10 +64,12 @@ export default compose(
connect(
(state, props) => ({
key: props.language,
validInputEntered: validInputEnteredSelector(state)
validInputEntered: validInputEnteredSelector(state),
conversationStarted: state.conversationStarted,
quickLinks: state.simulation?.config["questions à l'affiche"]
}),
{
startConversation
}
)
)(QuickLink)
)(QuickLinks)

View File

@ -3,13 +3,9 @@
import Distribution from 'Components/Distribution'
import PaySlip from 'Components/PaySlip'
import SearchButton from 'Components/SearchButton'
import withTracker from 'Components/utils/withTracker'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import ficheDePaieSelectors from 'Selectors/ficheDePaieSelectors'
import './ResultView.css'
import './SalaryCompactExplanation.css'
import type { Tracker } from 'Components/utils/withTracker'
@ -27,7 +23,7 @@ const resultViewTitle = {
payslip: 'Fiche de paie'
}
class ResultView extends Component<Props, State> {
export default class SalaryCompactExplanation extends Component<Props, State> {
state = {
resultView: this.props.conversationStarted ? 'payslip' : 'distribution'
}
@ -67,15 +63,3 @@ class ResultView extends Component<Props, State> {
)
}
}
export default compose(
withTracker,
connect(
state => ({
conversationStarted: state.conversationStarted,
key: state.conversationStarted,
displayResults: !!ficheDePaieSelectors(state)
}),
{}
)
)(ResultView)

View File

@ -0,0 +1,41 @@
import React, { Component } from 'react'
import withTracker from 'Components/utils/withTracker'
import { compose } from 'ramda'
import { connect } from 'react-redux'
import ficheDePaieSelectors from 'Selectors/ficheDePaieSelectors'
import './SalaryCompactExplanation.css'
import SalaryFirstExplanation from './SalaryFirstExplanation'
import SalaryCompactExplanation from './SalaryCompactExplanation'
import * as Animate from 'Ui/animate'
import { startConversation } from 'Actions/actions'
import { formValueSelector } from 'redux-form'
export default compose(
withTracker,
connect(
state => ({
conversationStarted: state.conversationStarted,
displayResults: !!ficheDePaieSelectors(state),
arePreviousAnswers: state.conversationSteps.foldedSteps.length > 0,
period: formValueSelector('conversation')(state, 'période')
}),
{
startConversation
}
)
)(
class SalaryExplanation extends Component {
render() {
return (
<Animate.fromBottom>
{!this.props.conversationStarted ? (
<SalaryFirstExplanation {...this.props} />
) : (
<SalaryCompactExplanation {...this.props} />
)}
<div style={{ textAlign: 'center' }} />
</Animate.fromBottom>
)
}
}
)

View File

@ -0,0 +1,58 @@
/* @flow */
import Distribution from 'Components/Distribution'
import PaySlip from 'Components/PaySlip'
import SearchButton from 'Components/SearchButton'
import withTracker from 'Components/utils/withTracker'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import ficheDePaieSelectors from 'Selectors/ficheDePaieSelectors'
import type { Tracker } from 'Components/utils/withTracker'
export default class SalaryFirstExplanation extends Component<Props, State> {
render() {
return (
<>
<h2>
<Trans>A quoi servent mes cotisations ?</Trans>
</h2>
<Distribution />
{!(this.props.arePreviousAnswers && this.props.conversationStarted) && (
<>
<h2>
<Trans>Simulation personnalisée</Trans>
</h2>
<p>
<Trans i18nKey="custom-simulation">
Il s'agit pour l'instant d'une
<strong> première estimation</strong> sur la base d'un contrat
générique. La législation française prévoit une multitude de cas
particuliers et de règles spécifiques qui modifient
considérablement les montants de l'embauche.
</Trans>
</p>
<p style={{ textAlign: 'center' }}>
<button
className="ui__ button"
onClick={this.props.startConversation}>
<Trans>Faire une simulation personnalisée</Trans>
</button>
</p>
</>
)}
<h2>
<Trans>
{this.props.period === 'mois'
? 'Fiche de paie mensuelle'
: 'Détail annuel des cotisations'}
</Trans>
</h2>
<PaySlip />
</>
)
}
}

View File

@ -1,239 +0,0 @@
import { startConversation } from 'Actions/actions'
import { Component, React, T } from 'Components'
import AnswerList from 'Components/AnswerList'
import Distribution from 'Components/Distribution'
import PaySlip from 'Components/PaySlip'
import { ScrollToTop } from 'Components/utils/Scroll'
import withColours from 'Components/utils/withColours'
import withLanguage from 'Components/utils/withLanguage'
import withSitePaths from 'Components/utils/withSitePaths'
import { compose } from 'ramda'
import emoji from 'react-easy-emoji'
import { Trans, withNamespaces } from 'react-i18next'
import { connect } from 'react-redux'
import { Redirect, withRouter } from 'react-router'
import { Link } from 'react-router-dom'
import { formValueSelector } from 'redux-form'
import {
blockingInputControlsSelector,
nextStepsSelector,
noUserInputSelector,
validInputEnteredSelector
} from 'Selectors/analyseSelectors'
import * as Animate from 'Ui/animate'
import { normalizeBasePath } from '../utils'
import Conversation from './conversation/Conversation'
import PageFeedback from './Feedback/PageFeedback'
import QuickLink from './QuickLink'
import ResultView from './ResultView'
import './SalarySimulation.css'
import TargetSelection from './TargetSelection'
export default compose(
withRouter,
withColours,
withNamespaces(), // Triggers rerender when the language changes
connect(
state => ({
blockingInputControls: blockingInputControlsSelector(state),
conversationStarted: state.conversationStarted,
validInputEntered: validInputEnteredSelector(state),
arePreviousAnswers: state.conversationSteps.foldedSteps.length !== 0,
nextSteps: state.conversationStarted && nextStepsSelector(state),
noUserInput: noUserInputSelector(state),
period: formValueSelector('conversation')(state, 'période')
}),
{
startConversation
}
),
withLanguage,
withSitePaths
)(
class Simulation extends Component {
state = {
displayPreviousAnswers: false
}
render() {
let {
colours,
conversationStarted,
arePreviousAnswers,
nextSteps,
blockingInputControls,
displayHiringProcedures,
match,
validInputEntered,
period,
location,
sitePaths,
noUserInput
} = this.props
const displayConversation = conversationStarted && !blockingInputControls
const simulationCompleted =
!blockingInputControls && conversationStarted && !nextSteps.length
const displayPreviousAnswers =
arePreviousAnswers && this.state.displayPreviousAnswers
const simulationHomePath = normalizeBasePath(match.path).replace(
/\/simulation\/$/,
''
)
return (
<>
<div id="simu">
{noUserInput && (
<p
id="updateMessage"
style={{ fontStyle: 'italic', textAlign: 'center' }}>
{emoji('🌟')}{' '}
<T k="maj2019">
Le simulateur est à jour aux taux 2019 {' '}
<a href="https://github.com/betagouv/syso/issues/441">
détails
</a>
</T>
</p>
)}
<QuickLink />
{location.pathname.endsWith('/simulation') && (
<>
{!conversationStarted && <Redirect to={simulationHomePath} />}
<Link to={simulationHomePath} style={{ position: 'absolute' }}>
<i
className="fa fa-arrow-left"
aria-hidden="true"
style={{ marginRight: '0.5rem' }}
/>
<Trans>Retour</Trans>
</Link>
<div
className="change-answer-link"
style={{
visibility: arePreviousAnswers ? 'visible' : 'hidden'
}}>
<button
className="ui__ link-button"
onClick={() =>
this.setState({ displayPreviousAnswers: true })
}>
<Trans>Modifier mes réponses</Trans>
</button>
</div>
{displayPreviousAnswers && (
<AnswerList
onClose={() =>
this.setState({ displayPreviousAnswers: false })
}
/>
)}
{simulationCompleted && (
<>
<h1>
<Trans i18nKey="simulation-end.title">
Plus de questions !
</Trans>
</h1>
<p>
<Trans i18nKey="simulation-end.text">
Vous avez atteint l'estimation la plus précise. Vous
pouvez maintenant concrétiser votre projet d'embauche.
</Trans>
</p>
{displayHiringProcedures && (
<div style={{ textAlign: 'center' }}>
<Link
className="ui__ button"
to={sitePaths.démarcheEmbauche}>
<Trans i18nKey="simulation-end.cta">
Connaître les démarches
</Trans>
</Link>
</div>
)}
<br />
</>
)}
{displayConversation && (
<>
<ScrollToTop />
<Conversation
textColourOnWhite={colours.textColourOnWhite}
/>
</>
)}
</>
)}
<TargetSelection colours={colours} />
{location.pathname.endsWith('/simulation') && (
<>
{conversationStarted && (
<Animate.fromBottom>
<ResultView />
<div style={{ textAlign: 'center' }} />
</Animate.fromBottom>
)}
</>
)}
{validInputEntered && (
<PageFeedback
customMessage={
<Trans i18nKey="feedback.simulator">
Ce simulateur vous a plu ?
</Trans>
}
customEventName="rate simulator"
/>
)}
</div>
{!location.pathname.endsWith('/simulation') && validInputEntered && (
<Animate.fromBottom>
<div style={{ textAlign: 'center' }}>
{arePreviousAnswers && conversationStarted && (
<button className="ui__ button" onClick={startConversation}>
<Trans>Continuer la simulation</Trans>
</button>
)}
</div>
<h2>
<Trans>A quoi servent mes cotisations ?</Trans>
</h2>
<Distribution />
{!(arePreviousAnswers && conversationStarted) && (
<>
<h2>
<Trans>Simulation personnalisée</Trans>
</h2>
<p>
<Trans i18nKey="custom-simulation">
Il s'agit pour l'instant d'une
<strong> première estimation</strong> sur la base d'un
contrat générique. La législation française prévoit une
multitude de cas particuliers et de règles spécifiques qui
modifient considérablement les montants de l'embauche.
</Trans>
</p>
<p style={{ textAlign: 'center' }}>
<button className="ui__ button" onClick={startConversation}>
<Trans>Faire une simulation personnalisée</Trans>
</button>
</p>
</>
)}
<h2>
<Trans>
{period === 'mois'
? 'Fiche de paie mensuelle'
: 'Détail annuel des cotisations'}
</Trans>
</h2>
<PaySlip />
</Animate.fromBottom>
)}
</>
)
}
}
)

View File

@ -7,30 +7,38 @@ import React from 'react'
import { connect } from 'react-redux'
import {
nextStepsSelector,
noUserInputSelector
noUserInputSelector,
blockingInputControlsSelector,
validInputEnteredSelector
} from 'Selectors/analyseSelectors'
import Animate from 'Ui/animate'
export default compose(
withColours,
connect(state => ({
conversationStarted: state.conversationStarted,
previousAnswers: state.conversationSteps.foldedSteps,
noNextSteps: nextStepsSelector(state).length == 0,
noUserInput: noUserInputSelector(state)
noUserInput: noUserInputSelector(state),
blockingInputControls: blockingInputControlsSelector(state),
validInputEntered: validInputEnteredSelector(state)
}))
)(
class SimpleSimulation extends React.Component {
class Simulation extends React.Component {
state = {
displayAnswers: false
}
render() {
let {
children,
noNextSteps,
previousAnswers,
noUserInput,
hideUntilUserInput
conversationStarted,
hideUntilUserInput,
blockingInputControls
} = this.props
let arePreviousAnswers = previousAnswers.length > 0,
displayConversation = conversationStarted && !blockingInputControls
return (
<>
{this.state.displayAnswers && (
@ -39,24 +47,32 @@ export default compose(
{!isEmpty(previousAnswers) && (
<button
className="ui__ button small plain"
onClick={() => this.setState({ displayAnswers: true })}>
Mes réponses
style={{
visibility: arePreviousAnswers ? 'visible' : 'hidden'
}}>
onClick={() => this.setState({ displayAnswers: true })}> Mes
réponses
</button>
)}
<ScrollToElement>
<Conversation
textColourOnWhite={this.props.colours.textColourOnWhite}
/>
{noNextSteps && (
<>
<h2>Plus de questions ! </h2>
<p>Vous avez atteint l'estimation la plus précise.</p>
</>
)}
{(!hideUntilUserInput || !noUserInput) && (
<Animate.fromBottom>{children}</Animate.fromBottom>
)}
</ScrollToElement>
{displayConversation && (
<>
<Conversation
textColourOnWhite={this.props.colours.textColourOnWhite}
/>
{noNextSteps && (
<>
<h2>Plus de questions ! </h2>
<p>Vous avez atteint l'estimation la plus précise.</p>
{this.props.customEndMessage && (
<p>{this.props.customEndMessage}</p>
)}
</>
)}
</>
)}
<Animate.fromBottom>{this.props.targets}</Animate.fromBottom>
{!noUserInput && this.props.explication}
</>
)
}

View File

@ -0,0 +1,94 @@
import Answers from 'Components/AnswerList'
import Conversation from 'Components/conversation/Conversation'
import { ScrollToElement } from 'Components/utils/Scroll'
import withColours from 'Components/utils/withColours'
import { compose, isEmpty } from 'ramda'
import { React, T } from 'Components'
import { connect } from 'react-redux'
import {
nextStepsSelector,
noUserInputSelector,
blockingInputControlsSelector,
validInputEnteredSelector
} from 'Selectors/analyseSelectors'
import Animate from 'Ui/animate'
import PageFeedback from 'Components/Feedback/PageFeedback'
export default compose(
withColours,
connect(state => ({
conversationStarted: state.conversationStarted,
previousAnswers: state.conversationSteps.foldedSteps,
noNextSteps:
state.conversationStarted && nextStepsSelector(state).length == 0,
noUserInput: noUserInputSelector(state),
blockingInputControls: blockingInputControlsSelector(state),
validInputEntered: validInputEnteredSelector(state)
}))
)(
class Simulation extends React.Component {
state = {
displayAnswers: false
}
render() {
let {
noNextSteps,
previousAnswers,
noUserInput,
conversationStarted,
hideUntilUserInput,
blockingInputControls,
validInputEntered
} = this.props
let arePreviousAnswers = previousAnswers.length > 0,
displayConversation = conversationStarted && !blockingInputControls
return (
<>
{this.state.displayAnswers && (
<Answers onClose={() => this.setState({ displayAnswers: false })} />
)}
{!isEmpty(previousAnswers) && (
<button
className="ui__ button small plain"
style={{
visibility: arePreviousAnswers ? 'visible' : 'hidden'
}}
onClick={() => this.setState({ displayAnswers: true })}>
{' '}
Mes réponses
</button>
)}
{displayConversation && (
<>
<Conversation
textColourOnWhite={this.props.colours.textColourOnWhite}
/>
{noNextSteps && (
<>
<h1>
<T k="simulation-end.title">Plus de questions !</T>
</h1>
<T k="simulation-end.text">
Vous avez atteint l'estimation la plus précise. Vous
</T>
{this.props.customEndMessage}
</>
)}
</>
)}
<Animate.fromBottom>{this.props.targets}</Animate.fromBottom>
{validInputEntered && (
<PageFeedback
customMessage={
<T k="feedback.simulator">Ce simulateur vous a plu ?</T>
}
customEventName="rate simulator"
/>
)}
{!noUserInput && this.props.explanation}
</>
)
}
}
)

View File

@ -22,6 +22,7 @@ import AnimatedTargetValue from 'Ui/AnimatedTargetValue'
import CurrencyInput from './CurrencyInput/CurrencyInput'
import ProgressCircle from './ProgressCircle'
import './TargetSelection.css'
import QuickLinks from './QuickLinks'
export default compose(
translate(),
@ -56,6 +57,7 @@ export default compose(
let { colours } = this.props
return (
<div id="targetSelection">
<QuickLinks />
{/* <Controls {...{ controls }} /> */}
<section
id="targetsContainer"

View File

@ -6,6 +6,10 @@ questions:
- entreprise . charges
- entreprise . catégorie d'activité
questions à l'affiche:
Charges: entreprise . charges
Commerçant, artisan, ou libéral ?: entreprise . catégorie d'activité
situation:
indépendant: oui
micro entreprise: non

View File

@ -3,6 +3,13 @@ objectifs:
- contrat salarié . salaire . brut de base
- contrat salarié . salaire . net
- contrat salarié . salaire . net après impôt
questions à l'affiche:
CDD: contrat salarié . CDD
Cadre: contrat salarié . statut cadre
Temps partiel: contrat salarié . temps partiel
Localisation: établissement . localisation
situation:
contrat salarié: oui
contrat salarié . assimilé salarié: non

View File

@ -123,8 +123,10 @@ Mes réponses: My answers
Modifier mes réponses: Change my answers
simulation-end:
title: No more questions left!
text: You have reached the most accurate estimate. You can now turn your hiring project into reality.
cta: Know the procedures
text: You have reached the most accurate estimate.
hiring:
text: You can now turn your hiring project into reality.
cta: Know the procedures
feedback:
simulator: Do you like this simulator ?
reportError: Report an error

View File

@ -1,5 +1,5 @@
import PreviousSimulationBanner from 'Components/PreviousSimulationBanner'
import Simu from 'Components/SalarySimulation'
import { SalarySimulation } from '../../mycompanyinfrance.fr/pages/SocialSecurity/Salarié'
import salariéConfig from 'Components/simulationConfigs/salarié.yaml'
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
import Marianne from 'Images/marianne.svg'
@ -12,7 +12,7 @@ import './Home.css'
const Home = () => (
<div id="home" className="ui__ container">
<PreviousSimulationBanner />
<Simu />
<SalarySimulation />
<div id="logos">
<a
id="marianne"

View File

@ -1,4 +1,4 @@
import SimpleSimulation from 'Components/SimpleSimulation'
import Simulation from 'Components/Simulation'
import indépendantConfig from 'Components/simulationConfigs/indépendant.yaml'
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
import TargetSelection from 'Components/TargetSelection'
@ -26,14 +26,18 @@ const Indépendant = () => (
<li> gérants et associés de SNC et EURL</li>
<li> gérant majoritaire de SARL</li>
</ul>
<SimpleSimulation>
<TargetSelection keepFormValues />
</SimpleSimulation>
<p>
La sécurité sociale des indépendants ne couvre ni les accidents du
travail, ni la perte d'emploi (assurance-chômage). Pour être couvert, le
professionnel peut souscrire volontairement des assurances spécifiques.
</p>
<Simulation
targetsTriggerConversation={true}
targets={<TargetSelection />}
explication={
<p>
La sécurité sociale des indépendants ne couvre ni les accidents du
travail, ni la perte d'emploi (assurance-chômage). Pour être couvert,
le professionnel peut souscrire volontairement des assurances
spécifiques.
</p>
}
/>
</>
)
export default withSimulationConfig(indépendantConfig)(Indépendant)

View File

@ -1,8 +1,33 @@
import SalarySimulation from 'Components/SalarySimulation'
import Simulation from 'Components/Simulation'
import salariéConfig from 'Components/simulationConfigs/salarié.yaml'
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
import React from 'react'
import { React, T } from 'Components'
import { Helmet } from 'react-helmet'
import TargetSelection from 'Components/TargetSelection'
import SalaryExplanation from 'Components/SalaryExplanation'
import { Link } from 'react-router-dom'
import sitePaths from '../../sitePaths'
export let SalarySimulation = () => (
<Simulation
targetsTriggerConversation={true}
customEndMessages={
<>
<T k="simulation-end.hiring.text">
Vous avez atteint l'estimation la plus précise. Vous pouvez maintenant
concrétiser votre projet d'embauche.
</T>
<div style={{ textAlign: 'center' }}>
<Link className="ui__ button" to={sitePaths().démarcheEmbauche}>
<T k="simulation-end.cta">Connaître les démarches</T>
</Link>
</div>
</>
}
targets={<TargetSelection />}
explanation={<SalaryExplanation />}
/>
)
const Salarié = () => (
<>