Crée un hook useSituationConfig

pull/1355/head
Maxime Quandalle 2021-01-08 18:03:37 +01:00
parent 935e687373
commit 77add355aa
9 changed files with 102 additions and 101 deletions

View File

@ -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<typeof setSimulationConfig>
type ResetSimulationAction = ReturnType<typeof resetSimulation>
type UpdateAction = ReturnType<typeof updateSituation>
type UpdateSituationAction = ReturnType<typeof updateSituation>
@ -89,19 +83,15 @@ export const setSituationBranch = (id: number) =>
export const setSimulationConfig = (
config: SimulationConfig,
useCompanyDetails = false
): ThunkResult<void> => (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) =>
({

View File

@ -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(

View File

@ -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]))

View File

@ -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'
),
}),
}
}

View File

@ -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<DottedName>
| Array<{ icône: string; nom: string; objectifs: Array<DottedName> }>
'objectifs cachés'?: Array<DottedName>
situation: Simulation['situation']
bloquant?: Array<DottedName>
questions?: Partial<Record<QuestionsKind, Array<DottedName>>>
branches?: Array<{ nom: string; situation: SimulationConfig['situation'] }>
bloquant: Array<DottedName>
questions: Partial<Record<QuestionsKind, Array<DottedName>>>
branches: Array<{ nom: string; situation: SimulationConfig['situation'] }>
'unité par défaut': string
color?: string
}
color: string
}>
export type Situation = Partial<Record<DottedName, any>>
export type Simulation = {
@ -72,44 +74,25 @@ export type Simulation = {
foldedSteps: Array<DottedName>
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<DottedName>,
foldedSteps: Object.keys(initialSituation ?? {}) as Array<Names>,
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<SavedSimulation | null>,
situationBranch,
activeTargetInput,
inFranceApp: inFranceAppReducer,
})(state, action)
const mainReducer = combineReducers({
explainedVariable,
simulation,
previousSimulation: defaultTo(null) as Reducer<SavedSimulation | null>,
situationBranch,
activeTargetInput,
inFranceApp: inFranceAppReducer,
})
export default reduceReducers<RootState>(
mainReducer as any,
existingCompanyReducer as any,
storageRootReducer as any
mainReducer,
existingCompanyReducer as Reducer<RootState>,
storageRootReducer as Reducer<RootState>
) as Reducer<RootState>
export type RootState = ReturnType<typeof mainReducer>

View File

@ -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,

View File

@ -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 (
<>

View File

@ -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 (
<>

View File

@ -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 (
<>