From 2f43d541e71cbaeeaeb6aedb6f78b85ea458e4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Thu, 24 Mar 2022 14:42:58 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Refacto=20de=20Exon=C3=A9rationCovi?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exoneration-covid/index.d.ts | 6 +- .../source/components/utils/EngineContext.tsx | 19 +- .../components/utils/SituationContext.tsx | 6 +- .../ExonerationCovid/ExonérationCovid.tsx | 149 +++++++++++++++ .../ExonerationCovid/FormulaireS1S1Bis.tsx | 20 +- .../ExonerationCovid/FormulaireS2.tsx | 14 +- .../Simulateurs/ExonerationCovid/Table.tsx | 16 +- .../Simulateurs/ExonerationCovid/index.tsx | 176 +++--------------- 8 files changed, 208 insertions(+), 198 deletions(-) create mode 100644 site/source/pages/Simulateurs/ExonerationCovid/ExonérationCovid.tsx diff --git a/exoneration-covid/index.d.ts b/exoneration-covid/index.d.ts index 910d25c17..c284e03f0 100644 --- a/exoneration-covid/index.d.ts +++ b/exoneration-covid/index.d.ts @@ -2,9 +2,9 @@ // sub-section of them. We might support "code-splitting" the rules in the // future. import { Rule } from 'publicodes' -import { Names } from './dist/names' +import { Names as ExoCovidDottedNames } from './dist/names' -export type DottedNames = Names -declare let rules: Record +declare let rules: Record +export type { ExoCovidDottedNames } export default rules diff --git a/site/source/components/utils/EngineContext.tsx b/site/source/components/utils/EngineContext.tsx index 3c6de6393..d1386b76d 100644 --- a/site/source/components/utils/EngineContext.tsx +++ b/site/source/components/utils/EngineContext.tsx @@ -1,6 +1,6 @@ import { DottedName } from 'modele-social' import Engine, { PublicodesExpression, Rule } from 'publicodes' -import React, { createContext, useContext, useRef } from 'react' +import React, { createContext, useContext } from 'react' import i18n from '../../locales/i18n' export type Rules = Record @@ -23,21 +23,8 @@ export function engineFactory(rules: Rules, options = {}) { export const EngineContext = createContext(new Engine()) export const EngineProvider = EngineContext.Provider -export function useEngine() { - return useContext(EngineContext) as Engine -} - -/** - * Use this hooks to keep state of engine with the react fast refresh - * @param originalEngine - * @returns engine - */ -export const useEngineKeepState = ( - originalEngine: Engine -) => { - const { current: engine } = useRef(originalEngine) - - return engine +export function useEngine() { + return useContext(EngineContext) as Engine } type SituationProviderProps = { diff --git a/site/source/components/utils/SituationContext.tsx b/site/source/components/utils/SituationContext.tsx index cbdded1c4..3b28500d3 100644 --- a/site/source/components/utils/SituationContext.tsx +++ b/site/source/components/utils/SituationContext.tsx @@ -1,4 +1,4 @@ -import { PublicodesExpression } from 'publicodes' +import Engine, { PublicodesExpression } from 'publicodes' import { createContext, Dispatch, @@ -7,7 +7,6 @@ import { useRef, useState, } from 'react' -import { useEngine } from './EngineContext' export type Situation = Partial< Record @@ -28,10 +27,9 @@ export interface SituationState { * @returns situation state */ export const useSynchronizedSituationState = ( + engine: Engine, defaultSituation: Situation | (() => Situation) = {} ): SituationState => { - const engine = useEngine() - const [localSituation, setLocalSituation] = useState>(defaultSituation) diff --git a/site/source/pages/Simulateurs/ExonerationCovid/ExonérationCovid.tsx b/site/source/pages/Simulateurs/ExonerationCovid/ExonérationCovid.tsx new file mode 100644 index 000000000..596fff9a9 --- /dev/null +++ b/site/source/pages/Simulateurs/ExonerationCovid/ExonérationCovid.tsx @@ -0,0 +1,149 @@ +import RuleInput from '@/components/conversation/RuleInput' +import { + SituationStateProvider, + useSynchronizedSituationState, +} from '@/components/utils/SituationContext' +import { Button } from '@/design-system/buttons' +import { Spacing } from '@/design-system/layout' +import { H3 } from '@/design-system/typography/heading' +import { Grid } from '@mui/material' +import { ExoCovidDottedNames } from 'exoneration-covid' +import { PublicodesExpression } from 'publicodes' +import { useCallback, useEffect } from 'react' +import { Trans } from 'react-i18next' +import { useLocation } from 'react-router' +import { useExoCovidEngine } from '.' +import { FormulaireS1S1Bis } from './FormulaireS1S1Bis' +import { FormulaireS2 } from './FormulaireS2' + +const rootDottedNames = [ + 'secteur', + "début d'activité", + "lieu d'exercice", +] as const + +export const ExonérationCovid = () => { + const location = useLocation() + const searchParams = new URLSearchParams(location.search) + const params = Object.fromEntries(searchParams.entries()) as { + [key in typeof rootDottedNames[number]]?: string + } + + useEffect(() => { + window.scrollTo(0, 0) + }, [location]) + + const engine = useExoCovidEngine() + const situationState = useSynchronizedSituationState(engine, params) + const { situation, setSituation } = situationState + + const updateSituation = useCallback( + (name: ExoCovidDottedNames, value: PublicodesExpression | undefined) => { + setSituation({ ...situation, [name]: value }) + }, + [setSituation, situation] + ) + + const setStep1Situation = useCallback(() => { + const step1Situation = Object.fromEntries( + Object.entries(situation).filter(([dotName]) => + (rootDottedNames as readonly string[]).includes(dotName) + ) + ) + setSituation(step1Situation) + }, [setSituation, situation]) + + const step2 = rootDottedNames.every((names) => params[names]) + + return ( + + {step2 ? ( + engine.evaluate('secteur').nodeValue !== 'S2' ? ( + + ) : ( + + ) + ) : ( + <> + +

