Refacto de ExonérationCovid

pull/2077/head
Jérémy Rialland 2022-03-24 14:42:58 +01:00 committed by Johan Girod
parent 82016d3a17
commit 2f43d541e7
8 changed files with 208 additions and 198 deletions

View File

@ -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<Names, Rule>
declare let rules: Record<ExoCovidDottedNames, Rule>
export type { ExoCovidDottedNames }
export default rules

View File

@ -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<DottedName, Rule>
@ -23,21 +23,8 @@ export function engineFactory(rules: Rules, options = {}) {
export const EngineContext = createContext<Engine>(new Engine())
export const EngineProvider = EngineContext.Provider
export function useEngine<Names extends string = DottedName>() {
return useContext(EngineContext) as Engine<Names>
}
/**
* Use this hooks to keep state of engine with the react fast refresh
* @param originalEngine
* @returns engine
*/
export const useEngineKeepState = <Names extends string>(
originalEngine: Engine<Names>
) => {
const { current: engine } = useRef(originalEngine)
return engine
export function useEngine() {
return useContext(EngineContext) as Engine<DottedName>
}
type SituationProviderProps<Names extends string> = {

View File

@ -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<Names extends string> = Partial<
Record<Names, PublicodesExpression | undefined>
@ -28,10 +27,9 @@ export interface SituationState<Names extends string> {
* @returns situation state
*/
export const useSynchronizedSituationState = <Names extends string>(
engine: Engine<Names>,
defaultSituation: Situation<Names> | (() => Situation<Names>) = {}
): SituationState<Names> => {
const engine = useEngine<Names>()
const [localSituation, setLocalSituation] =
useState<Situation<Names>>(defaultSituation)

View File

@ -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 (
<SituationStateProvider value={situationState}>
{step2 ? (
engine.evaluate('secteur').nodeValue !== 'S2' ? (
<FormulaireS1S1Bis onChange={updateSituation} />
) : (
<FormulaireS2 onChange={updateSituation} />
)
) : (
<>
<Grid item xs={12}>
<H3>{engine.getRule('secteur').rawNode.question}</H3>
</Grid>
<Grid item xs={12} sm={8}>
<RuleInput
dottedName={'secteur'}
onChange={(value) => updateSituation('secteur', value)}
/>
</Grid>
<Grid item xs={12}>
<H3>{engine.getRule("début d'activité").rawNode.question}</H3>
</Grid>
<Grid item xs={12} sm={6}>
<RuleInput
dottedName="début d'activité"
onChange={(value) => updateSituation("début d'activité", value)}
/>
</Grid>
<Grid item xs={12}>
<H3>{engine.getRule("lieu d'exercice").rawNode.question}</H3>
</Grid>
<Grid item xs={12}>
<RuleInput
dottedName="lieu d'exercice"
onChange={(value) => updateSituation("lieu d'exercice", value)}
/>
</Grid>
</>
)}
<Spacing lg />
<Grid container justifyContent={step2 ? '' : 'end'}>
<Grid item xs={6} sm="auto">
{step2 ? (
<Button
size="MD"
to={{
pathname: location.pathname,
search: '',
}}
onClick={setStep1Situation}
>
<Trans>Précédent</Trans>
</Button>
) : (
<Button
size="MD"
isDisabled={!rootDottedNames.every((names) => situation[names])}
{...(rootDottedNames.every((names) => situation[names])
? {
to: () => {
rootDottedNames.forEach((key) =>
searchParams.append(
key,
situation[key]?.toString() ?? ''
)
)
return {
pathname: location.pathname,
search: searchParams.toString(),
}
},
}
: null)}
>
<Trans>Suivant</Trans>
</Button>
)}
</Grid>
</Grid>
<Spacing lg />
</SituationStateProvider>
)
}

View File

@ -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<DottedNames>,
situation: Situation<DottedNames>
engine: Engine<ExoCovidDottedNames>,
situation: Situation<ExoCovidDottedNames>
) => {
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<DottedNames>
const { situation = {} } = useSituationState<DottedNames>()
const engine = useExoCovidEngine()
const { situation = {} } = useSituationState<ExoCovidDottedNames>()
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}
/>

View File

@ -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<DottedNames>
const engine = useExoCovidEngine()
const { t } = useTranslation()
const monthNames = [

View File

@ -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<number>
@ -112,7 +112,7 @@ export const Row = ({
}: RowProps) => {
const { t } = useTranslation()
const engine = useContext(EngineContext) as Engine<DottedNames>
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 }

View File

@ -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 = <Names extends string>(
originalEngine: Engine<Names>
) => {
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<DottedNames>()
const situationState = useSynchronizedSituationState<DottedNames>(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 (
<SituationStateProvider value={situationState}>
{step2 ? (
engine.evaluate('secteur').nodeValue !== 'S2' ? (
<FormulaireS1S1Bis onChange={updateSituation} />
) : (
<FormulaireS2 onChange={updateSituation} />
)
) : (
<>
<Grid item xs={12}>
<H3>{engine.getRule('secteur').rawNode.question}</H3>
</Grid>
<Grid item xs={12} sm={8}>
<RuleInput
dottedName={'secteur'}
onChange={(value) => updateSituation('secteur', value)}
/>
</Grid>
<Grid item xs={12}>
<H3>{engine.getRule("début d'activité").rawNode.question}</H3>
</Grid>
<Grid item xs={12} sm={6}>
<RuleInput
dottedName="début d'activité"
onChange={(value) => updateSituation("début d'activité", value)}
/>
</Grid>
<Grid item xs={12}>
<H3>{engine.getRule("lieu d'exercice").rawNode.question}</H3>
</Grid>
<Grid item xs={12}>
<RuleInput
dottedName="lieu d'exercice"
onChange={(value) => updateSituation("lieu d'exercice", value)}
/>
</Grid>
</>
)}
<Spacing lg />
<Grid container justifyContent={step2 ? '' : 'end'}>
<Grid item xs={6} sm="auto">
{step2 ? (
<Button
size="MD"
to={{
pathname: location.pathname,
search: '',
}}
onClick={setStep1Situation}
>
<Trans>Précédent</Trans>
</Button>
) : (
<Button
size="MD"
isDisabled={!rootDottedNames.every((names) => situation[names])}
{...(rootDottedNames.every((names) => situation[names])
? {
to: () => {
rootDottedNames.forEach((key) =>
searchParams.append(
key,
situation[key]?.toString() ?? ''
)
)
return {
pathname: location.pathname,
search: searchParams.toString(),
}
},
}
: null)}
>
<Trans>Suivant</Trans>
</Button>
)}
</Grid>
</Grid>
<Spacing lg />
</SituationStateProvider>
)
}
export default ExonérationCovidProvider