diff --git a/mon-entreprise/source/actions/actions.ts b/mon-entreprise/source/actions/actions.ts index 11c49d9ab..b25fa376b 100644 --- a/mon-entreprise/source/actions/actions.ts +++ b/mon-entreprise/source/actions/actions.ts @@ -1,6 +1,6 @@ import { SitePaths } from 'Components/utils/SitePathsContext' import { History } from 'history' -import { RootState, SimulationConfig } from 'Reducers/rootReducer' +import { RootState, SimulationConfig, Situation } from 'Reducers/rootReducer' import { ThunkAction } from 'redux-thunk' import { DottedName } from 'modele-social' import { deletePersistedSimulation } from '../storage/persistSimulation' @@ -34,17 +34,11 @@ type StepAction = { step: DottedName } -type SetSimulationConfigAction = { - type: 'SET_SIMULATION' - url: string - config: SimulationConfig - useCompanyDetails: boolean -} - type DeletePreviousSimulationAction = { type: 'DELETE_PREVIOUS_SIMULATION' } +type SetSimulationConfigAction = ReturnType type ResetSimulationAction = ReturnType type UpdateAction = ReturnType type UpdateSituationAction = ReturnType @@ -89,19 +83,15 @@ export const setSituationBranch = (id: number) => export const setSimulationConfig = ( config: SimulationConfig, - useCompanyDetails = false -): ThunkResult => (dispatch, getState, { history }): void => { - if (getState().simulation?.config === config) { - return - } - const url = history.location.pathname - dispatch({ + url: string, + initialSituation?: Situation +) => + ({ type: 'SET_SIMULATION', url, - useCompanyDetails, config, - }) -} + initialSituation, + } as const) export const setActiveTarget = (targetName: DottedName) => ({ diff --git a/mon-entreprise/source/components/SchemeComparaison.tsx b/mon-entreprise/source/components/SchemeComparaison.tsx index cb4a64403..0730f9e45 100644 --- a/mon-entreprise/source/components/SchemeComparaison.tsx +++ b/mon-entreprise/source/components/SchemeComparaison.tsx @@ -1,4 +1,3 @@ -import { setSimulationConfig } from 'Actions/actions' import { defineDirectorStatus, isAutoentrepreneur, @@ -19,6 +18,7 @@ import InfoBulle from 'Components/ui/InfoBulle' import './SchemeComparaison.css' import { engineOptions, useEngine } from './utils/EngineContext' import { DottedName } from 'modele-social' +import useSimulationConfig from './utils/useSimulationConfig' type SchemeComparaisonProps = { hideAutoEntrepreneur?: boolean @@ -29,10 +29,8 @@ export default function SchemeComparaison({ hideAutoEntrepreneur = false, hideAssimiléSalarié = false, }: SchemeComparaisonProps) { + useSimulationConfig(dirigeantComparaison) const dispatch = useDispatch() - useEffect(() => { - dispatch(setSimulationConfig(dirigeantComparaison)) - }, []) const engine = useEngine() const plafondAutoEntrepreneurDépassé = engine.evaluate( diff --git a/mon-entreprise/source/components/conversation/Conversation.tsx b/mon-entreprise/source/components/conversation/Conversation.tsx index ba68b388c..288a7801a 100644 --- a/mon-entreprise/source/components/conversation/Conversation.tsx +++ b/mon-entreprise/source/components/conversation/Conversation.tsx @@ -36,6 +36,7 @@ export default function Conversation({ customEndMessages }: ConversationProps) { dispatch(goToQuestion(currentQuestion)) } }, [dispatch, currentQuestion]) + const setDefault = () => dispatch( // TODO: Skiping a question shouldn't be equivalent to answering the @@ -46,6 +47,15 @@ export default function Conversation({ customEndMessages }: ConversationProps) { undefined ) ) + // TODO: Skiping a question shouldn't be equivalent to answering the + // default value (for instance the question shouldn't appear in the + // answered questions). + dispatch({ + type: 'STEP_ACTION', + name: 'fold', + step: currentQuestion, + }) + const goToPrevious = () => dispatch(goToQuestion(previousAnswers.slice(-1)[0])) diff --git a/mon-entreprise/source/components/utils/useSimulationConfig.ts b/mon-entreprise/source/components/utils/useSimulationConfig.ts new file mode 100644 index 000000000..584259823 --- /dev/null +++ b/mon-entreprise/source/components/utils/useSimulationConfig.ts @@ -0,0 +1,41 @@ +import { setSimulationConfig } from 'Actions/actions' +import { useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory } from 'react-router' +import { Company } from 'Reducers/inFranceAppReducer' +import { RootState, SimulationConfig, Situation } from 'Reducers/rootReducer' + +export default function useSituationConfig( + config: SimulationConfig | undefined, + { useExistingCompanyFromSituation = false } = {} +) { + const dispatch = useDispatch() + const url = useHistory().location.pathname + const lastUrl = useSelector((state: RootState) => state.simulation?.url) + const existingCompany = useSelector( + (state: RootState) => state.inFranceApp.existingCompany + ) + const initialSituation = useExistingCompanyFromSituation + ? getCompanySituation(existingCompany) + : undefined + + useEffect(() => { + if (config && url !== lastUrl) { + dispatch(setSimulationConfig(config ?? {}, url, initialSituation)) + } + }, [config, url, lastUrl, initialSituation]) +} + +export function getCompanySituation(company: Company | null): Situation { + return { + ...(company?.localisation && { + 'établissement . localisation': { objet: company.localisation }, + }), + ...(company?.dateDeCréation && { + 'entreprise . date de création': company.dateDeCréation.replace( + /(.*)-(.*)-(.*)/, + '$3/$2/$1' + ), + }), + } +} diff --git a/mon-entreprise/source/reducers/rootReducer.ts b/mon-entreprise/source/reducers/rootReducer.ts index 19391c6f0..357a3258a 100644 --- a/mon-entreprise/source/reducers/rootReducer.ts +++ b/mon-entreprise/source/reducers/rootReducer.ts @@ -7,6 +7,8 @@ import { DottedName } from 'modele-social' import { objectifsSelector } from '../selectors/simulationSelectors' import inFranceAppReducer, { Company } from './inFranceAppReducer' import storageRootReducer from './storageReducer' +import { Names } from 'modele-social/dist/names' +import { getCompanySituation } from 'Components/utils/useSimulationConfig' function explainedVariable( state: DottedName | null = null, @@ -48,18 +50,18 @@ type QuestionsKind = | 'liste' | 'liste noire' -export type SimulationConfig = { - objectifs?: +export type SimulationConfig = Partial<{ + objectifs: | Array | Array<{ icône: string; nom: string; objectifs: Array }> 'objectifs cachés'?: Array situation: Simulation['situation'] - bloquant?: Array - questions?: Partial>> - branches?: Array<{ nom: string; situation: SimulationConfig['situation'] }> + bloquant: Array + questions: Partial>> + branches: Array<{ nom: string; situation: SimulationConfig['situation'] }> 'unité par défaut': string - color?: string -} + color: string +}> export type Situation = Partial> export type Simulation = { @@ -72,44 +74,25 @@ export type Simulation = { foldedSteps: Array unfoldedStep?: DottedName | null } -function getCompanySituation(company: Company | null): Situation { - return { - ...(company?.localisation && { - 'établissement . localisation': { objet: company.localisation }, - }), - ...(company?.dateDeCréation && { - 'entreprise . date de création': company.dateDeCréation.replace( - /(.*)-(.*)-(.*)/, - '$3/$2/$1' - ), - }), - } -} function simulation( state: Simulation | null = null, - action: Action, - existingCompany: Company + action: Action ): Simulation | null { if (action.type === 'SET_SIMULATION') { - if (state && state.config === action.config) { - return state - } - const companySituation = action.useCompanyDetails - ? getCompanySituation(existingCompany) - : {} - const { config, url } = action + const { config, url, initialSituation } = action return { config, url, hiddenNotifications: [], - situation: companySituation, - initialSituation: companySituation, + situation: initialSituation ?? {}, + initialSituation: initialSituation ?? {}, targetUnit: config['unité par défaut'] || '€/mois', - foldedSteps: Object.keys(companySituation) as Array, + foldedSteps: Object.keys(initialSituation ?? {}) as Array, unfoldedStep: null, } } + if (state === null) { return state } @@ -188,22 +171,19 @@ const existingCompanyReducer = (state: RootState, action: Action) => { } return state } -const mainReducer = (state: any, action: Action) => - combineReducers({ - explainedVariable, - // We need to access the `rules` in the simulation reducer - simulation: (a: Simulation | null = null, b: Action): Simulation | null => - simulation(a, b, state?.inFranceApp?.existingCompany), - previousSimulation: defaultTo(null) as Reducer, - situationBranch, - activeTargetInput, - inFranceApp: inFranceAppReducer, - })(state, action) +const mainReducer = combineReducers({ + explainedVariable, + simulation, + previousSimulation: defaultTo(null) as Reducer, + situationBranch, + activeTargetInput, + inFranceApp: inFranceAppReducer, +}) export default reduceReducers( - mainReducer as any, - existingCompanyReducer as any, - storageRootReducer as any + mainReducer, + existingCompanyReducer as Reducer, + storageRootReducer as Reducer ) as Reducer export type RootState = ReturnType diff --git a/mon-entreprise/source/site/pages/Gérer/AideDéclarationIndépendant/index.tsx b/mon-entreprise/source/site/pages/Gérer/AideDéclarationIndépendant/index.tsx index b06b980ca..820ad2177 100644 --- a/mon-entreprise/source/site/pages/Gérer/AideDéclarationIndépendant/index.tsx +++ b/mon-entreprise/source/site/pages/Gérer/AideDéclarationIndépendant/index.tsx @@ -1,4 +1,4 @@ -import { setSimulationConfig, updateSituation } from 'Actions/actions' +import { updateSituation } from 'Actions/actions' import Aide from 'Components/conversation/Aide' import { Explicable, ExplicableRule } from 'Components/conversation/Explicable' import RuleInput from 'Components/conversation/RuleInput' @@ -11,6 +11,7 @@ import { EngineContext, useEngine } from 'Components/utils/EngineContext' import { ScrollToTop } from 'Components/utils/Scroll' import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting' import { useNextQuestions } from 'Components/utils/useNextQuestion' +import useSimulationConfig from 'Components/utils/useSimulationConfig' import { DottedName } from 'modele-social' import { RuleNode } from 'publicodes' import { Fragment, useCallback, useContext, useEffect } from 'react' @@ -24,15 +25,13 @@ import { CompanySection } from '../Home' import simulationConfig from './config.yaml' export default function AideDéclarationIndépendant() { + useSimulationConfig(simulationConfig) const dispatch = useDispatch() const engine = useEngine() const company = useSelector( (state: RootState) => state.inFranceApp.existingCompany ) - useEffect(() => { - dispatch(setSimulationConfig(simulationConfig, true)) - }, [dispatch]) const [resultsRef, resultsInViewPort] = useDisplayOnIntersecting({ threshold: 0.5, diff --git a/mon-entreprise/source/site/pages/Simulateurs/ArtisteAuteur.tsx b/mon-entreprise/source/site/pages/Simulateurs/ArtisteAuteur.tsx index 489d91949..d6a221990 100644 --- a/mon-entreprise/source/site/pages/Simulateurs/ArtisteAuteur.tsx +++ b/mon-entreprise/source/site/pages/Simulateurs/ArtisteAuteur.tsx @@ -1,4 +1,4 @@ -import { setSimulationConfig } from 'Actions/actions' +import useSimulationConfig from 'Components/utils/useSimulationConfig' import { DistributionBranch } from 'Components/Distribution' import Value, { Condition } from 'Components/EngineValue' import SimulateurWarning from 'Components/SimulateurWarning' @@ -16,10 +16,7 @@ import styled from 'styled-components' import config from './configs/artiste-auteur.yaml' export default function ArtisteAuteur() { - const dispatch = useDispatch() - useEffect(() => { - dispatch(setSimulationConfig(config)) - }, []) + useSimulationConfig(config) return ( <> diff --git a/mon-entreprise/source/site/pages/Simulateurs/ISSimulation.tsx b/mon-entreprise/source/site/pages/Simulateurs/ISSimulation.tsx index 4a18475e2..1e30728b2 100644 --- a/mon-entreprise/source/site/pages/Simulateurs/ISSimulation.tsx +++ b/mon-entreprise/source/site/pages/Simulateurs/ISSimulation.tsx @@ -1,4 +1,4 @@ -import { setSimulationConfig, updateSituation } from 'Actions/actions' +import { updateSituation } from 'Actions/actions' import RuleInput from 'Components/conversation/RuleInput' import Value from 'Components/EngineValue' import Notifications from 'Components/Notifications' @@ -6,27 +6,20 @@ import { SimulationGoal, SimulationGoals } from 'Components/SimulationGoals' import Animate from 'Components/ui/animate' import Warning from 'Components/ui/WarningBlock' import { ThemeColorsContext } from 'Components/utils/colors' -import { useContext, useEffect } from 'react' +import useSimulationConfig from 'Components/utils/useSimulationConfig' +import { useContext } from 'react' import emoji from 'react-easy-emoji' import { Trans } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { situationSelector } from 'Selectors/simulationSelectors' -const config = { - color: '', - 'unité par défaut': '€/an', - situation: {}, -} export default function ISSimulation() { - const dispatch = useDispatch() const { color } = useContext(ThemeColorsContext) - useEffect(() => { - // HACK The config is mutated to avoid reseting the situation everytime the - // component is loaded. `setSimulationConfig` relies on config object - // equality. The `setSimulationConfig` design should be improved. - config.color = color - dispatch(setSimulationConfig(config)) - }, []) + useSimulationConfig({ + color, + 'unité par défaut': '€/an', + situation: {}, + }) return ( <> diff --git a/mon-entreprise/source/site/pages/Simulateurs/Page.tsx b/mon-entreprise/source/site/pages/Simulateurs/Page.tsx index c4a91de7d..726b1b7c3 100644 --- a/mon-entreprise/source/site/pages/Simulateurs/Page.tsx +++ b/mon-entreprise/source/site/pages/Simulateurs/Page.tsx @@ -1,9 +1,8 @@ -import { setSimulationConfig } from 'Actions/actions' import { ThemeColorsProvider } from 'Components/utils/colors' import { IsEmbeddedContext } from 'Components/utils/embeddedContext' import Meta from 'Components/utils/Meta' +import useSimulationConfig from 'Components/utils/useSimulationConfig' import { default as React, useContext, useEffect } from 'react' -import { useDispatch } from 'react-redux' import { useLocation } from 'react-router-dom' import { SimulatorData } from './metadata' @@ -16,14 +15,8 @@ export default function SimulateurPage({ seoExplanations, }: SimulatorData[keyof SimulatorData]) { const inIframe = useContext(IsEmbeddedContext) - const dispatch = useDispatch() const fromGérer = !!useLocation<{ fromGérer?: boolean }>().state?.fromGérer - useEffect(() => { - if (!config) { - return - } - dispatch(setSimulationConfig(config, fromGérer)) - }, [config]) + useSimulationConfig(config, { useExistingCompanyFromSituation: fromGérer }) return ( <>