{engine.getRule('secteur').rawNode.question}

+
+ + + updateSituation('secteur', value)} + /> + + + +

{engine.getRule("début d'activité").rawNode.question}

+
+ + + updateSituation("début d'activité", value)} + /> + + + +

{engine.getRule("lieu d'exercice").rawNode.question}

+
+ + + updateSituation("lieu d'exercice", value)} + /> + + + )} + + + + + + {step2 ? ( + + ) : ( + + )} + + + + +
+ ) +} diff --git a/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS1S1Bis.tsx b/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS1S1Bis.tsx index 207e5e312..ae44eef6e 100644 --- a/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS1S1Bis.tsx +++ b/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS1S1Bis.tsx @@ -1,5 +1,4 @@ import Value from '@/components/EngineValue' -import { EngineContext } from '@/components/utils/EngineContext' import { Situation, useSituationState, @@ -8,16 +7,16 @@ import { Spacing } from '@/design-system/layout' import { H3 } from '@/design-system/typography/heading' import { Li, Ul } from '@/design-system/typography/list' import { Grid } from '@mui/material' -import { DottedNames } from 'exoneration-covid' +import { ExoCovidDottedNames } from 'exoneration-covid' import Engine, { EvaluatedNode, PublicodesExpression } from 'publicodes' -import { useContext } from 'react' import { Trans } from 'react-i18next' +import { useExoCovidEngine } from '.' import { Bold, GridTotal, Italic, Recap, RecapExpert, Total } from './Recap' import { Row, Table, Tbody, Th, Thead, Tr } from './Table' const getTotalByMonth = ( - engine: Engine, - situation: Situation + engine: Engine, + situation: Situation ) => { const ret = Object.fromEntries( Object.entries(situation) @@ -45,12 +44,15 @@ const getTotalByMonth = ( } interface Props { - onChange?: (dottedName: DottedNames, value: PublicodesExpression) => void + onChange?: ( + dottedName: ExoCovidDottedNames, + value: PublicodesExpression + ) => void } export const FormulaireS1S1Bis = ({ onChange }: Props) => { - const engine = useContext(EngineContext) as Engine - const { situation = {} } = useSituationState() + const engine = useExoCovidEngine() + const { situation = {} } = useSituationState() const totalByMonth = getTotalByMonth(engine, situation) ?? {} @@ -111,7 +113,7 @@ export const FormulaireS1S1Bis = ({ onChange }: Props) => { total={totalByMonth[dotName]} onSelectionChange={(key) => { const val = (key as string).replace(/\.\d+$/, '') - onChange?.(dotName as DottedNames, `'${val}'`) + onChange?.(dotName as ExoCovidDottedNames, `'${val}'`) }} key={dotName} /> diff --git a/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS2.tsx b/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS2.tsx index 97832d948..cf0afe1f5 100644 --- a/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS2.tsx +++ b/site/source/pages/Simulateurs/ExonerationCovid/FormulaireS2.tsx @@ -1,16 +1,15 @@ import Value from '@/components/EngineValue' -import { EngineContext } from '@/components/utils/EngineContext' import { Radio, ToggleGroup } from '@/design-system/field' import { Spacing } from '@/design-system/layout' import { H3 } from '@/design-system/typography/heading' import { Li } from '@/design-system/typography/list' import { Body } from '@/design-system/typography/paragraphs' import { Grid } from '@mui/material' -import { DottedNames } from 'exoneration-covid' -import Engine, { Evaluation, PublicodesExpression } from 'publicodes' -import { useContext } from 'react' +import { ExoCovidDottedNames } from 'exoneration-covid' +import { Evaluation, PublicodesExpression } from 'publicodes' import { Trans, useTranslation } from 'react-i18next' import styled from 'styled-components' +import { useExoCovidEngine } from '.' import { Bold, GridTotal, Italic, Recap, RecapExpert, Total } from './Recap' const Info = styled(Body)` @@ -23,9 +22,12 @@ const Info = styled(Body)` export const FormulaireS2 = ({ onChange, }: { - onChange?: (dottedName: DottedNames, value: PublicodesExpression) => void + onChange?: ( + dottedName: ExoCovidDottedNames, + value: PublicodesExpression + ) => void }) => { - const engine = useContext(EngineContext) as Engine + const engine = useExoCovidEngine() const { t } = useTranslation() const monthNames = [ diff --git a/site/source/pages/Simulateurs/ExonerationCovid/Table.tsx b/site/source/pages/Simulateurs/ExonerationCovid/Table.tsx index 4c7a16394..6f83c9a97 100644 --- a/site/source/pages/Simulateurs/ExonerationCovid/Table.tsx +++ b/site/source/pages/Simulateurs/ExonerationCovid/Table.tsx @@ -1,12 +1,12 @@ import { Item, Select } from '@/design-system/field/Select' import { baseParagraphStyle } from '@/design-system/typography/paragraphs' import { getMeta } from '@/utils' -import { Key, useContext } from 'react' +import { ExoCovidDottedNames } from 'exoneration-covid' +import { EvaluatedNode, formatValue } from 'publicodes' +import { Key } from 'react' import { Trans, useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' -import { DottedNames } from 'exoneration-covid' -import { EngineContext } from '@/components/utils/EngineContext' -import Engine, { EvaluatedNode, formatValue } from 'publicodes' +import { useExoCovidEngine } from '.' export const Th = styled.th<{ alignSelf?: string }>` flex: 2; @@ -94,7 +94,7 @@ const Empty = styled.div` ` type RowProps = { - dottedNames: DottedNames[] + dottedNames: ExoCovidDottedNames[] actualMonth: string title?: string total?: EvaluatedNode @@ -112,7 +112,7 @@ export const Row = ({ }: RowProps) => { const { t } = useTranslation() - const engine = useContext(EngineContext) as Engine + const engine = useExoCovidEngine() const choices = { non: [t('Aucun')], @@ -141,7 +141,9 @@ export const Row = ({ : true) ) .flatMap((node) => { - const name = (actualMonth + ' . ' + node.dottedName) as DottedNames + const name = (actualMonth + + ' . ' + + node.dottedName) as ExoCovidDottedNames const rawNode = engine.getRule(name).rawNode type Meta = { "baisse d'au moins"?: string } diff --git a/site/source/pages/Simulateurs/ExonerationCovid/index.tsx b/site/source/pages/Simulateurs/ExonerationCovid/index.tsx index 7005827a0..924955f0a 100644 --- a/site/source/pages/Simulateurs/ExonerationCovid/index.tsx +++ b/site/source/pages/Simulateurs/ExonerationCovid/index.tsx @@ -1,28 +1,28 @@ -import RuleInput from '@/components/conversation/RuleInput' -import { - EngineProvider, - useEngineKeepState, - useEngine, -} from '@/components/utils/EngineContext' -import { - useSynchronizedSituationState, - SituationStateProvider, -} from '@/components/utils/SituationContext' -import { Button } from '@/design-system/buttons' -import { Spacing } from '@/design-system/layout' -import { H3 } from '@/design-system/typography/heading' -import { Grid } from '@mui/material' -import exonerationCovid, { DottedNames } from 'exoneration-covid' -import Engine, { PublicodesExpression } from 'publicodes' -import { useCallback, useEffect } from 'react' -import { Trans } from 'react-i18next' -import { useLocation } from 'react-router' -import { FormulaireS1S1Bis } from './FormulaireS1S1Bis' -import { FormulaireS2 } from './FormulaireS2' +import { EngineContext, EngineProvider } from '@/components/utils/EngineContext' +import exonerationCovid from 'exoneration-covid' +import Engine from 'publicodes' +import { useContext, useRef } from 'react' +import { ExonérationCovid } from './ExonérationCovid' const exoCovidEngine = new Engine(exonerationCovid) -export default function ExonérationCovidProvider() { +export const useExoCovidEngine = () => + useContext(EngineContext) as typeof exoCovidEngine + +/** + * Use this hooks to keep state of engine with the react fast refresh + * @param originalEngine + * @returns engine + */ +export const useEngineKeepState = ( + originalEngine: Engine +) => { + const { current: engine } = useRef(originalEngine) + + return engine +} + +const ExonérationCovidProvider = () => { const engine = useEngineKeepState(exoCovidEngine) return ( @@ -32,134 +32,4 @@ export default function ExonérationCovidProvider() { ) } -const rootDottedNames = [ - 'secteur', - "début d'activité", - "lieu d'exercice", -] as const - -const ExonérationCovid = () => { - const location = useLocation() - const searchParams = new URLSearchParams(location.search) - const params = Object.fromEntries(searchParams.entries()) as { - [key in typeof rootDottedNames[number]]?: string - } - - useEffect(() => { - window.scrollTo(0, 0) - }, [location]) - - const engine = useEngine() - const situationState = useSynchronizedSituationState(params) - const { situation, setSituation } = situationState - - const updateSituation = useCallback( - (name: DottedNames, value: PublicodesExpression | undefined) => { - setSituation({ ...situation, [name]: value }) - }, - [setSituation, situation] - ) - - const setStep1Situation = useCallback(() => { - const step1Situation = Object.fromEntries( - Object.entries(situation).filter(([dotName]) => - (rootDottedNames as readonly string[]).includes(dotName) - ) - ) - setSituation(step1Situation) - }, [setSituation, situation]) - - const step2 = rootDottedNames.every((names) => params[names]) - - return ( - - {step2 ? ( - engine.evaluate('secteur').nodeValue !== 'S2' ? ( - - ) : ( - - ) - ) : ( - <> - -

{engine.getRule('secteur').rawNode.question}

-
- - - updateSituation('secteur', value)} - /> - - - -

{engine.getRule("début d'activité").rawNode.question}

-
- - - updateSituation("début d'activité", value)} - /> - - - -

{engine.getRule("lieu d'exercice").rawNode.question}

-
- - - updateSituation("lieu d'exercice", value)} - /> - - - )} - - - - - - {step2 ? ( - - ) : ( - - )} - - - - -
- ) -} +export default ExonérationCovidProvider