Wip test
parent
54bcc94227
commit
00bad59fbc
|
@ -84,6 +84,7 @@ module.exports = {
|
|||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
|
||||
'@typescript-eslint/member-delimiter-style': [
|
||||
'error',
|
||||
|
|
|
@ -3,7 +3,7 @@ import { join, resolve } from 'path'
|
|||
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
import { PageConfig } from '@/pages/simulateurs/_configs/types'
|
||||
import type { PageConfig } from '@/pages/simulateurs/_configs/types'
|
||||
|
||||
import { objectTransform } from '../source/utils'
|
||||
|
||||
|
|
|
@ -1,33 +1,23 @@
|
|||
import { ErrorBoundary } from '@sentry/react'
|
||||
import { FallbackRender } from '@sentry/react/types/errorboundary'
|
||||
import rules from 'modele-social'
|
||||
import { ComponentProps, StrictMode, useMemo } from 'react'
|
||||
import { ComponentProps, StrictMode, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import { css, styled } from 'styled-components'
|
||||
|
||||
import Footer from '@/components/layout/Footer/Footer'
|
||||
import Header from '@/components/layout/Header'
|
||||
import {
|
||||
engineFactory,
|
||||
EngineProvider,
|
||||
Rules,
|
||||
useEngine,
|
||||
useSetupSafeSituation,
|
||||
} from '@/components/utils/EngineContext'
|
||||
import { Container } from '@/design-system/layout'
|
||||
import { useAxeCoreAnalysis } from '@/hooks/useAxeCoreAnalysis'
|
||||
import { useGetFullURL } from '@/hooks/useGetFullURL'
|
||||
import { useIsEmbedded } from '@/hooks/useIsEmbedded'
|
||||
import { useLazyPromise } from '@/hooks/usePromise'
|
||||
import { useSaveAndRestoreScrollPosition } from '@/hooks/useSaveAndRestoreScrollPosition'
|
||||
import Landing from '@/pages/_landing/Landing'
|
||||
import Page404 from '@/pages/404'
|
||||
import Accessibilité from '@/pages/Accessibilité'
|
||||
import Assistants from '@/pages/assistants/index'
|
||||
import Budget from '@/pages/budget/index'
|
||||
import IntegrationTest from '@/pages/dev/IntegrationTest'
|
||||
import Documentation from '@/pages/Documentation'
|
||||
import Iframes from '@/pages/iframes'
|
||||
import Integration from '@/pages/integration/index'
|
||||
import Nouveautés from '@/pages/nouveautés/index'
|
||||
import Offline from '@/pages/Offline'
|
||||
|
@ -36,53 +26,274 @@ import Simulateurs from '@/pages/simulateurs'
|
|||
import SimulateursEtAssistants from '@/pages/simulateurs-et-assistants'
|
||||
import Stats from '@/pages/statistiques/LazyStats'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
import {
|
||||
useAsyncGetRule,
|
||||
useAsyncParsedRules,
|
||||
useLazyPromiseOnSituationChange,
|
||||
usePromiseOnSituationChange,
|
||||
useShallowCopy,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import Provider, { ProviderProps } from './Provider'
|
||||
import Redirections from './Redirections'
|
||||
|
||||
type RootProps = {
|
||||
basename: ProviderProps['basename']
|
||||
rulesPreTransform?: (rules: Rules) => Rules
|
||||
// rulesPreTransform?: (rules: Rules) => Rules
|
||||
}
|
||||
|
||||
const TestWorkerEngine = () => {
|
||||
const [refresh, setRefresh] = useState(0)
|
||||
const workerEngine = useWorkerEngine()
|
||||
// const workerEngineCtx = useWorkerEngineContext()
|
||||
|
||||
const [, trigger] = useLazyPromise(
|
||||
async () =>
|
||||
workerEngine?.asyncSetSituationWithEngineId({
|
||||
SMIC: '1000€/mois',
|
||||
}),
|
||||
[workerEngine],
|
||||
{ defaultValue: 'loading...' }
|
||||
)
|
||||
|
||||
const date = useAsyncGetRule('date', { defaultValue: 'loading...' })
|
||||
|
||||
const parsedRules = useAsyncParsedRules()
|
||||
|
||||
const resultSmic = usePromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId('SMIC'),
|
||||
[workerEngine],
|
||||
{ defaultValue: 'loading...' }
|
||||
)
|
||||
|
||||
const [resultLazySmic, triggerLazySmic] = useLazyPromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId('SMIC'),
|
||||
[workerEngine],
|
||||
{ defaultValue: 'wait 2sec...' }
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('??? useEffect')
|
||||
|
||||
void (async () => {
|
||||
await workerEngine.isWorkerReady
|
||||
setTimeout(() => {
|
||||
void triggerLazySmic()
|
||||
}, 3000)
|
||||
})()
|
||||
}, [triggerLazySmic, workerEngine.isWorkerReady])
|
||||
|
||||
const workerEngineCopy = useShallowCopy(workerEngine)
|
||||
// // const workerEngineCopy = workerEngine
|
||||
console.log('=========>', workerEngine, workerEngineCopy)
|
||||
|
||||
const [, triggerCopy] = useLazyPromise(async () => {
|
||||
// console.log('+++++++++>', workerEngineCopy)
|
||||
|
||||
await workerEngineCopy?.asyncSetSituationWithEngineId({
|
||||
SMIC: '2000€/mois',
|
||||
})
|
||||
}, [workerEngineCopy])
|
||||
|
||||
const dateCopy = useAsyncGetRule('date', {
|
||||
defaultValue: 'loading...',
|
||||
// workerEngine: workerEngineCopy,
|
||||
})
|
||||
|
||||
const parsedRulesCopy = useAsyncParsedRules({
|
||||
workerEngine: workerEngineCopy,
|
||||
})
|
||||
|
||||
const resultSmicCopy = usePromiseOnSituationChange(
|
||||
async () => workerEngineCopy?.asyncEvaluateWithEngineId('SMIC'),
|
||||
[workerEngineCopy],
|
||||
{
|
||||
defaultValue: 'loading...',
|
||||
workerEngine: workerEngineCopy,
|
||||
}
|
||||
)
|
||||
|
||||
const [resultLazySmicCopy, triggerLazySmicCopy] =
|
||||
useLazyPromiseOnSituationChange(
|
||||
async () => workerEngineCopy?.asyncEvaluateWithEngineId('SMIC'),
|
||||
[workerEngineCopy],
|
||||
{
|
||||
defaultValue: 'wait 2sec...',
|
||||
workerEngine: workerEngineCopy,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('useEffect')
|
||||
|
||||
void (async () => {
|
||||
await workerEngine.isWorkerReady
|
||||
setTimeout(() => {
|
||||
void triggerLazySmicCopy()
|
||||
}, 3000)
|
||||
})()
|
||||
}, [triggerLazySmicCopy, workerEngine.isWorkerReady])
|
||||
|
||||
const { asyncSetSituationWithEngineId } = workerEngineCopy ?? {}
|
||||
usePromiseOnSituationChange(async () => {
|
||||
// console.log('**************>', workerEngineCopy, resultSmic)
|
||||
|
||||
if (
|
||||
typeof resultSmic !== 'string' &&
|
||||
typeof resultSmic.nodeValue === 'number'
|
||||
) {
|
||||
// console.log('ooooooooooooooooooo', resultSmic)
|
||||
|
||||
await asyncSetSituationWithEngineId?.({
|
||||
SMIC: resultSmic.nodeValue + '€/mois',
|
||||
})
|
||||
}
|
||||
}, [asyncSetSituationWithEngineId, resultSmic])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Test worker engine</h1>
|
||||
<button onClick={() => setRefresh((r) => r + 1)}>
|
||||
Refresh {refresh}
|
||||
</button>
|
||||
<button onClick={() => void trigger()}>trigger</button>
|
||||
<button onClick={() => void triggerCopy()}>trigger copy</button>
|
||||
|
||||
<p>
|
||||
date title:{' '}
|
||||
{JSON.stringify(typeof date === 'string' ? date : date?.title)}
|
||||
</p>
|
||||
<p>
|
||||
parsedRules length:{' '}
|
||||
{JSON.stringify(Object.entries(parsedRules ?? {}).length)}
|
||||
</p>
|
||||
<p>
|
||||
resultSmic:{' '}
|
||||
{JSON.stringify(
|
||||
typeof resultSmic === 'string' ? resultSmic : resultSmic?.nodeValue
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
resultLazySmic:{' '}
|
||||
{JSON.stringify(
|
||||
typeof resultLazySmic === 'string'
|
||||
? resultLazySmic
|
||||
: resultLazySmic?.nodeValue
|
||||
)}
|
||||
</p>
|
||||
|
||||
<p>workerEngineCopy: {JSON.stringify(workerEngineCopy?.engineId)}</p>
|
||||
|
||||
<p>
|
||||
dateCopy title:{' '}
|
||||
{JSON.stringify(
|
||||
typeof dateCopy === 'string' ? dateCopy : dateCopy?.title
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
parsedRulesCopy length:{' '}
|
||||
{JSON.stringify(Object.entries(parsedRulesCopy ?? {}).length)}
|
||||
</p>
|
||||
<p>
|
||||
resultSmicCopy:{' '}
|
||||
{JSON.stringify(
|
||||
typeof resultSmicCopy === 'string'
|
||||
? resultSmicCopy
|
||||
: resultSmicCopy?.nodeValue
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
resultLazySmicCopy:{' '}
|
||||
{JSON.stringify(
|
||||
typeof resultLazySmicCopy === 'string'
|
||||
? resultLazySmicCopy
|
||||
: resultLazySmicCopy?.nodeValue
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Root({
|
||||
basename,
|
||||
rulesPreTransform = (r) => r,
|
||||
}: RootProps) {
|
||||
const engine = useMemo(
|
||||
() => engineFactory(rulesPreTransform(rules)),
|
||||
}: // rulesPreTransform = (r) => r,
|
||||
RootProps) {
|
||||
// const situationVersion = useCreateWorkerEngine(basename)
|
||||
// const engine = useMemo(
|
||||
// () => engineFactory(rulesPreTransform(rules)),
|
||||
|
||||
// We need to keep [rules] in the dependency list for hot reload of the rules
|
||||
// in dev mode, even if ESLint think it is unnecessary since `rules` isn't
|
||||
// defined in the component scope.
|
||||
//
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[rules]
|
||||
)
|
||||
// // We need to keep [rules] in the dependency list for hot reload of the rules
|
||||
// // in dev mode, even if ESLint think it is unnecessary since `rules` isn't
|
||||
// // defined in the component scope.
|
||||
// //
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// [rules]
|
||||
// )
|
||||
|
||||
return (
|
||||
<StrictMode>
|
||||
<EngineProvider value={engine}>
|
||||
<Provider basename={basename}>
|
||||
<Redirections>
|
||||
<Router />
|
||||
</Redirections>
|
||||
</Provider>
|
||||
</EngineProvider>
|
||||
{/* <EngineProvider value={engine}> */}
|
||||
<Provider basename={basename}>
|
||||
<Redirections>
|
||||
<Router />
|
||||
</Redirections>
|
||||
</Provider>
|
||||
{/* </EngineProvider> */}
|
||||
</StrictMode>
|
||||
)
|
||||
}
|
||||
|
||||
const Router = () => {
|
||||
const engine = useEngine()
|
||||
/*
|
||||
const exampleSyncValue = usePromiseOnSituationChange(
|
||||
() => asyncEvaluate('SMIC'),
|
||||
[]
|
||||
)?.nodeValue
|
||||
|
||||
useSetupSafeSituation(engine)
|
||||
const exampleSyncValueWithDefault = usePromiseOnSituationChange(
|
||||
async () => (await asyncEvaluate('SMIC')).nodeValue,
|
||||
[],
|
||||
'loading...'
|
||||
)
|
||||
|
||||
const [exampleAsyncValue, fireEvaluate] = useLazyPromise(
|
||||
async (param: PublicodesExpression) =>
|
||||
(await asyncEvaluate(param)).nodeValue,
|
||||
[],
|
||||
42
|
||||
)
|
||||
|
||||
usePromise(async () => {
|
||||
let count = 0
|
||||
const interval = setInterval(() => {
|
||||
void fireEvaluate(count++ % 2 === 0 ? 'date' : 'SMIC')
|
||||
if (count === 7) clearInterval(interval)
|
||||
}, 1000)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
await asyncSetSituation({ date: '01/01/2022' })
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
await asyncSetSituation({ date: '01/01/2021' })
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
}, [fireEvaluate])
|
||||
|
||||
*/
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/iframes/*" element={<Iframes />} />
|
||||
<Route path="*" element={<App />} />
|
||||
</Routes>
|
||||
<>
|
||||
{/* exemple sans valeur par defaut : {JSON.stringify(exampleSyncValue)}
|
||||
<br />
|
||||
exemple avec valeur par defaut :{' '}
|
||||
{JSON.stringify(exampleSyncValueWithDefault)} <br />
|
||||
exemple d'execution manuel : {JSON.stringify(exampleAsyncValue)} */}
|
||||
{/* */}
|
||||
<Routes>
|
||||
<Route path="test-worker" element={<TestWorkerEngine />} />
|
||||
|
||||
{/* <Route path="/iframes/*" element={<Iframes />} /> */}
|
||||
<Route path="*" element={<App />} />
|
||||
</Routes>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -96,11 +307,8 @@ const CatchOffline = ({ error }: ComponentProps<FallbackRender>) => {
|
|||
|
||||
const App = () => {
|
||||
const { relativeSitePaths } = useSitePaths()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const fullURL = useGetFullURL()
|
||||
|
||||
useSaveAndRestoreScrollPosition()
|
||||
const isEmbedded = useIsEmbedded()
|
||||
if (!import.meta.env.PROD && import.meta.env.VITE_AXE_CORE_ENABLED) {
|
||||
|
@ -108,7 +316,6 @@ const App = () => {
|
|||
useAxeCoreAnalysis()
|
||||
}
|
||||
const documentationPath = useSitePaths().absoluteSitePaths.documentation.index
|
||||
const engine = useEngine()
|
||||
|
||||
return (
|
||||
<StyledLayout $isEmbedded={isEmbedded}>
|
||||
|
@ -137,10 +344,10 @@ const App = () => {
|
|||
<Routes>
|
||||
<Route index element={<Landing />} />
|
||||
|
||||
<Route
|
||||
{/* <Route
|
||||
path={relativeSitePaths.assistants.index + '/*'}
|
||||
element={<Assistants />}
|
||||
/>
|
||||
/> */}
|
||||
<Route
|
||||
path={relativeSitePaths.simulateurs.index + '/*'}
|
||||
element={<Simulateurs />}
|
||||
|
@ -149,15 +356,15 @@ const App = () => {
|
|||
path={relativeSitePaths.simulateursEtAssistants + '/*'}
|
||||
element={<SimulateursEtAssistants />}
|
||||
/>
|
||||
<Route
|
||||
path={relativeSitePaths.documentation.index + '/*'}
|
||||
element={
|
||||
<Documentation
|
||||
documentationPath={documentationPath}
|
||||
engine={engine}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* <Route
|
||||
path={relativeSitePaths.documentation.index + '/*'}
|
||||
element={
|
||||
<Documentation
|
||||
documentationPath={documentationPath}
|
||||
engine={engine}
|
||||
/>
|
||||
}
|
||||
/> */}
|
||||
<Route
|
||||
path={relativeSitePaths.développeur.index + '/*'}
|
||||
element={<Integration />}
|
||||
|
@ -172,12 +379,10 @@ const App = () => {
|
|||
path={relativeSitePaths.accessibilité}
|
||||
element={<Accessibilité />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/dev/integration-test"
|
||||
element={<IntegrationTest />}
|
||||
/>
|
||||
|
||||
<Route path={relativeSitePaths.plan} element={<Plan />} />
|
||||
|
||||
<Route path="*" element={<Page404 />} />
|
||||
|
@ -185,7 +390,6 @@ const App = () => {
|
|||
</ErrorBoundary>
|
||||
</Container>
|
||||
</main>
|
||||
|
||||
{!isEmbedded && <Footer />}
|
||||
</StyledLayout>
|
||||
)
|
||||
|
|
|
@ -6,15 +6,21 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||
import { styled } from 'styled-components'
|
||||
|
||||
import { Switch } from '@/design-system/switch'
|
||||
import { useLazyPromise } from '@/hooks/usePromise'
|
||||
import { batchUpdateSituation } from '@/store/actions/actions'
|
||||
import { situationSelector } from '@/store/selectors/simulationSelectors'
|
||||
import { ReplaceReturnType } from '@/types/utils'
|
||||
import { catchDivideByZeroError } from '@/utils'
|
||||
import {
|
||||
useAsyncGetRule,
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { ExplicableRule } from './conversation/Explicable'
|
||||
import { Condition, WhenApplicable } from './EngineValue'
|
||||
import { SimulationGoal } from './Simulation'
|
||||
import { FromTop } from './ui/animate'
|
||||
import { useEngine } from './utils/EngineContext'
|
||||
|
||||
const proportions = {
|
||||
'entreprise . activités . revenus mixtes . proportions . service BIC':
|
||||
|
@ -60,7 +66,12 @@ export default function ChiffreAffairesActivitéMixte({
|
|||
<SimulationGoal
|
||||
small
|
||||
key={chiffreAffaires}
|
||||
onUpdateSituation={adjustProportions}
|
||||
onUpdateSituation={
|
||||
adjustProportions as ReplaceReturnType<
|
||||
ReturnType<typeof useAdjustProportions>,
|
||||
void
|
||||
>
|
||||
}
|
||||
dottedName={chiffreAffaires}
|
||||
/>
|
||||
))}
|
||||
|
@ -72,11 +83,11 @@ export default function ChiffreAffairesActivitéMixte({
|
|||
}
|
||||
|
||||
function useAdjustProportions(CADottedName: DottedName) {
|
||||
const engine = useEngine()
|
||||
const dispatch = useDispatch()
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
return useCallback(
|
||||
(name: DottedName, value?: PublicodesExpression) => {
|
||||
const [, trigger] = useLazyPromise(
|
||||
async (name: DottedName, value?: PublicodesExpression) => {
|
||||
const checkValue = (
|
||||
val: unknown
|
||||
): val is { valeur: number; unité: string } =>
|
||||
|
@ -87,61 +98,79 @@ function useAdjustProportions(CADottedName: DottedName) {
|
|||
typeof val.valeur === 'number' &&
|
||||
typeof val.unité === 'string'
|
||||
|
||||
const old = Object.values(proportions).map((chiffreAffaire) =>
|
||||
serializeEvaluation(
|
||||
engine.evaluate(
|
||||
name === chiffreAffaire && checkValue(value)
|
||||
? value
|
||||
: chiffreAffaire
|
||||
const old = await Promise.all(
|
||||
Object.values(proportions).map(async (chiffreAffaire) =>
|
||||
serializeEvaluation(
|
||||
await workerEngine.asyncEvaluateWithEngineId(
|
||||
name === chiffreAffaire && checkValue(value)
|
||||
? value
|
||||
: chiffreAffaire
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
const nouveauCA = serializeEvaluation(
|
||||
engine.evaluate({ somme: old.filter(Boolean) })
|
||||
await workerEngine.asyncEvaluateWithEngineId({
|
||||
somme: old.filter(Boolean),
|
||||
})
|
||||
)
|
||||
|
||||
if (nouveauCA === '0€/an') {
|
||||
return // Avoid division by 0
|
||||
}
|
||||
const situation = Object.entries(proportions).reduce(
|
||||
(acc, [proportionName, valueName]) => {
|
||||
|
||||
const entries = Object.entries(proportions).map(
|
||||
async ([proportionName, valueName]) => {
|
||||
const newValue = serializeEvaluation(
|
||||
engine.evaluate(
|
||||
await workerEngine.asyncEvaluateWithEngineId(
|
||||
valueName === name && checkValue(value)
|
||||
? value
|
||||
: { valeur: valueName, 'par défaut': '0€/an' }
|
||||
)
|
||||
)
|
||||
const newProportion = serializeEvaluation(
|
||||
catchDivideByZeroError(() =>
|
||||
engine.evaluate({
|
||||
await catchDivideByZeroError(() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
valeur: `${newValue ?? ''} / ${nouveauCA ?? ''}`,
|
||||
unité: '%',
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[proportionName]: newProportion,
|
||||
[valueName]: undefined,
|
||||
}
|
||||
},
|
||||
return [proportionName, valueName, newProportion] as const
|
||||
}
|
||||
)
|
||||
|
||||
const situation = (await Promise.all(entries)).reduce(
|
||||
(acc, [proportionName, valueName, newProportion]) => ({
|
||||
...acc,
|
||||
[proportionName]: newProportion,
|
||||
[valueName]: undefined,
|
||||
}),
|
||||
{ [CADottedName]: nouveauCA }
|
||||
)
|
||||
dispatch(batchUpdateSituation(situation))
|
||||
},
|
||||
[CADottedName, engine, dispatch]
|
||||
[CADottedName, dispatch, workerEngine]
|
||||
)
|
||||
|
||||
return trigger
|
||||
}
|
||||
|
||||
function ActivitéMixte() {
|
||||
const dispatch = useDispatch()
|
||||
const situation = useSelector(situationSelector)
|
||||
const rule = useEngine().getRule('entreprise . activités . revenus mixtes')
|
||||
const rule = useAsyncGetRule('entreprise . activités . revenus mixtes')
|
||||
const workerEngine = useWorkerEngine()
|
||||
const defaultChecked =
|
||||
useEngine().evaluate('entreprise . activités . revenus mixtes')
|
||||
.nodeValue === true
|
||||
usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId(
|
||||
'entreprise . activités . revenus mixtes'
|
||||
),
|
||||
[workerEngine]
|
||||
)?.nodeValue === true
|
||||
|
||||
const onMixteChecked = useCallback(
|
||||
(checked: boolean) => {
|
||||
dispatch(
|
||||
|
@ -173,7 +202,7 @@ function ActivitéMixte() {
|
|||
Activité mixte
|
||||
</Switch>
|
||||
</Trans>
|
||||
<ExplicableRule dottedName={rule.dottedName} light />
|
||||
{rule && <ExplicableRule dottedName={rule.dottedName} light />}
|
||||
</StyledActivitéMixteContainer>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, {
|
||||
import {
|
||||
ASTNode,
|
||||
EvaluatedNode,
|
||||
formatValue,
|
||||
|
@ -10,13 +10,21 @@ import React from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { keyframes, styled } from 'styled-components'
|
||||
|
||||
import {
|
||||
useAsyncParsedRules,
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import RuleLink from './RuleLink'
|
||||
import { useEngine } from './utils/EngineContext'
|
||||
|
||||
// import { useEngine } from './utils/EngineContext'
|
||||
|
||||
export type ValueProps<Names extends string> = {
|
||||
expression: PublicodesExpression
|
||||
unit?: string
|
||||
engine?: Engine<Names>
|
||||
// engine?: Engine<Names>
|
||||
engineId?: number
|
||||
displayedUnit?: string
|
||||
precision?: number
|
||||
documentationPath?: string
|
||||
|
@ -27,7 +35,7 @@ export type ValueProps<Names extends string> = {
|
|||
export default function Value<Names extends string>({
|
||||
expression,
|
||||
unit,
|
||||
engine,
|
||||
engineId = 0,
|
||||
displayedUnit,
|
||||
flashOnChange = false,
|
||||
precision,
|
||||
|
@ -36,27 +44,41 @@ export default function Value<Names extends string>({
|
|||
...props
|
||||
}: ValueProps<Names>) {
|
||||
const { language } = useTranslation().i18n
|
||||
const workerEngine = useWorkerEngine()
|
||||
if (expression === null) {
|
||||
throw new TypeError('expression cannot be null')
|
||||
}
|
||||
const defaultEngine = useEngine()
|
||||
const e = engine ?? defaultEngine
|
||||
const isRule =
|
||||
typeof expression === 'string' && expression in e.getParsedRules()
|
||||
|
||||
const evaluation = e.evaluate({
|
||||
valeur: expression,
|
||||
...(unit && { unité: unit }),
|
||||
const parsedRules = useAsyncParsedRules({
|
||||
workerEngine,
|
||||
})
|
||||
|
||||
const isRule =
|
||||
typeof expression === 'string' && parsedRules && expression in parsedRules
|
||||
|
||||
const evaluation = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
valeur: expression,
|
||||
...(unit && { unité: unit }),
|
||||
}),
|
||||
[expression, unit, workerEngine]
|
||||
)
|
||||
|
||||
const value = formatValue(evaluation, {
|
||||
displayedUnit,
|
||||
language,
|
||||
precision,
|
||||
}) as string
|
||||
|
||||
if (isRule && linkToRule) {
|
||||
const ruleEvaluation = e.evaluate(expression)
|
||||
const ruleEvaluation = usePromiseOnSituationChange(
|
||||
async () =>
|
||||
isRule &&
|
||||
linkToRule &&
|
||||
workerEngine.asyncEvaluateWithEngineId(expression),
|
||||
[expression, isRule, linkToRule, workerEngine]
|
||||
)
|
||||
|
||||
if (isRule && linkToRule && ruleEvaluation) {
|
||||
let dottedName = expression as DottedName
|
||||
if (ruleEvaluation.sourceMap?.mecanismName === 'replacement') {
|
||||
dottedName =
|
||||
|
@ -88,17 +110,16 @@ export default function Value<Names extends string>({
|
|||
</StyledValue>
|
||||
)
|
||||
}
|
||||
const flash = keyframes`
|
||||
|
||||
const flash = keyframes`
|
||||
from {
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
to {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
to {
|
||||
background-color: transparent;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledValue = styled.span<{ $flashOnChange: boolean }>`
|
||||
|
@ -109,118 +130,168 @@ const StyledValue = styled.span<{ $flashOnChange: boolean }>`
|
|||
type ConditionProps = {
|
||||
expression: PublicodesExpression | ASTNode
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
// engine?: Engine<DottedName>
|
||||
engineId?: number
|
||||
}
|
||||
|
||||
export function Condition({
|
||||
expression,
|
||||
children,
|
||||
engine: engineFromProps,
|
||||
// engine: engineFromProps,
|
||||
engineId = 0,
|
||||
}: ConditionProps) {
|
||||
const defaultEngine = useEngine()
|
||||
const engine = engineFromProps ?? defaultEngine
|
||||
const nodeValue = engine.evaluate({ '!=': [expression, 'non'] }).nodeValue
|
||||
// const defaultEngine = useEngine()
|
||||
// const engine = engineFromProps ?? defaultEngine
|
||||
// const nodeValue = engine.evaluate({ '!=': [expression, 'non'] }).nodeValue
|
||||
|
||||
if (!nodeValue) {
|
||||
return null
|
||||
}
|
||||
// if (!nodeValue) {
|
||||
// return null
|
||||
// }
|
||||
|
||||
return <>{children}</>
|
||||
// return <>{children}</>
|
||||
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const node = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
'!=': [expression, 'non'],
|
||||
}),
|
||||
[expression, workerEngine]
|
||||
)
|
||||
|
||||
return !node?.nodeValue ? null : <>{children}</>
|
||||
}
|
||||
|
||||
export function WhenValueEquals({
|
||||
expression,
|
||||
value,
|
||||
children,
|
||||
engine: engineFromProps,
|
||||
// engine: engineFromProps,
|
||||
engineId = 0,
|
||||
}: ConditionProps & { value: string | number }) {
|
||||
const defaultEngine = useEngine()
|
||||
const engine = engineFromProps ?? defaultEngine
|
||||
const nodeValue = engine.evaluate(expression).nodeValue
|
||||
// const defaultEngine = useEngine()
|
||||
// const engine = engineFromProps ?? defaultEngine
|
||||
// const nodeValue = engine.evaluate(expression).nodeValue
|
||||
|
||||
if (nodeValue !== value) {
|
||||
return null
|
||||
}
|
||||
// if (nodeValue !== value) {
|
||||
// return null
|
||||
// }
|
||||
|
||||
return <>{children}</>
|
||||
// return <>{children}</>
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const node = usePromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId(expression),
|
||||
[expression, workerEngine]
|
||||
)
|
||||
|
||||
return node?.nodeValue !== value ? null : <>{children}</>
|
||||
}
|
||||
|
||||
export function WhenApplicable({
|
||||
dottedName,
|
||||
children,
|
||||
engine,
|
||||
engineId = 0,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
// engine?: Engine<DottedName>
|
||||
engineId?: number
|
||||
}) {
|
||||
const defaultEngine = useEngine()
|
||||
const workerEngine = useWorkerEngine()
|
||||
// const defaultEngine = useEngine()
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
// const engineValue = engine ?? defaultEngine
|
||||
|
||||
if (
|
||||
engineValue.evaluate({ 'est applicable': dottedName }).nodeValue !== true
|
||||
) {
|
||||
return null
|
||||
}
|
||||
// if (
|
||||
// engineValue.evaluate({ 'est applicable': dottedName }).nodeValue !== true
|
||||
// ) {
|
||||
// return null
|
||||
// }
|
||||
|
||||
return <>{children}</>
|
||||
// return <>{children}</>
|
||||
|
||||
const node = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
'est applicable': dottedName,
|
||||
}),
|
||||
[dottedName, workerEngine]
|
||||
)
|
||||
|
||||
return node?.nodeValue !== true ? <>{children}</> : null
|
||||
}
|
||||
|
||||
export function WhenNotApplicable({
|
||||
dottedName,
|
||||
children,
|
||||
engine,
|
||||
engineId = 0,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
// engine?: Engine<DottedName>
|
||||
engineId?: number
|
||||
}) {
|
||||
const defaultEngine = useEngine()
|
||||
// const defaultEngine = useEngine()
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
// const engineValue = engine ?? defaultEngine
|
||||
|
||||
if (
|
||||
engineValue.evaluate({ 'est non applicable': dottedName }).nodeValue !==
|
||||
true
|
||||
) {
|
||||
return null
|
||||
}
|
||||
// if (
|
||||
// engineValue.evaluate({ 'est non applicable': dottedName }).nodeValue !==
|
||||
// true
|
||||
// ) {
|
||||
// return null
|
||||
// }
|
||||
|
||||
return <>{children}</>
|
||||
// return <>{children}</>
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const node = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
'est non applicable': dottedName,
|
||||
}),
|
||||
[dottedName, workerEngine]
|
||||
)
|
||||
|
||||
return node?.nodeValue !== true ? null : <>{children}</>
|
||||
}
|
||||
|
||||
export function WhenAlreadyDefined({
|
||||
dottedName,
|
||||
children,
|
||||
engine,
|
||||
engineId = 0,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
// engine?: Engine<DottedName>
|
||||
engineId?: number
|
||||
}) {
|
||||
const defaultEngine = useEngine()
|
||||
const workerEngine = useWorkerEngine()
|
||||
const node = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({ 'est non défini': dottedName }),
|
||||
[dottedName, workerEngine]
|
||||
)
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
|
||||
if (engineValue.evaluate({ 'est non défini': dottedName }).nodeValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
return node?.nodeValue ? null : <>{children}</>
|
||||
}
|
||||
|
||||
export function WhenNotAlreadyDefined({
|
||||
dottedName,
|
||||
children,
|
||||
engineId = 0,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engineId?: number
|
||||
}) {
|
||||
const engine = useEngine()
|
||||
if (engine.evaluate({ 'est défini': dottedName }).nodeValue) {
|
||||
return null
|
||||
}
|
||||
const workerEngine = useWorkerEngine()
|
||||
const node = usePromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId({ 'est défini': dottedName }),
|
||||
[dottedName, workerEngine]
|
||||
)
|
||||
|
||||
return <>{children}</>
|
||||
return node?.nodeValue ? null : <>{children}</>
|
||||
}
|
||||
|
|
|
@ -15,6 +15,15 @@ const FeedbackButton = ({ isEmbedded }: { isEmbedded?: boolean }) => {
|
|||
const { t } = useTranslation()
|
||||
const containerRef = useRef<HTMLElement | null>(null)
|
||||
const [feedbackFormIsOpened, setFeedbackFormIsOpened] = useState(false)
|
||||
|
||||
// const { absoluteSitePaths } = useSitePaths()
|
||||
// const currentPath = useLocation().pathname
|
||||
// const isSimulateurSalaire =
|
||||
// currentPath.includes(absoluteSitePaths.simulateurs.salarié) ||
|
||||
// currentPath.includes(IFRAME_SIMULATEUR_EMBAUCHE_PATH)
|
||||
|
||||
// const { shouldShowRater, customTitle } = useFeedback()
|
||||
|
||||
useOnClickOutside(
|
||||
containerRef,
|
||||
() => !feedbackFormIsOpened && setIsFormOpen(false)
|
||||
|
|
|
@ -4,12 +4,18 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import { useEngine, useInversionFail } from '@/components/utils/EngineContext'
|
||||
// import { useEngine, useInversionFail } from '@/components/utils/EngineContext'
|
||||
import { Message } from '@/design-system'
|
||||
import { CloseButton } from '@/design-system/buttons'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import { hideNotification } from '@/store/actions/actions'
|
||||
import { RootState } from '@/store/reducers/rootReducer'
|
||||
import { isNotNull } from '@/utils'
|
||||
import {
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { ExplicableRule } from './conversation/Explicable'
|
||||
import { Appear } from './ui/animate'
|
||||
|
@ -26,44 +32,64 @@ type Notification = {
|
|||
sévérité: 'avertissement' | 'information'
|
||||
}
|
||||
|
||||
function getNotifications(engine: Engine) {
|
||||
return Object.values(engine.getParsedRules())
|
||||
.filter(
|
||||
(rule) =>
|
||||
rule.rawNode.type === 'notification' &&
|
||||
!!engine.evaluate(rule.dottedName).nodeValue
|
||||
async function getNotifications(workerEngine: WorkerEngine) {
|
||||
return (
|
||||
await Promise.all(
|
||||
Object.values(await workerEngine.asyncGetParsedRulesWithEngineId()).map(
|
||||
async (rule) =>
|
||||
rule.rawNode.type === 'notification' &&
|
||||
!!(await workerEngine.asyncEvaluateWithEngineId(rule.dottedName))
|
||||
.nodeValue
|
||||
? {
|
||||
dottedName: rule.dottedName,
|
||||
sévérité: rule.rawNode.sévérité,
|
||||
résumé: rule.rawNode.résumé,
|
||||
description: rule.rawNode.description,
|
||||
}
|
||||
: null
|
||||
)
|
||||
)
|
||||
.map(({ dottedName, rawNode: { sévérité, résumé, description } }) => ({
|
||||
dottedName,
|
||||
sévérité,
|
||||
résumé,
|
||||
description,
|
||||
}))
|
||||
).filter(isNotNull)
|
||||
// .map(({ dottedName, rawNode: { sévérité, résumé, description } }) => ({
|
||||
// dottedName,
|
||||
// sévérité,
|
||||
// résumé,
|
||||
// description,
|
||||
// }))
|
||||
}
|
||||
|
||||
export default function Notifications() {
|
||||
const { t } = useTranslation()
|
||||
const engine = useEngine()
|
||||
const inversionFail = useInversionFail()
|
||||
const workerEngine = useWorkerEngine()
|
||||
// const inversionFail = useInversionFail()
|
||||
const hiddenNotifications = useSelector(
|
||||
(state: RootState) => state.simulation?.hiddenNotifications
|
||||
)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const messages: Array<Notification> = (
|
||||
inversionFail
|
||||
? [
|
||||
{
|
||||
dottedName: 'inversion fail',
|
||||
description: t(
|
||||
'simulateurs.inversionFail',
|
||||
'Le montant saisi abouti à un résultat impossible. Cela est dû à un effet de seuil dans le calcul des cotisations.\n\nNous vous invitons à réessayer en modifiant légèrement le montant renseigné (quelques euros de plus par exemple).'
|
||||
),
|
||||
sévérité: 'avertissement',
|
||||
} as Notification,
|
||||
]
|
||||
: (getNotifications(engine) as Array<Notification>)
|
||||
).filter(({ dottedName }) => !hiddenNotifications?.includes(dottedName))
|
||||
const messages = usePromise(
|
||||
async () =>
|
||||
(await getNotifications(workerEngine)).filter(
|
||||
({ dottedName }) => !hiddenNotifications?.includes(dottedName)
|
||||
),
|
||||
[hiddenNotifications, workerEngine],
|
||||
[]
|
||||
)
|
||||
// const messages: Array<Notification> = (
|
||||
// inversionFail
|
||||
// ? [
|
||||
// {
|
||||
// dottedName: 'inversion fail',
|
||||
// description: t(
|
||||
// 'simulateurs.inversionFail',
|
||||
// 'Le montant saisi abouti à un résultat impossible. Cela est dû à un effet de seuil dans le calcul des cotisations.\n\nNous vous invitons à réessayer en modifiant légèrement le montant renseigné (quelques euros de plus par exemple).'
|
||||
// ),
|
||||
// sévérité: 'avertissement',
|
||||
// } as Notification,
|
||||
// ]
|
||||
// : (getNotifications(engine) as Array<Notification>)
|
||||
// )
|
||||
// .filter(({ dottedName }) => !hiddenNotifications?.includes(dottedName))
|
||||
|
||||
const isMultiline = (str: string) => str.trim().split('\n').length > 1
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import { H1, H4 } from '@/design-system/typography/heading'
|
|||
import { Link } from '@/design-system/typography/link'
|
||||
import { Body, Intro } from '@/design-system/typography/paragraphs'
|
||||
import { EmbededContextProvider } from '@/hooks/useIsEmbedded'
|
||||
import { WorkerEngineProvider } from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { Message } from '../design-system'
|
||||
import * as safeLocalStorage from '../storage/safeLocalStorage'
|
||||
|
@ -27,7 +28,7 @@ import { IframeResizer } from './IframeResizer'
|
|||
import { ServiceWorker } from './ServiceWorker'
|
||||
import { DarkModeProvider } from './utils/DarkModeContext'
|
||||
|
||||
type SiteName = 'mon-entreprise' | 'infrance' | 'publicodes'
|
||||
type SiteName = 'mon-entreprise' | 'infrance'
|
||||
|
||||
export const SiteNameContext = createContext<SiteName | null>(null)
|
||||
|
||||
|
@ -51,26 +52,28 @@ export default function Provider({
|
|||
<I18nextProvider i18n={i18next}>
|
||||
<ReduxProvider store={store}>
|
||||
<BrowserRouterProvider basename={basename}>
|
||||
<ErrorBoundary
|
||||
fallback={(errorData) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<ErrorFallback {...errorData} showFeedbackForm />
|
||||
)}
|
||||
>
|
||||
{!import.meta.env.SSR &&
|
||||
import.meta.env.MODE === 'production' &&
|
||||
'serviceWorker' in navigator && <ServiceWorker />}
|
||||
<IframeResizer />
|
||||
<OverlayProvider>
|
||||
<ThemeColorsProvider>
|
||||
<DisableAnimationOnPrintProvider>
|
||||
<SiteNameContext.Provider value={basename}>
|
||||
{children}
|
||||
</SiteNameContext.Provider>
|
||||
</DisableAnimationOnPrintProvider>
|
||||
</ThemeColorsProvider>
|
||||
</OverlayProvider>
|
||||
</ErrorBoundary>
|
||||
<WorkerEngineProvider basename={basename}>
|
||||
<ErrorBoundary
|
||||
fallback={(errorData) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<ErrorFallback {...errorData} />
|
||||
)}
|
||||
>
|
||||
{!import.meta.env.SSR &&
|
||||
import.meta.env.MODE === 'production' &&
|
||||
'serviceWorker' in navigator && <ServiceWorker />}
|
||||
<IframeResizer />
|
||||
<OverlayProvider>
|
||||
<ThemeColorsProvider>
|
||||
<DisableAnimationOnPrintProvider>
|
||||
<SiteNameContext.Provider value={basename}>
|
||||
{children}
|
||||
</SiteNameContext.Provider>
|
||||
</DisableAnimationOnPrintProvider>
|
||||
</ThemeColorsProvider>
|
||||
</OverlayProvider>
|
||||
</ErrorBoundary>
|
||||
</WorkerEngineProvider>
|
||||
</BrowserRouterProvider>
|
||||
</ReduxProvider>
|
||||
</I18nextProvider>
|
||||
|
|
|
@ -3,11 +3,12 @@ import { utils } from 'publicodes'
|
|||
import { useContext } from 'react'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import { EngineContext, useEngine } from '@/components/utils/EngineContext'
|
||||
// import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { Link } from '@/design-system/typography/link'
|
||||
import { Li, Ul } from '@/design-system/typography/list'
|
||||
import { capitalise0 } from '@/utils'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import { capitalise0, isNotNullOrUndefined } from '@/utils'
|
||||
|
||||
export function References({
|
||||
references,
|
||||
|
@ -131,21 +132,37 @@ const getDomain = (link: string) =>
|
|||
)
|
||||
|
||||
export function RuleReferences({ dottedNames }: { dottedNames: DottedName[] }) {
|
||||
const engine = useContext(EngineContext)
|
||||
const references = usePromise(
|
||||
async () => {
|
||||
const values = await Promise.all(
|
||||
dottedNames.map(
|
||||
async (dottedName) =>
|
||||
(await asyncEvaluate(`${dottedName} != non`)).nodeValue
|
||||
)
|
||||
)
|
||||
|
||||
const refs = await Promise.all(
|
||||
values
|
||||
.filter(isNotNullOrUndefined)
|
||||
.map(async (dottedName) =>
|
||||
Object.entries(
|
||||
(await asyncGetRule(dottedName as DottedName)).rawNode
|
||||
.références ?? {}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return refs.flat()
|
||||
},
|
||||
[dottedNames],
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Ul>
|
||||
{dottedNames
|
||||
.filter(
|
||||
(dottedName) => engine.evaluate(`${dottedName} != non`).nodeValue
|
||||
)
|
||||
.map((dottedName) =>
|
||||
Object.entries(
|
||||
engine.getRule(dottedName).rawNode.références ?? {}
|
||||
).map(([title, href]) => (
|
||||
<Reference key={href} title={title} href={href} />
|
||||
))
|
||||
)}
|
||||
{references.map(([title, href]) => (
|
||||
<Reference key={href} title={title} href={href} />
|
||||
))}
|
||||
</Ul>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import { RuleLink as EngineRuleLink } from 'publicodes-react'
|
||||
import React, { ReactNode, useContext } from 'react'
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
import { Link } from '@/design-system/typography/link'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
|
||||
import { EngineContext } from './utils/EngineContext'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
// TODO : quicklink -> en cas de variations ou de somme avec un seul élément actif, faire un lien vers cet élément
|
||||
export default function RuleLink(
|
||||
|
@ -16,23 +17,32 @@ export default function RuleLink(
|
|||
children?: React.ReactNode
|
||||
documentationPath?: string
|
||||
linkComponent?: ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
engineId?: number
|
||||
} & Omit<React.ComponentProps<typeof Link>, 'to' | 'children'>
|
||||
) {
|
||||
const engineId = props.engineId ?? 0
|
||||
const { absoluteSitePaths } = useSitePaths()
|
||||
const defaultEngine = useContext(EngineContext)
|
||||
const [loading, setLoading] = React.useState(true)
|
||||
const [error, setError] = React.useState(false)
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const engineUsed = props?.engine ?? defaultEngine
|
||||
usePromiseOnSituationChange(() => {
|
||||
setLoading(true)
|
||||
setError(false)
|
||||
|
||||
try {
|
||||
engineUsed.getRule(props.dottedName)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
return workerEngine
|
||||
.asyncGetRuleWithEngineId(props.dottedName)
|
||||
.catch(() => setError(true))
|
||||
.then(() => setLoading(false))
|
||||
}, [props.dottedName, workerEngine])
|
||||
|
||||
if (loading || error) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>EngineRuleLink</>
|
||||
|
||||
// TODO : publicodes-react ne supporte pas encore les engines dans un worker
|
||||
return (
|
||||
<EngineRuleLink
|
||||
{...props}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { Evaluation } from 'publicodes'
|
||||
import { useContext } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import Banner from '@/components/Banner'
|
||||
import { EngineContext } from '@/components/utils/EngineContext'
|
||||
import { Link as DesignSystemLink } from '@/design-system/typography/link'
|
||||
import { updateSituation } from '@/store/actions/actions'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
const Bold = styled.span<{ $bold: boolean }>`
|
||||
${({ $bold }) => ($bold ? 'font-weight: bold;' : '')}
|
||||
|
@ -15,16 +17,18 @@ const Bold = styled.span<{ $bold: boolean }>`
|
|||
|
||||
export const SelectSimulationYear = () => {
|
||||
const dispatch = useDispatch()
|
||||
const year = useContext(EngineContext).evaluate('date')
|
||||
const workerEngine = useWorkerEngine()
|
||||
const year = usePromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId('date'),
|
||||
[workerEngine]
|
||||
)
|
||||
const choices = [2022, 2023]
|
||||
|
||||
const actualYear = Number(
|
||||
(year.nodeValue?.toString().slice(-4) as Evaluation<number> | undefined) ||
|
||||
(year?.nodeValue?.toString().slice(-4) as Evaluation<number>) ||
|
||||
new Date().getFullYear()
|
||||
)
|
||||
|
||||
// return null // Waiting for next year.
|
||||
|
||||
return (
|
||||
<Banner hideAfterFirstStep={false} icon={'📅'}>
|
||||
<Trans i18nKey="pages.simulateurs.select-year.info">
|
||||
|
|
|
@ -34,9 +34,9 @@ export function useUrl() {
|
|||
? import.meta.env.VITE_FR_BASE_URL
|
||||
: import.meta.env.VITE_EN_BASE_URL
|
||||
|
||||
searchParams.set('utm_source', 'sharing')
|
||||
searchParams?.set('utm_source', 'sharing')
|
||||
|
||||
return siteUrl + path + '?' + searchParams.toString()
|
||||
return siteUrl + path + '?' + (searchParams ?? '').toString()
|
||||
}
|
||||
|
||||
const ButtonLabel = styled.span`
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Evaluation } from 'publicodes'
|
||||
import { useContext } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
|
@ -8,8 +7,10 @@ import { Link } from '@/design-system/typography/link'
|
|||
import { Li, Ul } from '@/design-system/typography/list'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { AbsoluteSitePaths } from '@/sitePaths'
|
||||
|
||||
import { EngineContext } from './utils/EngineContext'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
type SimulateurWarningProps = {
|
||||
simulateur: Exclude<keyof AbsoluteSitePaths['simulateurs'], 'index'>
|
||||
|
@ -18,10 +19,13 @@ type SimulateurWarningProps = {
|
|||
export default function SimulateurWarning({
|
||||
simulateur,
|
||||
}: SimulateurWarningProps) {
|
||||
const year = useContext(EngineContext)
|
||||
.evaluate('date')
|
||||
.nodeValue?.toString()
|
||||
.slice(-4) as Evaluation<number> | undefined
|
||||
const workerEngine = useWorkerEngine()
|
||||
const year = usePromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId('date'),
|
||||
[workerEngine]
|
||||
)
|
||||
?.nodeValue?.toString()
|
||||
.slice(-4) as Evaluation<number>
|
||||
|
||||
return (
|
||||
<Warning
|
||||
|
|
|
@ -10,13 +10,17 @@ import { Strong } from '@/design-system/typography'
|
|||
import { Body, SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import { updateSituation } from '@/store/actions/actions'
|
||||
import { targetUnitSelector } from '@/store/selectors/simulationSelectors'
|
||||
import {
|
||||
useAsyncGetRule,
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { ExplicableRule } from '../conversation/Explicable'
|
||||
import RuleInput, { InputProps } from '../conversation/RuleInput'
|
||||
import RuleLink from '../RuleLink'
|
||||
import { Appear } from '../ui/animate'
|
||||
import AnimatedTargetValue from '../ui/AnimatedTargetValue'
|
||||
import { useEngine } from '../utils/EngineContext'
|
||||
import { useInitialRender } from '../utils/useInitialRender'
|
||||
|
||||
type SimulationGoalProps = {
|
||||
|
@ -48,14 +52,18 @@ export function SimulationGoal({
|
|||
isInfoMode = false,
|
||||
}: SimulationGoalProps) {
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
const currentUnit = useSelector(targetUnitSelector)
|
||||
const evaluation = engine.evaluate({
|
||||
valeur: dottedName,
|
||||
arrondi: round ? 'oui' : 'non',
|
||||
...(!isTypeBoolean ? { unité: currentUnit } : {}),
|
||||
})
|
||||
const rule = engine.getRule(dottedName)
|
||||
const workerEngine = useWorkerEngine()
|
||||
const evaluation = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
value: dottedName,
|
||||
arrondi: round ? 'oui' : 'non',
|
||||
...(!isTypeBoolean ? { unité: currentUnit } : {}),
|
||||
}),
|
||||
[workerEngine, dottedName, round, isTypeBoolean, currentUnit]
|
||||
)
|
||||
const rule = useAsyncGetRule(dottedName)
|
||||
const initialRender = useInitialRender()
|
||||
const [isFocused, setFocused] = useState(false)
|
||||
const onChange = useCallback(
|
||||
|
@ -65,10 +73,11 @@ export function SimulationGoal({
|
|||
},
|
||||
[dispatch, onUpdateSituation, dottedName]
|
||||
)
|
||||
if (evaluation.nodeValue === null) {
|
||||
return null
|
||||
}
|
||||
if (small && !editable && evaluation.nodeValue === undefined) {
|
||||
|
||||
if (
|
||||
evaluation?.nodeValue === null ||
|
||||
(small && !editable && evaluation?.nodeValue === undefined)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -96,7 +105,7 @@ export function SimulationGoal({
|
|||
<StyledBody
|
||||
id={`${dottedName.replace(/\s|\./g, '_')}-label`}
|
||||
>
|
||||
<Strong>{label || rule.title}</Strong>
|
||||
<Strong>{label || rule?.title}</Strong>
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
@ -114,7 +123,7 @@ export function SimulationGoal({
|
|||
</RuleLink>
|
||||
)}
|
||||
|
||||
{rule.rawNode.résumé && (
|
||||
{rule?.rawNode.résumé && (
|
||||
<StyledSmallBody
|
||||
className={small ? 'sr-only' : ''}
|
||||
id={`${dottedName.replace(/\s|\./g, '_')}-description`}
|
||||
|
@ -129,7 +138,7 @@ export function SimulationGoal({
|
|||
</StyledGuideLectureContainer>
|
||||
{editable ? (
|
||||
<Grid item md={small ? 2 : 3} sm={small ? 3 : 4} xs={4}>
|
||||
{!isFocused && !small && (
|
||||
{!isFocused && !small && evaluation && (
|
||||
<AnimatedTargetValue value={evaluation.nodeValue as number} />
|
||||
)}
|
||||
<RuleInput
|
||||
|
@ -140,7 +149,7 @@ export function SimulationGoal({
|
|||
}
|
||||
: undefined
|
||||
}
|
||||
aria-label={engine.getRule(dottedName)?.title}
|
||||
aria-label={rule?.title}
|
||||
aria-describedby={`${dottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
|
@ -151,7 +160,9 @@ export function SimulationGoal({
|
|||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
onChange={onChange}
|
||||
missing={dottedName in evaluation.missingVariables}
|
||||
missing={
|
||||
evaluation && dottedName in evaluation.missingVariables
|
||||
}
|
||||
small={small}
|
||||
formatOptions={{
|
||||
maximumFractionDigits: round ? 0 : 2,
|
||||
|
|
|
@ -9,9 +9,12 @@ import { styled } from 'styled-components'
|
|||
import RuleLink from '@/components/RuleLink'
|
||||
import useDisplayOnIntersecting from '@/components/utils/useDisplayOnIntersecting'
|
||||
import { targetUnitSelector } from '@/store/selectors/simulationSelectors'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { DisableAnimationContext } from './utils/DisableAnimationContext'
|
||||
import { useEngine } from './utils/EngineContext'
|
||||
|
||||
const BarStack = styled.div`
|
||||
display: flex;
|
||||
|
@ -139,6 +142,7 @@ export function StackedBarChart({
|
|||
const styles = useSpring({ opacity: displayChart ? 1 : 0 })
|
||||
|
||||
return !useContext(DisableAnimationContext) ? (
|
||||
// @ts-ignore type too deep
|
||||
<animated.div ref={intersectionRef} style={styles}>
|
||||
<InnerStackedBarChart data={data} precision={precision} />
|
||||
</animated.div>
|
||||
|
@ -201,20 +205,27 @@ export default function StackedRulesChart({
|
|||
data,
|
||||
precision = 0.1,
|
||||
}: StackedRulesChartProps) {
|
||||
const engine = useEngine()
|
||||
const targetUnit = useSelector(targetUnitSelector)
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
return (
|
||||
<StackedBarChart
|
||||
precision={precision}
|
||||
data={data.map(({ dottedName, title, color }) => ({
|
||||
key: dottedName,
|
||||
value: engine.evaluate({ valeur: dottedName, unité: targetUnit })
|
||||
.nodeValue,
|
||||
legend: <RuleLink dottedName={dottedName}>{title}</RuleLink>,
|
||||
title,
|
||||
color,
|
||||
}))}
|
||||
/>
|
||||
const datas = usePromiseOnSituationChange(
|
||||
() =>
|
||||
Promise.all(
|
||||
data.map(async ({ dottedName, title, color }) => ({
|
||||
key: dottedName,
|
||||
value: (
|
||||
await workerEngine.asyncEvaluateWithEngineId({
|
||||
valeur: dottedName,
|
||||
unité: targetUnit,
|
||||
})
|
||||
).nodeValue,
|
||||
legend: <RuleLink dottedName={dottedName}>{title}</RuleLink>,
|
||||
title,
|
||||
color,
|
||||
}))
|
||||
),
|
||||
[data, targetUnit, workerEngine]
|
||||
)
|
||||
|
||||
return <StackedBarChart precision={precision} data={datas || []} />
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import { PublicodesExpression, RuleNode, utils } from 'publicodes'
|
||||
import {
|
||||
EvaluatedNode,
|
||||
PublicodesExpression,
|
||||
RuleNode,
|
||||
utils,
|
||||
} from 'publicodes'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import { EvaluatedRule, useEngine } from '@/components/utils/EngineContext'
|
||||
import { EvaluatedRule } from '@/components/utils/EngineContext'
|
||||
import { Message, PopoverWithTrigger } from '@/design-system'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
|
@ -17,6 +22,7 @@ import { Link } from '@/design-system/typography/link'
|
|||
import { Body, Intro } from '@/design-system/typography/paragraphs'
|
||||
import { useCurrentSimulatorData } from '@/hooks/useCurrentSimulatorData'
|
||||
import { useNextQuestions } from '@/hooks/useNextQuestion'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import { answerQuestion, resetSimulation } from '@/store/actions/actions'
|
||||
import { resetCompany } from '@/store/actions/companyActions'
|
||||
import { isCompanyDottedName } from '@/store/reducers/companySituationReducer'
|
||||
|
@ -25,7 +31,7 @@ import {
|
|||
companySituationSelector,
|
||||
situationSelector,
|
||||
} from '@/store/selectors/simulationSelectors'
|
||||
import { evaluateQuestion } from '@/utils'
|
||||
import { useWorkerEngine } from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import Value from '../EngineValue'
|
||||
import { JeDonneMonAvis } from '../JeDonneMonAvis'
|
||||
|
@ -41,27 +47,43 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
|
|||
const { t } = useTranslation()
|
||||
const { currentSimulatorData } = useCurrentSimulatorData()
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
const workerEngine = useWorkerEngine()
|
||||
const situation = useSelector(situationSelector)
|
||||
const companySituation = useSelector(companySituationSelector)
|
||||
const passedQuestions = useSelector(answeredQuestionsSelector)
|
||||
const answeredAndPassedQuestions = useMemo(
|
||||
() =>
|
||||
(Object.keys(situation) as DottedName[])
|
||||
.filter(
|
||||
(answered) => !passedQuestions.some((passed) => answered === passed)
|
||||
const answeredAndPassedQuestions = usePromise(
|
||||
async () =>
|
||||
(
|
||||
await Promise.all(
|
||||
(Object.keys(situation) as DottedName[])
|
||||
.filter(
|
||||
(answered) =>
|
||||
!passedQuestions.some((passed) => answered === passed)
|
||||
)
|
||||
.concat(passedQuestions)
|
||||
.map(
|
||||
async (dottedName) =>
|
||||
await workerEngine.asyncGetRuleWithEngineId(dottedName)
|
||||
)
|
||||
)
|
||||
.concat(passedQuestions)
|
||||
.filter(
|
||||
(dottedName) =>
|
||||
engine.getRule(dottedName).rawNode.question !== undefined
|
||||
)
|
||||
.map((dottedName) => engine.getRule(dottedName)),
|
||||
[engine, passedQuestions, situation]
|
||||
).filter((rule) => rule.rawNode.question !== undefined),
|
||||
[passedQuestions, situation, workerEngine],
|
||||
[] as RuleNode<DottedName>[]
|
||||
)
|
||||
const nextQuestions = useNextQuestions()
|
||||
const nextSteps = usePromise(
|
||||
() =>
|
||||
Promise.all(
|
||||
nextQuestions.map(
|
||||
async (dottedName) =>
|
||||
workerEngine.asyncEvaluateWithEngineId(
|
||||
await workerEngine.asyncGetRuleWithEngineId(dottedName)
|
||||
) as Promise<EvaluatedNode>
|
||||
)
|
||||
),
|
||||
[nextQuestions, workerEngine],
|
||||
[] as EvaluatedRule[]
|
||||
)
|
||||
const nextSteps = useNextQuestions().map((dottedName) =>
|
||||
engine.evaluate(engine.getRule(dottedName))
|
||||
) as Array<EvaluatedRule>
|
||||
|
||||
const situationQuestions = useMemo(
|
||||
() =>
|
||||
|
@ -70,23 +92,33 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
|
|||
),
|
||||
[answeredAndPassedQuestions]
|
||||
)
|
||||
const companyQuestions = useMemo(
|
||||
const companyQuestions = usePromise(
|
||||
() =>
|
||||
Array.from(
|
||||
new Set(
|
||||
(
|
||||
[
|
||||
...answeredAndPassedQuestions.map(({ dottedName }) => dottedName),
|
||||
...Object.keys(situation),
|
||||
...Object.keys(companySituation),
|
||||
] as Array<DottedName>
|
||||
).filter(isCompanyDottedName)
|
||||
)
|
||||
).map((dottedName) => engine.getRule(dottedName)),
|
||||
[answeredAndPassedQuestions]
|
||||
Promise.all(
|
||||
Array.from(
|
||||
new Set(
|
||||
(
|
||||
[
|
||||
...answeredAndPassedQuestions.map(
|
||||
({ dottedName }) => dottedName
|
||||
),
|
||||
...Object.keys(situation),
|
||||
...Object.keys(companySituation),
|
||||
] as Array<DottedName>
|
||||
).filter(isCompanyDottedName)
|
||||
)
|
||||
).map((dottedName) => workerEngine.asyncGetRuleWithEngineId(dottedName))
|
||||
),
|
||||
[answeredAndPassedQuestions, companySituation, situation, workerEngine],
|
||||
[] as RuleNode<DottedName>[]
|
||||
)
|
||||
|
||||
const siret = engine.evaluate('établissement . SIRET').nodeValue as string
|
||||
const siret = usePromise(
|
||||
async () =>
|
||||
(await workerEngine.asyncEvaluateWithEngineId('établissement . SIRET'))
|
||||
.nodeValue as string,
|
||||
[workerEngine]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="answer-list">
|
||||
|
@ -101,7 +133,7 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
|
|||
<Trans>Simulation en cours</Trans>
|
||||
</H3>
|
||||
|
||||
<StepsTable {...{ rules: situationQuestions, onClose }} />
|
||||
<StepsTable rules={situationQuestions} onClose={onClose} />
|
||||
{children}
|
||||
<div
|
||||
className="print-hidden"
|
||||
|
@ -203,7 +235,7 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
|
|||
textAlign: 'center',
|
||||
}}
|
||||
></div>
|
||||
<StepsTable {...{ rules: companyQuestions, onClose }} />
|
||||
<StepsTable rules={companyQuestions} onClose={onClose} />
|
||||
<Spacing md />
|
||||
<div className="print-hidden">
|
||||
<Body style={{ marginTop: 0 }}>
|
||||
|
@ -231,7 +263,7 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
|
|||
<Emoji emoji="🔮 " />
|
||||
<Trans>Prochaines questions</Trans>
|
||||
</H2>
|
||||
<StepsTable {...{ rules: nextSteps, onClose }} />
|
||||
<StepsTable rules={nextSteps} onClose={onClose} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -259,7 +291,7 @@ function StepsTable({
|
|||
title: rule.title,
|
||||
})}
|
||||
light
|
||||
dottedName={rule.dottedName}
|
||||
dottedName={rule.dottedName as DottedName}
|
||||
/>
|
||||
</Grid>
|
||||
<StyledAnswer item xs="auto">
|
||||
|
@ -273,14 +305,19 @@ function StepsTable({
|
|||
|
||||
function AnswerElement(rule: RuleNode) {
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
|
||||
const workerEngine = useWorkerEngine()
|
||||
const parentDottedName = utils.ruleParent(rule.dottedName) as DottedName
|
||||
const questionDottedName = rule.rawNode.question
|
||||
? (rule.dottedName as DottedName)
|
||||
: parentDottedName && engine.getRule(parentDottedName).rawNode.API
|
||||
? parentDottedName
|
||||
: undefined
|
||||
const questionDottedName = usePromise(
|
||||
async () =>
|
||||
rule.rawNode.question
|
||||
? (rule.dottedName as DottedName)
|
||||
: parentDottedName &&
|
||||
(await workerEngine.asyncGetRuleWithEngineId(parentDottedName))
|
||||
.rawNode.API
|
||||
? parentDottedName
|
||||
: undefined,
|
||||
[parentDottedName, rule.dottedName, rule.rawNode.question, workerEngine]
|
||||
)
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: PublicodesExpression | undefined) => {
|
||||
|
@ -311,7 +348,7 @@ function AnswerElement(rule: RuleNode) {
|
|||
<>
|
||||
<form onSubmit={onClose}>
|
||||
<H3>
|
||||
{evaluateQuestion(engine, engine.getRule(questionDottedName))}
|
||||
{/* {evaluateQuestion(engine, engine.getRule(questionDottedName))} */}
|
||||
<ExplicableRule light dottedName={questionDottedName} />
|
||||
</H3>
|
||||
<RuleInput
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, { PublicodesExpression } from 'publicodes'
|
||||
import Engine, { PublicodesExpression, RuleNode } from 'publicodes'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
@ -7,7 +7,6 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||
import RuleInput from '@/components/conversation/RuleInput'
|
||||
import Notifications from '@/components/Notifications'
|
||||
import QuickLinks from '@/components/QuickLinks'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Grid, Spacing } from '@/design-system/layout'
|
||||
|
@ -19,7 +18,12 @@ import {
|
|||
answeredQuestionsSelector,
|
||||
situationSelector,
|
||||
} from '@/store/selectors/simulationSelectors'
|
||||
import { evaluateQuestion } from '@/utils'
|
||||
import {
|
||||
useAsyncGetRule,
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { TrackPage } from '../ATInternetTracking'
|
||||
import { JeDonneMonAvis } from '../JeDonneMonAvis'
|
||||
|
@ -32,17 +36,16 @@ import { useNavigateQuestions } from './useNavigateQuestions'
|
|||
export type ConversationProps = {
|
||||
customEndMessages?: React.ReactNode
|
||||
customSituationVisualisation?: React.ReactNode
|
||||
engines?: Array<Engine<DottedName>>
|
||||
workerEngines?: WorkerEngine[]
|
||||
}
|
||||
|
||||
export default function Conversation({
|
||||
customEndMessages,
|
||||
customSituationVisualisation,
|
||||
engines,
|
||||
workerEngines,
|
||||
}: ConversationProps) {
|
||||
const { currentSimulatorData } = useCurrentSimulatorData()
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
|
||||
const situation = useSelector(situationSelector)
|
||||
|
||||
|
@ -55,7 +58,7 @@ export default function Conversation({
|
|||
currentQuestionIsAnswered,
|
||||
goToPrevious: goToPreviousQuestion,
|
||||
goToNext: goToNextQuestion,
|
||||
} = useNavigateQuestions(engines)
|
||||
} = useNavigateQuestions(workerEngines)
|
||||
|
||||
const onChange = (
|
||||
value: PublicodesExpression | undefined,
|
||||
|
@ -88,6 +91,13 @@ export default function Conversation({
|
|||
}, [focusFirstElemInForm, goToNextQuestion])
|
||||
|
||||
const formRef = React.useRef<HTMLFormElement>(null)
|
||||
const workerEngine = useWorkerEngine()
|
||||
const rule = useAsyncGetRule(currentQuestion)
|
||||
|
||||
const question = usePromiseOnSituationChange(
|
||||
async () => rule && evaluateQuestion(workerEngine, rule),
|
||||
[rule, workerEngine]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -120,7 +130,7 @@ export default function Conversation({
|
|||
}}
|
||||
>
|
||||
<H3 id="questionHeader" as="h2">
|
||||
{evaluateQuestion(engine, engine.getRule(currentQuestion))}
|
||||
{question}
|
||||
<ExplicableRule light dottedName={currentQuestion} />
|
||||
</H3>
|
||||
</div>
|
||||
|
@ -241,3 +251,19 @@ export default function Conversation({
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function evaluateQuestion(
|
||||
workerEngine: WorkerEngine,
|
||||
rule: RuleNode
|
||||
) {
|
||||
const question = rule.rawNode.question
|
||||
if (question && typeof question === 'object') {
|
||||
return (
|
||||
await workerEngine.asyncEvaluateWithEngineId(
|
||||
question as PublicodesExpression
|
||||
)
|
||||
).nodeValue as string
|
||||
}
|
||||
|
||||
return question
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { InputProps } from '@/components/conversation/RuleInput'
|
|||
import { DateField } from '@/design-system/field'
|
||||
import { DateFieldProps } from '@/design-system/field/DateField'
|
||||
|
||||
import { useEngine } from '../utils/EngineContext'
|
||||
// import { useEngine } from '../utils/EngineContext'
|
||||
import InputSuggestions from './InputSuggestions'
|
||||
|
||||
export default function DateInput({
|
||||
|
@ -17,7 +17,7 @@ export default function DateInput({
|
|||
value,
|
||||
type,
|
||||
}: InputProps & { type: DateFieldProps['type'] }) {
|
||||
const engine = useEngine()
|
||||
// const engine = useEngine()
|
||||
|
||||
const convertDate = (val?: unknown) => {
|
||||
if (!val || typeof val !== 'string') {
|
||||
|
@ -47,13 +47,12 @@ export default function DateInput({
|
|||
<InputSuggestions
|
||||
suggestions={suggestions}
|
||||
onFirstClick={(node) => {
|
||||
const value = engine.evaluate(node)
|
||||
|
||||
handleDateChange(
|
||||
'nodeValue' in value && typeof value.nodeValue === 'string'
|
||||
? value.nodeValue
|
||||
: undefined
|
||||
)
|
||||
// const value = engine.evaluate(node)
|
||||
// handleDateChange(
|
||||
// 'nodeValue' in value && typeof value.nodeValue === 'string'
|
||||
// ? value.nodeValue
|
||||
// : undefined
|
||||
// )
|
||||
}}
|
||||
onSecondClick={() => {
|
||||
onSubmit?.('suggestion')
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import { useContext } from 'react'
|
||||
import { useContext, useEffect } from 'react'
|
||||
|
||||
import { EngineContext } from '@/components/utils/EngineContext'
|
||||
// import { EngineContext } from '@/components/utils/EngineContext'
|
||||
import { Markdown } from '@/components/utils/markdown'
|
||||
import HelpButtonWithPopover from '@/design-system/buttons/HelpButtonWithPopover'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { H3 } from '@/design-system/typography/heading'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import { useWorkerEngine } from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { References } from '../References'
|
||||
import RuleLink from '../RuleLink'
|
||||
|
||||
export function ExplicableRule<Names extends string = DottedName>({
|
||||
export function ExplicableRule<Names extends DottedName>({
|
||||
dottedName,
|
||||
light,
|
||||
bigPopover,
|
||||
|
@ -22,15 +24,21 @@ export function ExplicableRule<Names extends string = DottedName>({
|
|||
bigPopover?: boolean
|
||||
title?: string
|
||||
}) {
|
||||
const engine = useContext(EngineContext)
|
||||
const workerEngine = useWorkerEngine()
|
||||
const rule = usePromise(
|
||||
async () =>
|
||||
dottedName != null
|
||||
? workerEngine.asyncGetRuleWithEngineId(dottedName)
|
||||
: null,
|
||||
[dottedName, workerEngine]
|
||||
)
|
||||
|
||||
// Rien à expliquer ici, ce n'est pas une règle
|
||||
if (dottedName == null) {
|
||||
return null
|
||||
}
|
||||
const rule = engine.getRule(dottedName)
|
||||
|
||||
if (rule.rawNode.description == null) {
|
||||
if (rule?.rawNode.description == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -50,9 +58,7 @@ export function ExplicableRule<Names extends string = DottedName>({
|
|||
>
|
||||
<Markdown>{rule.rawNode.description}</Markdown>
|
||||
|
||||
<RuleLink dottedName={dottedName as DottedName}>
|
||||
Lire la documentation
|
||||
</RuleLink>
|
||||
<RuleLink dottedName={dottedName}>Lire la documentation</RuleLink>
|
||||
|
||||
{rule.rawNode.références && (
|
||||
<>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { SmallBody } from '@/design-system/typography/paragraphs'
|
|||
|
||||
type InputSuggestionsProps = {
|
||||
suggestions?: Record<string, ASTNode>
|
||||
onFirstClick: (val: ASTNode) => void
|
||||
onFirstClick: (val: ASTNode) => void | Promise<void>
|
||||
onSecondClick?: (val: ASTNode) => void
|
||||
className?: string
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export default function InputSuggestions({
|
|||
<Link
|
||||
key={text}
|
||||
onPress={() => {
|
||||
onFirstClick(value)
|
||||
void onFirstClick(value)
|
||||
if (suggestion !== value) {
|
||||
setSuggestion(value)
|
||||
} else {
|
||||
|
|
|
@ -5,35 +5,53 @@ import { useTranslation } from 'react-i18next'
|
|||
|
||||
import { Checkbox } from '@/design-system'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { ExplicableRule } from './Explicable'
|
||||
import { InputProps } from './RuleInput'
|
||||
import { InputProps, RuleWithMultiplePossibilities } from './RuleInput'
|
||||
|
||||
export function MultipleChoicesInput<Names extends string = DottedName>(
|
||||
props: Omit<InputProps<Names>, 'onChange'> & {
|
||||
choices: Array<RuleNode<Names>>
|
||||
onChange: (value: PublicodesExpression, name: Names) => void
|
||||
props: Omit<InputProps<DottedName>, 'onChange'> & {
|
||||
engineId: number
|
||||
onChange: (value: PublicodesExpression, name: DottedName) => void
|
||||
}
|
||||
) {
|
||||
const handleChange = (isSelected: boolean, dottedName: Names) => {
|
||||
const { engineId, dottedName, onChange } = props
|
||||
const workerEngine = useWorkerEngine()
|
||||
const choices = usePromise(
|
||||
() => getMultiplePossibilitiesOptions(workerEngine, engineId, dottedName),
|
||||
[dottedName, engineId, workerEngine],
|
||||
[] as RuleNode<DottedName>[]
|
||||
)
|
||||
|
||||
const handleChange = (isSelected: boolean, dottedName: DottedName) => {
|
||||
// As soon as one option is selected, all the others are not missing anymore
|
||||
return props.choices.forEach((choice) => {
|
||||
const value =
|
||||
dottedName === choice.dottedName
|
||||
? isSelected
|
||||
: props.engine.evaluate(choice).nodeValue
|
||||
props.onChange(value ? 'oui' : 'non', choice.dottedName)
|
||||
})
|
||||
return Promise.all(
|
||||
choices.map(async (choice) => {
|
||||
const value =
|
||||
dottedName === choice.dottedName
|
||||
? isSelected
|
||||
: (await workerEngine.asyncEvaluateWithEngineId(choice)).nodeValue
|
||||
onChange(value ? 'oui' : 'non', choice.dottedName)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div aria-labelledby="questionHeader" role="group">
|
||||
{props.choices.map((node) => (
|
||||
{choices.map((node) => (
|
||||
<Fragment key={node.dottedName}>
|
||||
<CheckBoxRule
|
||||
node={node}
|
||||
onChange={(isSelected) => handleChange(isSelected, node.dottedName)}
|
||||
engine={props.engine}
|
||||
onChange={(isSelected) =>
|
||||
void handleChange(isSelected, node.dottedName)
|
||||
}
|
||||
engineId={engineId}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
@ -43,11 +61,16 @@ export function MultipleChoicesInput<Names extends string = DottedName>(
|
|||
|
||||
type CheckBoxRuleProps = {
|
||||
node: RuleNode
|
||||
engine: Engine
|
||||
engineId: number
|
||||
onChange: (isSelected: boolean) => void
|
||||
}
|
||||
function CheckBoxRule({ node, engine, onChange }: CheckBoxRuleProps) {
|
||||
const evaluation = engine.evaluate(node)
|
||||
function CheckBoxRule({ node, engineId, onChange }: CheckBoxRuleProps) {
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const evaluation = usePromiseOnSituationChange(
|
||||
() => workerEngine.asyncEvaluateWithEngineId(engineId, node),
|
||||
[engineId, node, workerEngine]
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
if (evaluation.nodeValue === null) {
|
||||
return null
|
||||
|
@ -73,3 +96,32 @@ function CheckBoxRule({ node, engine, onChange }: CheckBoxRuleProps) {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function getMultiplePossibilitiesOptions(
|
||||
workerEngine: WorkerEngine,
|
||||
engineId: number,
|
||||
// engine: Engine<Name>,
|
||||
dottedName: DottedName
|
||||
): Promise<RuleNode<DottedName>[]> {
|
||||
// return (
|
||||
// (engine.getRule(dottedName) as RuleWithMultiplePossibilities).rawNode[
|
||||
// 'plusieurs possibilités'
|
||||
// ] ?? []
|
||||
// ).map((name) => engine.getRule(`${dottedName} . ${name}` as Name))
|
||||
const posibilities =
|
||||
(
|
||||
(await workerEngine.asyncGetRuleWithEngineId(
|
||||
engineId,
|
||||
dottedName
|
||||
)) as RuleWithMultiplePossibilities
|
||||
).rawNode['plusieurs possibilités'] ?? []
|
||||
|
||||
return await Promise.all(
|
||||
posibilities.map((name) =>
|
||||
workerEngine.asyncGetRuleWithEngineId(
|
||||
engineId,
|
||||
`${dottedName} . ${name}` as DottedName
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { NumberFieldProps } from '@react-types/numberfield'
|
||||
import { ASTNode, parseUnit, serializeUnit, Unit } from 'publicodes'
|
||||
import { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import { EngineContext } from '@/components/utils/EngineContext'
|
||||
// import { EngineContext } from '@/components/utils/EngineContext'
|
||||
import { NumberField } from '@/design-system/field'
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
|
@ -30,7 +30,7 @@ export default function NumberInput({
|
|||
)
|
||||
const { i18n, t } = useTranslation()
|
||||
const parsedDisplayedUnit = displayedUnit ? parseUnit(displayedUnit) : unit
|
||||
const engine = useContext(EngineContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== currentValue) {
|
||||
setCurrentValue(
|
||||
|
@ -41,23 +41,27 @@ export default function NumberInput({
|
|||
}
|
||||
}, [value])
|
||||
|
||||
if (parsedDisplayedUnit && parsedDisplayedUnit.numerators.includes('€')) {
|
||||
parsedDisplayedUnit.numerators = parsedDisplayedUnit.numerators.filter(
|
||||
(u) => u === '€'
|
||||
)
|
||||
formatOptions = {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
minimumFractionDigits: 0,
|
||||
|
||||
...formatOptions,
|
||||
}
|
||||
} else {
|
||||
formatOptions = {
|
||||
const format = useMemo(() => {
|
||||
let ret = {
|
||||
style: 'decimal',
|
||||
...formatOptions,
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedDisplayedUnit && parsedDisplayedUnit.numerators.includes('€')) {
|
||||
parsedDisplayedUnit.numerators = parsedDisplayedUnit.numerators.filter(
|
||||
(u) => u === '€'
|
||||
)
|
||||
ret = {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
minimumFractionDigits: 0,
|
||||
...formatOptions,
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}, [formatOptions, parsedDisplayedUnit])
|
||||
|
||||
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
|
||||
|
||||
return (
|
||||
|
@ -83,7 +87,7 @@ export default function NumberInput({
|
|||
debouncedOnChange(valeur)
|
||||
}
|
||||
}}
|
||||
formatOptions={formatOptions}
|
||||
formatOptions={format}
|
||||
placeholder={
|
||||
missing && value != null && typeof value === 'number'
|
||||
? value
|
||||
|
@ -94,8 +98,8 @@ export default function NumberInput({
|
|||
<InputSuggestions
|
||||
className="print-hidden"
|
||||
suggestions={suggestions}
|
||||
onFirstClick={(node: ASTNode) => {
|
||||
const evaluatedNode = engine.evaluate(node)
|
||||
onFirstClick={async (node: ASTNode) => {
|
||||
const evaluatedNode = await asyncEvaluate(node)
|
||||
if (serializeUnit(evaluatedNode.unit) === serializeUnit(unit)) {
|
||||
setCurrentValue(evaluatedNode.nodeValue as number)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, {
|
||||
import {
|
||||
ASTNode,
|
||||
EvaluatedNode,
|
||||
Evaluation,
|
||||
|
@ -7,13 +7,19 @@ import Engine, {
|
|||
reduceAST,
|
||||
RuleNode,
|
||||
} from 'publicodes'
|
||||
import React, { useContext } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import NumberInput from '@/components/conversation/NumberInput'
|
||||
import SelectCommune from '@/components/conversation/select/SelectCommune'
|
||||
import { EngineContext } from '@/components/utils/EngineContext'
|
||||
import { DateFieldProps } from '@/design-system/field/DateField'
|
||||
import { getMeta } from '@/utils'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import { getMeta, isNotNull } from '@/utils'
|
||||
import {
|
||||
useAsyncGetRule,
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { Choice, MultipleAnswerInput, OuiNonInput } from './ChoicesInput'
|
||||
import DateInput from './DateInput'
|
||||
|
@ -49,7 +55,8 @@ type Props<Names extends string = DottedName> = Omit<
|
|||
formatOptions?: Intl.NumberFormatOptions
|
||||
displayedUnit?: string
|
||||
modifiers?: Record<string, string>
|
||||
engine?: Engine<DottedName>
|
||||
// engine?: Engine<DottedName>
|
||||
engineId?: number
|
||||
}
|
||||
|
||||
export type InputProps<Name extends string = string> = Omit<
|
||||
|
@ -61,7 +68,8 @@ export type InputProps<Name extends string = string> = Omit<
|
|||
description: RuleNode['rawNode']['description']
|
||||
value: EvaluatedNode['nodeValue']
|
||||
onChange: (value: PublicodesExpression | undefined) => void
|
||||
engine: Engine<Name>
|
||||
// engine: Engine<Name>
|
||||
engineId: number
|
||||
}
|
||||
|
||||
export const binaryQuestion = [
|
||||
|
@ -73,7 +81,7 @@ export const binaryQuestion = [
|
|||
// be displayed to get a user input through successive if statements
|
||||
// That's not great, but we won't invest more time until we have more diverse
|
||||
// input components and a better type system.
|
||||
export default function RuleInput<Names extends string = DottedName>({
|
||||
export default function RuleInput({
|
||||
dottedName,
|
||||
onChange,
|
||||
showSuggestions = true,
|
||||
|
@ -81,24 +89,64 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
showDefaultDateValue = false,
|
||||
missing,
|
||||
inputType,
|
||||
modifiers = {},
|
||||
engine,
|
||||
modifiers,
|
||||
engineId = 0,
|
||||
...props
|
||||
}: Props<Names>) {
|
||||
const defaultEngine = useContext(EngineContext)
|
||||
}: Props<DottedName>) {
|
||||
// const defaultEngine = useContext(EngineContext)
|
||||
|
||||
const engineValue = (engine ?? defaultEngine) as Engine<Names>
|
||||
// const engineValue = (engine ?? defaultEngine) as Engine<Names>
|
||||
|
||||
const rule = engineValue.getRule(dottedName)
|
||||
const evaluation = engineValue.evaluate({ valeur: dottedName, ...modifiers })
|
||||
const value = evaluation.nodeValue
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const commonProps: InputProps<Names> = {
|
||||
const rule = useAsyncGetRule(dottedName)
|
||||
|
||||
// const evaluation = engineValue.evaluate({ valeur: dottedName, ...modifiers })
|
||||
// async
|
||||
const evaluation = usePromiseOnSituationChange(
|
||||
() =>
|
||||
workerEngine.asyncEvaluateWithEngineId({
|
||||
valeur: dottedName,
|
||||
...(modifiers ?? {}),
|
||||
}),
|
||||
[dottedName, modifiers, workerEngine]
|
||||
)
|
||||
|
||||
const value = evaluation?.nodeValue
|
||||
|
||||
const isMultipleChoices = usePromiseOnSituationChange(
|
||||
async () =>
|
||||
rule && isMultiplePossibilities(workerEngine, engineId, dottedName),
|
||||
[dottedName, engineId, rule, workerEngine]
|
||||
)
|
||||
|
||||
console.log('=>', dottedName)
|
||||
|
||||
const choice = usePromise(
|
||||
() => getOnePossibilityOptions(workerEngine, dottedName),
|
||||
[workerEngine.situationVersion, dottedName]
|
||||
)
|
||||
|
||||
dottedName === 'entreprise . activité . nature' &&
|
||||
console.log(
|
||||
'choice',
|
||||
isMultipleChoices,
|
||||
choice,
|
||||
rule && isOnePossibility(rule)
|
||||
)
|
||||
|
||||
if (!rule || isMultipleChoices === undefined) {
|
||||
return <p>Chargement...</p>
|
||||
}
|
||||
|
||||
const commonProps: InputProps<DottedName> = {
|
||||
dottedName,
|
||||
value,
|
||||
missing:
|
||||
missing ??
|
||||
(!showDefaultDateValue && dottedName in evaluation.missingVariables),
|
||||
(!showDefaultDateValue &&
|
||||
evaluation &&
|
||||
dottedName in evaluation.missingVariables),
|
||||
onChange: (value: PublicodesExpression | undefined) =>
|
||||
onChange(value, dottedName),
|
||||
onSubmit,
|
||||
|
@ -106,24 +154,25 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
description: rule.rawNode.description,
|
||||
question: rule.rawNode.question,
|
||||
suggestions: showSuggestions ? rule.suggestions : {},
|
||||
engine: engineValue,
|
||||
// engine: engineValue,
|
||||
engineId,
|
||||
...props,
|
||||
// Les espaces ne sont pas autorisés dans un id, les points sont assimilés à une déclaration de class CSS par Cypress
|
||||
id: props?.id?.replace(/\s|\.]/g, '_') ?? dottedName.replace(/\s|\./g, '_'),
|
||||
}
|
||||
const meta = getMeta<{ affichage?: string }>(rule.rawNode, {})
|
||||
|
||||
if (isMultiplePossibilities(engineValue, dottedName)) {
|
||||
if (isMultipleChoices) {
|
||||
return (
|
||||
<MultipleChoicesInput
|
||||
{...commonProps}
|
||||
choices={getMultiplePossibilitiesOptions(engineValue, dottedName)}
|
||||
engineId={engineId}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isOnePossibility(engineValue.getRule(dottedName))) {
|
||||
if (isOnePossibility(rule) && choice) {
|
||||
const type =
|
||||
inputType ??
|
||||
(meta.affichage &&
|
||||
|
@ -131,32 +180,22 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
? (meta.affichage as 'radio' | 'card' | 'toggle' | 'select')
|
||||
: 'radio')
|
||||
|
||||
return (
|
||||
<MultipleAnswerInput
|
||||
{...commonProps}
|
||||
choice={getOnePossibilityOptions(engineValue, dottedName)}
|
||||
type={type}
|
||||
/>
|
||||
)
|
||||
return <MultipleAnswerInput {...commonProps} choice={choice} type={type} />
|
||||
}
|
||||
|
||||
if (rule.rawNode.API && rule.rawNode.API === 'commune') {
|
||||
return (
|
||||
<SelectCommune
|
||||
return `<SelectCommune
|
||||
{...commonProps}
|
||||
onChange={(c) => commonProps.onChange({ batchUpdate: c })}
|
||||
value={value as Evaluation<string>}
|
||||
/>
|
||||
)
|
||||
/>`
|
||||
}
|
||||
|
||||
if (rule.rawNode.API && rule.rawNode.API.startsWith('pays détachement')) {
|
||||
return (
|
||||
<SelectPaysDétachement
|
||||
return `<SelectPaysDétachement
|
||||
{...commonProps}
|
||||
plusFrance={rule.rawNode.API.endsWith('plus France')}
|
||||
/>
|
||||
)
|
||||
/>`
|
||||
}
|
||||
if (rule.rawNode.API) {
|
||||
throw new Error(
|
||||
|
@ -165,39 +204,33 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
}
|
||||
|
||||
if (rule.dottedName === 'établissement . taux ATMP . taux collectif') {
|
||||
return <SelectAtmp {...commonProps} />
|
||||
return '<SelectAtmp {...commonProps} />'
|
||||
}
|
||||
|
||||
if (rule.rawNode.type?.startsWith('date')) {
|
||||
return (
|
||||
<DateInput
|
||||
return `<DateInput
|
||||
{...commonProps}
|
||||
type={rule.rawNode.type as DateFieldProps['type']}
|
||||
/>
|
||||
)
|
||||
/>`
|
||||
}
|
||||
|
||||
if (
|
||||
evaluation.unit == null &&
|
||||
evaluation?.unit == null &&
|
||||
['booléen', 'notification', undefined].includes(rule.rawNode.type) &&
|
||||
typeof evaluation.nodeValue !== 'number'
|
||||
typeof evaluation?.nodeValue !== 'number'
|
||||
) {
|
||||
return <OuiNonInput {...commonProps} />
|
||||
}
|
||||
|
||||
if (rule.rawNode.type === 'texte') {
|
||||
return (
|
||||
<TextInput
|
||||
return `<TextInput
|
||||
{...commonProps}
|
||||
label={undefined}
|
||||
value={value as Evaluation<string>}
|
||||
/>
|
||||
)
|
||||
/>`
|
||||
}
|
||||
if (rule.rawNode.type === 'paragraphe') {
|
||||
return (
|
||||
<ParagrapheInput {...commonProps} value={value as Evaluation<string>} />
|
||||
)
|
||||
return '<ParagrapheInput {...commonProps} value={value as Evaluation<string>} />'
|
||||
}
|
||||
|
||||
// Pas de title sur NumberInput pour avoir une bonne expérience avec
|
||||
|
@ -207,7 +240,7 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
return (
|
||||
<NumberInput
|
||||
{...commonProps}
|
||||
unit={evaluation.unit}
|
||||
unit={evaluation?.unit}
|
||||
value={value as Evaluation<number>}
|
||||
/>
|
||||
)
|
||||
|
@ -224,11 +257,15 @@ const isOnePossibility = (node: RuleNode) =>
|
|||
node
|
||||
)
|
||||
|
||||
export const getOnePossibilityOptions = <Name extends string>(
|
||||
engine: Engine<Name>,
|
||||
path: Name
|
||||
): Choice => {
|
||||
const node = engine.getRule(path)
|
||||
const getOnePossibilityOptions = async (
|
||||
workerEngine: WorkerEngine,
|
||||
// engineId: number,
|
||||
path: DottedName
|
||||
): Promise<Choice> => {
|
||||
const node = await workerEngine.asyncGetRuleWithEngineId(path)
|
||||
|
||||
// if (path === 'entreprise . activité . nature') debugger
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`La règle ${path} est introuvable`)
|
||||
}
|
||||
|
@ -237,47 +274,58 @@ export const getOnePossibilityOptions = <Name extends string>(
|
|||
variant &&
|
||||
(!variant['choix obligatoire'] || variant['choix obligatoire'] === 'non')
|
||||
|
||||
return Object.assign(
|
||||
const ttt = Object.assign(
|
||||
node,
|
||||
variant
|
||||
? {
|
||||
canGiveUp,
|
||||
children: (
|
||||
variant.explanation as (ASTNode & {
|
||||
nodeKind: 'reference'
|
||||
})[]
|
||||
)
|
||||
.filter(
|
||||
(explanation) => engine.evaluate(explanation).nodeValue !== null
|
||||
await Promise.all(
|
||||
(
|
||||
variant.explanation as (ASTNode & { nodeKind: 'reference' })[]
|
||||
).map(async (explanation) => {
|
||||
console.log('=>>>>', explanation)
|
||||
|
||||
const evaluate = await workerEngine.asyncEvaluateWithEngineId(
|
||||
explanation
|
||||
)
|
||||
|
||||
return evaluate.nodeValue !== null
|
||||
? await getOnePossibilityOptions(
|
||||
workerEngine,
|
||||
explanation.dottedName as DottedName
|
||||
)
|
||||
: null
|
||||
})
|
||||
)
|
||||
.map(({ dottedName }) =>
|
||||
getOnePossibilityOptions(engine, dottedName as Name)
|
||||
),
|
||||
).filter(isNotNull),
|
||||
}
|
||||
: null
|
||||
) as Choice
|
||||
|
||||
console.log('choice=>', ttt)
|
||||
|
||||
return ttt
|
||||
}
|
||||
|
||||
type RuleWithMultiplePossibilities = RuleNode & {
|
||||
export type RuleWithMultiplePossibilities = RuleNode & {
|
||||
rawNode: RuleNode['rawNode'] & {
|
||||
'plusieurs possibilités'?: Array<string>
|
||||
}
|
||||
}
|
||||
function isMultiplePossibilities<Name extends string>(
|
||||
engine: Engine<Name>,
|
||||
dottedName: Name
|
||||
): boolean {
|
||||
return !!(engine.getRule(dottedName) as RuleWithMultiplePossibilities)
|
||||
.rawNode['plusieurs possibilités']
|
||||
}
|
||||
|
||||
function getMultiplePossibilitiesOptions<Name extends string>(
|
||||
engine: Engine<Name>,
|
||||
dottedName: Name
|
||||
): RuleNode<Name>[] {
|
||||
return (
|
||||
(engine.getRule(dottedName) as RuleWithMultiplePossibilities).rawNode[
|
||||
'plusieurs possibilités'
|
||||
] ?? []
|
||||
).map((name) => engine.getRule(`${dottedName} . ${name}` as Name))
|
||||
async function isMultiplePossibilities(
|
||||
workerEngine: WorkerEngine,
|
||||
engineId: number,
|
||||
// Engine<Name>,
|
||||
dottedName: DottedName
|
||||
): Promise<boolean> {
|
||||
// return !!(engine.getRule(dottedName) as RuleWithMultiplePossibilities)
|
||||
// .rawNode['plusieurs possibilités']
|
||||
|
||||
return !!(
|
||||
(await workerEngine.asyncGetRuleWithEngineId(
|
||||
dottedName
|
||||
)) as RuleWithMultiplePossibilities
|
||||
).rawNode['plusieurs possibilités']
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import { useEffect } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { useNextQuestions } from '@/hooks/useNextQuestion'
|
||||
import {
|
||||
goToQuestion,
|
||||
|
@ -15,14 +12,17 @@ import {
|
|||
currentQuestionSelector,
|
||||
useMissingVariables,
|
||||
} from '@/store/selectors/simulationSelectors'
|
||||
import {
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
export function useNavigateQuestions(engines?: Array<Engine<DottedName>>) {
|
||||
export function useNavigateQuestions(workerEngines?: WorkerEngine[]) {
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
const nextQuestion = useNextQuestions(engines)[0]
|
||||
const nextQuestions = useNextQuestions(workerEngines)
|
||||
const currentQuestion = useSelector(currentQuestionSelector)
|
||||
|
||||
const missingVariables = useMissingVariables({ engines: engines ?? [engine] })
|
||||
const missingVariables = useMissingVariables(workerEngines)
|
||||
const currentQuestionIsAnswered =
|
||||
currentQuestion && !(currentQuestion in missingVariables)
|
||||
|
||||
|
@ -40,13 +40,13 @@ export function useNavigateQuestions(engines?: Array<Engine<DottedName>>) {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentQuestion && nextQuestion) {
|
||||
dispatch(goToQuestion(nextQuestion))
|
||||
if (!currentQuestion && nextQuestions[0]) {
|
||||
dispatch(goToQuestion(nextQuestions[0]))
|
||||
}
|
||||
}, [nextQuestion, currentQuestion])
|
||||
}, [nextQuestions, currentQuestion, dispatch])
|
||||
|
||||
return {
|
||||
currentQuestion: currentQuestion ?? nextQuestion,
|
||||
currentQuestion: currentQuestion ?? nextQuestions[0],
|
||||
currentQuestionIsAnswered,
|
||||
goToPrevious,
|
||||
goToNext,
|
||||
|
|
|
@ -13,7 +13,6 @@ import Value, {
|
|||
} from '@/components/EngineValue'
|
||||
import RuleLink from '@/components/RuleLink'
|
||||
import { FromBottom } from '@/components/ui/animate'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Message } from '@/design-system'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
|
@ -171,25 +170,36 @@ export function ImpôtsDGFIP({ role }: { role?: string }) {
|
|||
)
|
||||
}
|
||||
|
||||
const caisses = [
|
||||
'CARCDSF',
|
||||
'CARPIMKO',
|
||||
'CIPAV',
|
||||
'CARMF',
|
||||
'CNBF',
|
||||
'CAVEC',
|
||||
'CAVP',
|
||||
] as const
|
||||
|
||||
function CaisseRetraite({ role }: { role?: string }) {
|
||||
const engine = useEngine()
|
||||
const unit = useSelector(targetUnitSelector)
|
||||
const caisses = [
|
||||
'CARCDSF',
|
||||
'CARPIMKO',
|
||||
'CIPAV',
|
||||
'CARMF',
|
||||
'CNBF',
|
||||
'CAVEC',
|
||||
'CAVP',
|
||||
] as const
|
||||
const rules = usePromiseOnSituationChange(
|
||||
() =>
|
||||
Promise.all(
|
||||
caisses.map((caisse) =>
|
||||
asyncGetRule(
|
||||
`dirigeant . indépendant . PL . ${caisse} . cotisations` as DottedName
|
||||
)
|
||||
)
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{caisses.map((caisse) => {
|
||||
{caisses.map((caisse, index) => {
|
||||
const dottedName =
|
||||
`dirigeant . indépendant . PL . ${caisse}` as DottedName
|
||||
const { description, références } = engine.getRule(dottedName).rawNode
|
||||
const { description, références } = rules?.[index].rawNode || {}
|
||||
|
||||
return (
|
||||
<Condition expression={dottedName} key={caisse}>
|
||||
|
@ -243,9 +253,9 @@ function CaisseRetraite({ role }: { role?: string }) {
|
|||
|
||||
export function InstitutionsPartenairesArtisteAuteur() {
|
||||
const unit = useSelector(targetUnitSelector)
|
||||
const { description: descriptionIRCEC } = useEngine().getRule(
|
||||
'artiste-auteur . cotisations . IRCEC'
|
||||
).rawNode
|
||||
const descriptionIRCEC = useAsyncGetRule(
|
||||
'artiste-auteur . cotisations . IRCEC' as DottedName
|
||||
)?.rawNode.description
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
|
|
@ -6,7 +6,7 @@ import Engine, {
|
|||
Rule,
|
||||
RuleNode,
|
||||
} from 'publicodes'
|
||||
import { createContext, useContext, useMemo } from 'react'
|
||||
import { createContext, useContext, useEffect, useMemo } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { deleteFromSituation } from '@/store/actions/actions'
|
||||
|
@ -16,6 +16,10 @@ import {
|
|||
situationSelector,
|
||||
} from '@/store/selectors/simulationSelectors'
|
||||
import { omit } from '@/utils'
|
||||
import {
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import i18n from '../../locales/i18n'
|
||||
|
||||
|
@ -63,12 +67,12 @@ export function engineFactory(rules: Rules, options = {}) {
|
|||
return new Engine(rules, { ...engineOptions, ...options, logger })
|
||||
}
|
||||
|
||||
export const EngineContext = createContext<Engine>(new Engine())
|
||||
export const EngineProvider = EngineContext.Provider
|
||||
// export const EngineContext = createContext<Engine>(new Engine())
|
||||
// export const EngineProvider = EngineContext.Provider
|
||||
|
||||
export function useEngine() {
|
||||
return useContext(EngineContext) as Engine<DottedName>
|
||||
}
|
||||
// export function useEngine() {
|
||||
// return useContext(EngineContext) as Engine<DottedName>
|
||||
// }
|
||||
|
||||
export const useRawSituation = () => {
|
||||
const simulatorSituation = useSelector(situationSelector)
|
||||
|
@ -97,6 +101,8 @@ export const safeSetSituation = <Names extends string>(
|
|||
situation: Partial<Record<Names, PublicodesExpression>>
|
||||
faultyDottedName?: Names
|
||||
}) => void
|
||||
// rawSituation: Parameters<Engine<Names>['setSituation']>[0],
|
||||
// options: Parameters<Engine<Names>['setSituation']>[1]
|
||||
) => {
|
||||
let situationError = false
|
||||
const errors: Error[] = []
|
||||
|
@ -104,6 +110,7 @@ export const safeSetSituation = <Names extends string>(
|
|||
do {
|
||||
try {
|
||||
// Try to set situation
|
||||
// engine.setSituation(situation, options)
|
||||
engine.setSituation(situation)
|
||||
situationError = false
|
||||
} catch (error) {
|
||||
|
@ -148,46 +155,58 @@ export const safeSetSituation = <Names extends string>(
|
|||
} while (situationError && errors.length < 1000)
|
||||
}
|
||||
|
||||
export const useSetupSafeSituation = (engine: Engine<DottedName>) => {
|
||||
// engine: Engine<DottedName>
|
||||
export const useSetupSafeSituation = (workerEngine?: WorkerEngine) => {
|
||||
const dispatch = useDispatch()
|
||||
// const workerEngine = useWorkerEngine()
|
||||
const rawSituation = useRawSituation()
|
||||
|
||||
const simulatorSituation = useSelector(situationSelector)
|
||||
const configSituation = useSelector(configSituationSelector)
|
||||
const companySituation = useSelector(companySituationSelector)
|
||||
const { asyncSetSituationWithEngineId } = workerEngine ?? {}
|
||||
|
||||
try {
|
||||
safeSetSituation(engine, rawSituation, ({ faultyDottedName }) => {
|
||||
if (!faultyDottedName) {
|
||||
throw new Error('Bad empty faultyDottedName')
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!asyncSetSituationWithEngineId) {
|
||||
return
|
||||
}
|
||||
console.log('set rawSituation', rawSituation, workerEngine)
|
||||
|
||||
if (faultyDottedName in simulatorSituation) {
|
||||
dispatch(deleteFromSituation(faultyDottedName))
|
||||
} else {
|
||||
throw new Error(
|
||||
'Bad ' +
|
||||
(faultyDottedName in configSituation
|
||||
? 'config'
|
||||
: faultyDottedName in companySituation
|
||||
? 'company'
|
||||
: 'unknow') +
|
||||
' situation : ' +
|
||||
JSON.stringify(faultyDottedName)
|
||||
)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
void asyncSetSituationWithEngineId(rawSituation)
|
||||
}, [asyncSetSituationWithEngineId, rawSituation])
|
||||
|
||||
engine.setSituation()
|
||||
}
|
||||
// try {
|
||||
// safeSetSituation(engine, rawSituation, ({ faultyDottedName }) => {
|
||||
// if (!faultyDottedName) {
|
||||
// throw new Error('Bad empty faultyDottedName')
|
||||
// }
|
||||
|
||||
// if (faultyDottedName in simulatorSituation) {
|
||||
// dispatch(deleteFromSituation(faultyDottedName))
|
||||
// } else {
|
||||
// throw new Error(
|
||||
// 'Bad ' +
|
||||
// (faultyDottedName in configSituation
|
||||
// ? 'config'
|
||||
// : faultyDottedName in companySituation
|
||||
// ? 'company'
|
||||
// : 'unknow') +
|
||||
// ' situation : ' +
|
||||
// JSON.stringify(faultyDottedName)
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
// } catch (error) {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.error(error)
|
||||
|
||||
// engine.setSituation()
|
||||
// }
|
||||
}
|
||||
|
||||
export function useInversionFail() {
|
||||
return useContext(EngineContext).inversionFail()
|
||||
}
|
||||
// export function useInversionFail() {
|
||||
// return useContext(EngineContext).inversionFail()
|
||||
// }
|
||||
|
||||
export type EvaluatedRule = EvaluatedNode &
|
||||
RuleNode & { dottedName: DottedName }
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, { ParsedRules, serializeEvaluation } from 'publicodes'
|
||||
import { ParsedRules, serializeEvaluation } from 'publicodes'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
// import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { batchUpdateSituation, setActiveTarget } from '@/store/actions/actions'
|
||||
import { Situation } from '@/store/reducers/rootReducer'
|
||||
import { configObjectifsSelector } from '@/store/selectors/simulationSelectors'
|
||||
import {
|
||||
useAsyncParsedRules,
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
type ShortName = string
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
|
@ -18,11 +24,11 @@ export default function useSearchParamsSimulationSharing() {
|
|||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const objectifs = useSelector(configObjectifsSelector)
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
const parsedRules = useAsyncParsedRules()
|
||||
|
||||
const dottedNameParamName = useMemo(
|
||||
() => getRulesParamNames(engine.getParsedRules()),
|
||||
[engine]
|
||||
() => (parsedRules ? getRulesParamNames(parsedRules) : []),
|
||||
[parsedRules]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -64,13 +70,25 @@ export default function useSearchParamsSimulationSharing() {
|
|||
}
|
||||
|
||||
export const useParamsFromSituation = (situation: Situation) => {
|
||||
const engine = useEngine()
|
||||
const parsedRules = useAsyncParsedRules()
|
||||
const workerEngine = useWorkerEngine()
|
||||
const dottedNameParamName = useMemo(
|
||||
() => getRulesParamNames(engine.getParsedRules()),
|
||||
[engine]
|
||||
() => (parsedRules ? getRulesParamNames(parsedRules) : []),
|
||||
[parsedRules]
|
||||
)
|
||||
|
||||
return getSearchParamsFromSituation(engine, situation, dottedNameParamName)
|
||||
const ret = usePromiseOnSituationChange(
|
||||
() =>
|
||||
getSearchParamsFromSituation(
|
||||
workerEngine,
|
||||
situation,
|
||||
dottedNameParamName
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dottedNameParamName, workerEngine]
|
||||
)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
export const cleanSearchParams = (
|
||||
|
@ -99,29 +117,36 @@ export const getRulesParamNames = (
|
|||
ruleNode.rawNode['identifiant court'] || dottedName,
|
||||
])
|
||||
|
||||
export function getSearchParamsFromSituation(
|
||||
engine: Engine,
|
||||
export async function getSearchParamsFromSituation(
|
||||
workerEngine: WorkerEngine,
|
||||
situation: Situation,
|
||||
dottedNameParamName: [DottedName, ParamName][]
|
||||
): URLSearchParams {
|
||||
): Promise<URLSearchParams> {
|
||||
const searchParams = new URLSearchParams()
|
||||
const dottedNameParamNameMapping = Object.fromEntries(dottedNameParamName)
|
||||
|
||||
Object.entries(situation).forEach(([dottedName, value]) => {
|
||||
const paramName = dottedNameParamNameMapping[dottedName]
|
||||
try {
|
||||
const serializedValue = serializeEvaluation(engine.evaluate(value))
|
||||
const promises = Object.entries(situation).map(
|
||||
async ([dottedName, value]) => {
|
||||
const paramName = dottedNameParamNameMapping[dottedName]
|
||||
try {
|
||||
const serializedValue = serializeEvaluation(
|
||||
await workerEngine.asyncEvaluateWithEngineId(value)
|
||||
)
|
||||
|
||||
if (typeof serializedValue !== 'undefined') {
|
||||
searchParams.set(paramName, serializedValue)
|
||||
} else if (typeof value === 'object') {
|
||||
searchParams.set(paramName, JSON.stringify(value))
|
||||
if (typeof serializedValue !== 'undefined') {
|
||||
searchParams.set(paramName, serializedValue)
|
||||
} else if (typeof value === 'object') {
|
||||
searchParams.set(paramName, JSON.stringify(value))
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
// debugger
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
await Promise.all(promises)
|
||||
|
||||
searchParams.sort()
|
||||
|
||||
return searchParams
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
import rawRules, { DottedName } from 'modele-social'
|
||||
import Engine, { Rule } from 'publicodes'
|
||||
|
||||
import type { ProviderProps } from '@/components/Provider'
|
||||
|
||||
import i18n from './locales/i18n'
|
||||
import ruleTranslations from './locales/rules-en.yaml'
|
||||
import translateRules from './locales/translateRules'
|
||||
|
||||
type Rules = Record<DottedName, Rule>
|
||||
|
||||
const unitsTranslations = Object.entries(
|
||||
i18n.getResourceBundle('fr', 'units') as Record<string, string>
|
||||
)
|
||||
const engineOptions = {
|
||||
getUnitKey(unit: string): string {
|
||||
const key = unitsTranslations
|
||||
.find(([, trans]) => trans === unit)?.[0]
|
||||
.replace(/_plural$/, '')
|
||||
|
||||
return key || unit
|
||||
},
|
||||
}
|
||||
|
||||
let warnCount = 0
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
const logger = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
warn: (message: string) => {
|
||||
// console.warn(message)
|
||||
|
||||
warnCount++
|
||||
timeout !== null && clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('⚠️', warnCount, 'warnings in the engine')
|
||||
warnCount = 0
|
||||
}, 1000)
|
||||
},
|
||||
error: (message: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message)
|
||||
},
|
||||
log: (message: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message)
|
||||
},
|
||||
}
|
||||
|
||||
export function engineFactory(rules: Rules, options = {}) {
|
||||
return new Engine(rules, { ...engineOptions, ...options, logger })
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type Actions =
|
||||
| {
|
||||
action: 'init'
|
||||
params: [{ basename: ProviderProps['basename'] }]
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'setSituation'
|
||||
params: Parameters<Engine<DottedName>['setSituation']>
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'evaluate'
|
||||
params: Parameters<Engine<DottedName>['evaluate']>
|
||||
result: ReturnType<Engine<DottedName>['evaluate']>
|
||||
}
|
||||
| {
|
||||
action: 'getRule'
|
||||
params: Parameters<Engine<DottedName>['getRule']>
|
||||
result: ReturnType<Engine<DottedName>['getRule']>
|
||||
}
|
||||
| {
|
||||
action: 'getParsedRules'
|
||||
params: []
|
||||
result: ReturnType<Engine<DottedName>['getParsedRules']>
|
||||
}
|
||||
| {
|
||||
action: 'shallowCopy'
|
||||
params: []
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'deleteShallowCopy'
|
||||
params: [{ engineId: number }]
|
||||
result: void
|
||||
}
|
||||
|
||||
type GenericParams = {
|
||||
/**
|
||||
* The id of the engine to use, the default engine is 0
|
||||
*/
|
||||
engineId?: number
|
||||
|
||||
/**
|
||||
* The id of the message, used to identify the response
|
||||
*/
|
||||
id: number
|
||||
}
|
||||
|
||||
export type Action<T extends Actions['action']> = Extract<
|
||||
Actions,
|
||||
{ action: T }
|
||||
>
|
||||
|
||||
let engines: (Engine<DottedName> | undefined)[] = []
|
||||
|
||||
let setDefaultEngineReady: (() => void) | null = null
|
||||
const isDefaultEngineReady = new Promise(
|
||||
(resolve) => (setDefaultEngineReady = resolve as () => void)
|
||||
)
|
||||
|
||||
onmessage = async (e) => {
|
||||
console.log('[onmessage]', e.data)
|
||||
|
||||
const { engineId = 0, id, action, params } = e.data as Actions & GenericParams
|
||||
|
||||
try {
|
||||
if (action === 'init') {
|
||||
const [{ basename }] = params
|
||||
try {
|
||||
let rules = rawRules
|
||||
if (basename === 'infrance') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
rules = translateRules('en', ruleTranslations, rules)
|
||||
}
|
||||
|
||||
const engineId = engines.length
|
||||
engines.push(engineFactory(rules))
|
||||
console.log('[engine ready]', engines[engineId])
|
||||
postMessage({ engineId, id })
|
||||
setDefaultEngineReady?.()
|
||||
} catch (e) {
|
||||
console.error('[error]', e)
|
||||
// postMessage('error')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await isDefaultEngineReady
|
||||
|
||||
const engine = engines[engineId]
|
||||
if (!engine) {
|
||||
throw new Error('Engine does not exist')
|
||||
}
|
||||
|
||||
if (action === 'setSituation') {
|
||||
// safeSetSituation(
|
||||
// engine,
|
||||
// ({ situation, <faultyDottedName> }) => {
|
||||
// console.error('setSituation', { situation, faultyDottedName })
|
||||
// },
|
||||
// ...params
|
||||
// )
|
||||
engine.setSituation(...params)
|
||||
|
||||
return postMessage({ engineId, id })
|
||||
} else if (action === 'evaluate') {
|
||||
const result = engine.evaluate(...params)
|
||||
console.log('[result]', result)
|
||||
|
||||
return postMessage({ engineId, id, result })
|
||||
} else if (action === 'getRule') {
|
||||
const result = engine.getRule(...params)
|
||||
|
||||
return postMessage({ engineId, id, result })
|
||||
} else if (action === 'getParsedRules') {
|
||||
const result = engine.getParsedRules()
|
||||
|
||||
return postMessage({ engineId, id, result })
|
||||
} else if (action === 'shallowCopy') {
|
||||
const result = engine.shallowCopy()
|
||||
engines.push(result)
|
||||
|
||||
return postMessage({ engineId: engines.length - 1, id })
|
||||
} else if (action === 'deleteShallowCopy') {
|
||||
if (engineId === 0) {
|
||||
throw new Error('Cannot delete the default engine')
|
||||
}
|
||||
delete engines[engineId]
|
||||
|
||||
// false positive warning from eslint
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const lastIndex: number = engines.findLastIndex(
|
||||
(el) => el instanceof Engine
|
||||
)
|
||||
engines = lastIndex >= 0 ? engines.splice(0, lastIndex) : engines
|
||||
|
||||
console.log('[engines]', engines)
|
||||
|
||||
return postMessage({ engineId, id })
|
||||
} else {
|
||||
console.log('[Message inconu]', e.data)
|
||||
}
|
||||
} catch (error) {
|
||||
return postMessage({ engineId, id, error })
|
||||
}
|
||||
}
|
|
@ -7,11 +7,17 @@ import i18next from '../locales/i18n'
|
|||
|
||||
import '../api/sentry'
|
||||
|
||||
export const AppFr = () => (
|
||||
<I18nProvider locale="fr-FR">
|
||||
<App basename="mon-entreprise" />
|
||||
</I18nProvider>
|
||||
)
|
||||
import { useCreateWorkerEngine } from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
export const AppFr = () => {
|
||||
// useCreateWorkerEngine('mon-entreprise')
|
||||
|
||||
return (
|
||||
<I18nProvider locale="fr-FR">
|
||||
<App basename="mon-entreprise" />
|
||||
</I18nProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const AppFrWithProfiler = withProfiler(AppFr)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import Engine, { RuleNode } from 'publicodes'
|
||||
import { useMemo } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
|
@ -10,8 +10,13 @@ import {
|
|||
useMissingVariables,
|
||||
} from '@/store/selectors/simulationSelectors'
|
||||
import { ImmutableType } from '@/types/utils'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { useEngine } from '../components/utils/EngineContext'
|
||||
// import { useEngine } from '../components/utils/EngineContext'
|
||||
|
||||
type MissingVariables = Partial<Record<DottedName, number>>
|
||||
|
||||
|
@ -67,23 +72,30 @@ export function getNextQuestions(
|
|||
}
|
||||
|
||||
export const useNextQuestions = function (
|
||||
engines?: Array<Engine<DottedName>>
|
||||
workerEngines?: WorkerEngine[]
|
||||
): Array<DottedName> {
|
||||
const answeredQuestions = useSelector(answeredQuestionsSelector)
|
||||
const config = useSelector(configSelector)
|
||||
const engine = useEngine()
|
||||
const missingVariables = useMissingVariables({ engines: engines ?? [engine] })
|
||||
const nextQuestions = useMemo(() => {
|
||||
const next = getNextQuestions(
|
||||
missingVariables,
|
||||
config.questions ?? {},
|
||||
answeredQuestions
|
||||
)
|
||||
const workerEngine = useWorkerEngine()
|
||||
const missingVariables = useMissingVariables(workerEngines)
|
||||
|
||||
return next.filter(
|
||||
(question) => engine.getRule(question).rawNode.question !== undefined
|
||||
)
|
||||
}, [missingVariables, config, answeredQuestions, engine])
|
||||
const nextQuestions = usePromiseOnSituationChange(
|
||||
async () => {
|
||||
const next = getNextQuestions(
|
||||
missingVariables,
|
||||
config.questions ?? {},
|
||||
answeredQuestions
|
||||
)
|
||||
|
||||
const rules = await Promise.all(
|
||||
next.map((question) => workerEngine.asyncGetRuleWithEngineId(question))
|
||||
)
|
||||
|
||||
return next.filter((_, i) => rules[i].rawNode.question !== undefined)
|
||||
},
|
||||
[missingVariables, config.questions, answeredQuestions, workerEngine],
|
||||
{ defaultValue: [] as DottedName[] }
|
||||
)
|
||||
|
||||
return nextQuestions
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { DependencyList, useCallback, useEffect, useState } from 'react'
|
|||
*/
|
||||
export const usePromise = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
deps: DependencyList = [],
|
||||
deps: DependencyList,
|
||||
defaultValue?: Default
|
||||
) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -15,9 +15,35 @@ export const usePromise = <T, Default = undefined>(
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => void lazyPromise(), deps)
|
||||
|
||||
// cancelable promise
|
||||
// useEffect(() => {
|
||||
// const controller = new window.AbortController()
|
||||
// const signal = controller.signal
|
||||
|
||||
// void new Promise((resolve, reject) => {
|
||||
// void lazyPromise().then(resolve)
|
||||
// // .then(resolve)
|
||||
|
||||
// signal.addEventListener('abort', () => {
|
||||
// reject(new Error('Promise aborted'))
|
||||
// })
|
||||
// })
|
||||
|
||||
// return () => {
|
||||
// console.log('### aborting')
|
||||
// controller.abort()
|
||||
// // promise.
|
||||
// }
|
||||
// }, deps)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a typed tuple.
|
||||
*/
|
||||
const tuple = <T extends unknown[]>(args: [...T]): T => args
|
||||
|
||||
/**
|
||||
* Execute an asynchronous function and return its result (Return default value if the promise is not finished).
|
||||
* Use this hook if you want to fire the promise manually.
|
||||
|
@ -25,19 +51,16 @@ export const usePromise = <T, Default = undefined>(
|
|||
export const useLazyPromise = <
|
||||
T,
|
||||
Params extends unknown[],
|
||||
Default = undefined
|
||||
Default = undefined,
|
||||
>(
|
||||
promise: (...params: Params) => Promise<T>,
|
||||
deps: DependencyList = [],
|
||||
deps: DependencyList,
|
||||
defaultValue?: Default
|
||||
) => {
|
||||
// console.log('===>', defaultValue)
|
||||
const [state, setState] = useState<T | Default>(defaultValue as Default)
|
||||
|
||||
const lazyPromise = useCallback(
|
||||
async (...params: Params) => {
|
||||
// console.log('====', defaultValue)
|
||||
|
||||
const result = await promise(...params)
|
||||
setState(result)
|
||||
|
||||
|
@ -47,5 +70,5 @@ export const useLazyPromise = <
|
|||
deps
|
||||
)
|
||||
|
||||
return [state, lazyPromise] as const
|
||||
return tuple([state, lazyPromise])
|
||||
}
|
||||
|
|
|
@ -76,11 +76,11 @@ export default function Landing() {
|
|||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<SimulateurCard {...simulators.salarié} />
|
||||
{/* <SimulateurCard {...simulators.salarié} /> */}
|
||||
|
||||
<SimulateurCard {...simulators['auto-entrepreneur']} />
|
||||
|
||||
<SimulateurCard {...simulators['comparaison-statuts']} />
|
||||
{/* <SimulateurCard {...simulators['comparaison-statuts']} /> */}
|
||||
|
||||
<Grid
|
||||
item
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
import { CompanyDetails } from '@/components/company/Details'
|
||||
import { CompanySearchField } from '@/components/company/SearchField'
|
||||
import { ForceThemeProvider } from '@/components/utils/DarkModeContext'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import AnswerGroup from '@/design-system/answer-group'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
|
@ -22,6 +21,7 @@ import { useSetEntreprise } from '@/hooks/useSetEntreprise'
|
|||
import { useSitePaths } from '@/sitePaths'
|
||||
import { getCookieValue } from '@/storage/readCookie'
|
||||
import { resetCompany } from '@/store/actions/companyActions'
|
||||
import { usePromiseOnSituationChange } from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
// import { RootState } from '@/store/reducers/rootReducer'
|
||||
|
||||
|
@ -30,7 +30,10 @@ export default function SearchOrCreate() {
|
|||
// const statutChoisi = useSelector(
|
||||
// (state: RootState) => state.choixStatutJuridique.companyStatusChoice
|
||||
// )
|
||||
const companySIREN = useEngine().evaluate('entreprise . SIREN').nodeValue
|
||||
const companySIREN = usePromiseOnSituationChange(
|
||||
() => asyncEvaluate('entreprise . SIREN'),
|
||||
[]
|
||||
)?.nodeValue
|
||||
useSetEntrepriseFromUrssafConnection()
|
||||
const handleCompanySubmit = useHandleCompanySubmit()
|
||||
const dispatch = useDispatch()
|
||||
|
@ -166,7 +169,11 @@ function useHandleCompanySubmit() {
|
|||
function useSetEntrepriseFromUrssafConnection() {
|
||||
const setEntreprise = useSetEntreprise()
|
||||
const siret = siretFromUrssafFrConnection()
|
||||
const companySIREN = useEngine().evaluate('entreprise . SIREN').nodeValue
|
||||
const companySIREN = usePromiseOnSituationChange(
|
||||
() => asyncEvaluate('entreprise . SIREN'),
|
||||
[]
|
||||
)?.nodeValue
|
||||
|
||||
useEffect(() => {
|
||||
if (siret && !companySIREN) {
|
||||
searchDenominationOrSiren(siret)
|
||||
|
|
|
@ -8,7 +8,7 @@ import { styled } from 'styled-components'
|
|||
import { ExplicableRule } from '@/components/conversation/Explicable'
|
||||
import RuleInput from '@/components/conversation/RuleInput'
|
||||
import { FadeIn } from '@/components/ui/animate'
|
||||
import { EngineContext } from '@/components/utils/EngineContext'
|
||||
// import { EngineContext } from '@/components/utils/EngineContext'
|
||||
import { Markdown } from '@/components/utils/markdown'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { H3 } from '@/design-system/typography/heading'
|
||||
|
@ -19,7 +19,7 @@ import {
|
|||
situationSelector,
|
||||
targetUnitSelector,
|
||||
} from '@/store/selectors/simulationSelectors'
|
||||
import { evaluateQuestion, getMeta } from '@/utils'
|
||||
import { getMeta } from '@/utils'
|
||||
|
||||
type SubSectionProp = {
|
||||
dottedName: DottedName
|
||||
|
@ -96,7 +96,7 @@ export function SimpleField(props: SimpleFieldProps) {
|
|||
)
|
||||
|
||||
let displayedQuestion =
|
||||
question ?? evaluateQuestion(engine, engine.getRule(dottedName))
|
||||
'question ?? evaluateQuestion(engine, engine.getRule(dottedName))'
|
||||
|
||||
const labelId = useSSRSafeId()
|
||||
const targetUnit = useSelector(targetUnitSelector)
|
||||
|
|
|
@ -4,7 +4,7 @@ import { styled } from 'styled-components'
|
|||
|
||||
import Value, { Condition } from '@/components/EngineValue'
|
||||
import { FromTop } from '@/components/ui/animate'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
// import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Markdown } from '@/components/utils/markdown'
|
||||
import { Article } from '@/design-system/card'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, { PublicodesExpression } from 'publicodes'
|
||||
import { Fragment, lazy, Suspense, useCallback, useContext } from 'react'
|
||||
import { Fragment, lazy, Suspense, useCallback } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
import { TrackPage } from '@/components/ATInternetTracking'
|
||||
import RuleInput from '@/components/conversation/RuleInput'
|
||||
import { WhenApplicable, WhenNotApplicable } from '@/components/EngineValue'
|
||||
import BrowserOnly from '@/components/utils/BrowserOnly'
|
||||
import { EngineContext, EngineProvider } from '@/components/utils/EngineContext'
|
||||
import { Markdown } from '@/components/utils/markdown'
|
||||
import { usePersistingState } from '@/components/utils/persistState'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
|
@ -17,13 +16,7 @@ import PopoverConfirm from '@/design-system/popover/PopoverConfirm'
|
|||
import { headings } from '@/design-system/typography'
|
||||
import { Intro, SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import useSimulationConfig from '@/hooks/useSimulationConfig'
|
||||
import {
|
||||
buildSituationFromObject,
|
||||
evaluateQuestion,
|
||||
getMeta,
|
||||
hash,
|
||||
omit,
|
||||
} from '@/utils'
|
||||
import { buildSituationFromObject, getMeta, hash, omit } from '@/utils'
|
||||
|
||||
import formulaire from './demande-mobilité.yaml'
|
||||
|
||||
|
@ -45,9 +38,9 @@ export default function PageMobilité() {
|
|||
certificat A1 afin d'être couverts pendant la période de travail à
|
||||
l'étranger.
|
||||
</Intro>
|
||||
<EngineProvider value={engine}>
|
||||
{/* <EngineProvider value={engine}>
|
||||
<FormulairePublicodes />
|
||||
</EngineProvider>
|
||||
</EngineProvider> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -74,7 +67,7 @@ const useFields = (
|
|||
|
||||
const VERSION = hash(JSON.stringify(formulaire))
|
||||
function FormulairePublicodes() {
|
||||
const engine = useContext(EngineContext)
|
||||
const engine = useEngine()
|
||||
const [situation, setSituation] = usePersistingState<
|
||||
Record<string, PublicodesExpression>
|
||||
>(`formulaire-détachement:${VERSION}`, {})
|
||||
|
@ -181,12 +174,12 @@ function FormulairePublicodes() {
|
|||
}}
|
||||
>
|
||||
{' '}
|
||||
<Markdown>
|
||||
{/* <Markdown>
|
||||
{evaluateQuestion(
|
||||
engine,
|
||||
engine.getRule(dottedName)
|
||||
) ?? ''}
|
||||
</Markdown>
|
||||
</Markdown> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -194,10 +187,10 @@ function FormulairePublicodes() {
|
|||
id={dottedName.replace(/\s|\./g, '_')}
|
||||
dottedName={dottedName as DottedName}
|
||||
onChange={(value) => onChange(dottedName, value)}
|
||||
aria-label={
|
||||
question &&
|
||||
evaluateQuestion(engine, engine.getRule(dottedName))
|
||||
}
|
||||
// aria-label={
|
||||
// question &&
|
||||
// evaluateQuestion(engine, engine.getRule(dottedName))
|
||||
// }
|
||||
/>
|
||||
{question && type === undefined && description && (
|
||||
<Markdown>{description}</Markdown>
|
||||
|
|
|
@ -1,37 +1,43 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Article } from '@/design-system/card'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
export function AnnuaireEntreprises() {
|
||||
const { t } = useTranslation()
|
||||
const engine = useEngine()
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const siren = engine.evaluate('entreprise . SIREN').nodeValue as string
|
||||
const siren = usePromiseOnSituationChange(
|
||||
async () =>
|
||||
await workerEngine.asyncEvaluateWithEngineId('entreprise . SIREN'),
|
||||
[workerEngine],
|
||||
{ defaultValue: null }
|
||||
)?.nodeValue as string | null
|
||||
|
||||
return (
|
||||
<>
|
||||
<Article
|
||||
title={t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.title',
|
||||
'Voir vos données publiques'
|
||||
)}
|
||||
href={`https://annuaire-entreprises.data.gouv.fr/entreprise/${siren}?mtm_campaign=mon-entreprise`}
|
||||
ctaLabel={t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.cta',
|
||||
'Visiter le site'
|
||||
)}
|
||||
aria-label={t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.aria-label',
|
||||
'Annuaire-entreprise, Visiter le site'
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.body',
|
||||
'Retrouvez toutes les informations publiques concernant votre entreprise sur'
|
||||
)}{' '}
|
||||
Annuaire des Entreprises.
|
||||
</Article>
|
||||
</>
|
||||
)
|
||||
return typeof siren === 'string' ? (
|
||||
<Article
|
||||
title={t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.title',
|
||||
'Voir vos données publiques'
|
||||
)}
|
||||
href={`https://annuaire-entreprises.data.gouv.fr/entreprise/${siren}?mtm_campaign=mon-entreprise`}
|
||||
ctaLabel={t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.cta',
|
||||
'Visiter le site'
|
||||
)}
|
||||
aria-label={t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.aria-label',
|
||||
'Annuaire-entreprise, Visiter le site'
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
'assistants.pour-mon-entreprise.annuaire-entreprises.body',
|
||||
'Retrouvez toutes les informations publiques concernant votre entreprise sur'
|
||||
)}{' '}
|
||||
Annuaire des Entreprises.
|
||||
</Article>
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export function CodeDuTravailNumeriqueCard() {
|
|||
Pour toutes vos questions en droit du travail, rendez-vous sur le site
|
||||
Code du travail numérique
|
||||
</Trans>
|
||||
<Spacing md />
|
||||
<Spacing md as="span" style={{ display: 'block' }} />
|
||||
<CodeDuTravailNumeriqueLogo />
|
||||
</Article>
|
||||
)
|
||||
|
|
|
@ -29,7 +29,6 @@ import { SimulateurCard } from '@/components/SimulateurCard'
|
|||
import { FromTop } from '@/components/ui/animate'
|
||||
import { ForceThemeProvider } from '@/components/utils/DarkModeContext'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Markdown } from '@/components/utils/markdown'
|
||||
import { Message, Popover } from '@/design-system'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { Container, Grid, Spacing } from '@/design-system/layout'
|
||||
|
@ -44,7 +43,6 @@ import { useSitePaths } from '@/sitePaths'
|
|||
import { resetCompany } from '@/store/actions/companyActions'
|
||||
import { SimulationConfig } from '@/store/reducers/rootReducer'
|
||||
import { companySituationSelector } from '@/store/selectors/simulationSelectors'
|
||||
import { evaluateQuestion } from '@/utils'
|
||||
|
||||
import forms from './forms.svg'
|
||||
import growth from './growth.svg'
|
||||
|
@ -294,9 +292,9 @@ const AskCompanyMissingDetails = () => {
|
|||
{questions.map((question) => (
|
||||
<FromTop key={question.dottedName}>
|
||||
<H3>
|
||||
<Markdown options={{ forceInline: true }}>
|
||||
{/* <Markdown options={{ forceInline: true }}>
|
||||
{evaluateQuestion(engine, question) ?? ''}
|
||||
</Markdown>
|
||||
</Markdown> */}
|
||||
</H3>
|
||||
<RuleInput
|
||||
dottedName={question.dottedName}
|
||||
|
|
|
@ -102,7 +102,7 @@ export default function SearchCodeAPE({
|
|||
[]
|
||||
)
|
||||
|
||||
const lazyData = usePromise(() => import('@/public/data/ape-search.json'))
|
||||
const lazyData = usePromise(() => import('@/public/data/ape-search.json'), [])
|
||||
|
||||
const lastIdxs = useRef<Record<string, UFuzzy.HaystackIdxs>>({})
|
||||
const prevValue = useRef<string>(searchQuery)
|
||||
|
|
|
@ -1,35 +1,36 @@
|
|||
import { ImmutableType } from '@/types/utils'
|
||||
import type { ImmutableType } from '@/types/utils'
|
||||
|
||||
import { choixStatutJuridiqueConfig } from '../assistants/choix-du-statut/config'
|
||||
import { déclarationChargesSocialesIndépendantConfig } from '../assistants/declaration-charges-sociales-independant/config'
|
||||
import { demandeMobilitéConfig } from '../assistants/demande-mobilité/config'
|
||||
import { économieCollaborativeConfig } from '../assistants/économie-collaborative/config'
|
||||
import { pourMonEntrepriseConfig } from '../assistants/pour-mon-entreprise/config'
|
||||
import { rechercheCodeApeConfig } from '../assistants/recherche-code-ape/config'
|
||||
// import { choixStatutJuridiqueConfig } from '../assistants/choix-du-statut/config'
|
||||
// import { déclarationChargesSocialesIndépendantConfig } from '../assistants/declaration-charges-sociales-independant/config'
|
||||
// import { demandeMobilitéConfig } from '../assistants/demande-mobilité/config'
|
||||
// import { économieCollaborativeConfig } from '../assistants/économie-collaborative/config'
|
||||
// import { pourMonEntrepriseConfig } from '../assistants/pour-mon-entreprise/config'
|
||||
// import { rechercheCodeApeConfig } from '../assistants/recherche-code-ape/config'
|
||||
import { PageConfig, SimulatorsDataParams } from '../simulateurs/_configs/types'
|
||||
import { artisteAuteurConfig } from '../simulateurs/artiste-auteur/config'
|
||||
// import { artisteAuteurConfig } from '../simulateurs/artiste-auteur/config'
|
||||
import { autoEntrepreneurConfig } from '../simulateurs/auto-entrepreneur/config'
|
||||
import { auxiliaireMédicalConfig } from '../simulateurs/auxiliaire-médical/config'
|
||||
import { avocatConfig } from '../simulateurs/avocat/config'
|
||||
import { chirurgienDentisteConfig } from '../simulateurs/chirurgien-dentiste/config'
|
||||
import { chômagePartielConfig } from '../simulateurs/chômage-partiel/config'
|
||||
import { cipavConfig } from '../simulateurs/cipav/config'
|
||||
import { comparaisonStatutsConfig } from '../simulateurs/comparaison-statuts/config'
|
||||
import { coûtCréationEntrepriseConfig } from '../simulateurs/cout-creation-entreprise/config.js'
|
||||
import { dividendesConfig } from '../simulateurs/dividendes/config'
|
||||
import { eirlConfig } from '../simulateurs/eirl/config'
|
||||
import { entrepriseIndividuelleConfig } from '../simulateurs/entreprise-individuelle/config'
|
||||
import { eurlConfig } from '../simulateurs/eurl/config'
|
||||
import { expertComptableConfig } from '../simulateurs/expert-comptable/config'
|
||||
import { impôtSociétéConfig } from '../simulateurs/impot-societe/config'
|
||||
import { indépendantConfig } from '../simulateurs/indépendant/config'
|
||||
import { médecinConfig } from '../simulateurs/médecin/config'
|
||||
import { pamcConfig } from '../simulateurs/pamc/config'
|
||||
import { pharmacienConfig } from '../simulateurs/pharmacien/config'
|
||||
import { professionLibéraleConfig } from '../simulateurs/profession-libérale/config'
|
||||
import { sageFemmeConfig } from '../simulateurs/sage-femme/config'
|
||||
import { salariéConfig } from '../simulateurs/salarié/config'
|
||||
import { sasuConfig } from '../simulateurs/sasu/config'
|
||||
|
||||
// import { auxiliaireMédicalConfig } from '../simulateurs/auxiliaire-médical/config'
|
||||
// import { avocatConfig } from '../simulateurs/avocat/config'
|
||||
// import { chirurgienDentisteConfig } from '../simulateurs/chirurgien-dentiste/config'
|
||||
// import { chômagePartielConfig } from '../simulateurs/chômage-partiel/config'
|
||||
// import { cipavConfig } from '../simulateurs/cipav/config'
|
||||
// import { comparaisonStatutsConfig } from '../simulateurs/comparaison-statuts/config'
|
||||
// import { coûtCréationEntrepriseConfig } from '../simulateurs/cout-creation-entreprise/config.js'
|
||||
// import { dividendesConfig } from '../simulateurs/dividendes/config'
|
||||
// import { eirlConfig } from '../simulateurs/eirl/config'
|
||||
// import { entrepriseIndividuelleConfig } from '../simulateurs/entreprise-individuelle/config'
|
||||
// import { eurlConfig } from '../simulateurs/eurl/config'
|
||||
// import { expertComptableConfig } from '../simulateurs/expert-comptable/config'
|
||||
// import { impôtSociétéConfig } from '../simulateurs/impot-societe/config'
|
||||
// import { indépendantConfig } from '../simulateurs/indépendant/config'
|
||||
// import { médecinConfig } from '../simulateurs/médecin/config'
|
||||
// import { pamcConfig } from '../simulateurs/pamc/config'
|
||||
// import { pharmacienConfig } from '../simulateurs/pharmacien/config'
|
||||
// import { professionLibéraleConfig } from '../simulateurs/profession-libérale/config'
|
||||
// import { sageFemmeConfig } from '../simulateurs/sage-femme/config'
|
||||
// import { salariéConfig } from '../simulateurs/salarié/config'
|
||||
// import { sasuConfig } from '../simulateurs/sasu/config'
|
||||
|
||||
/**
|
||||
* Contient l'intégralité des données concernant les différents simulateurs et assistants
|
||||
|
@ -39,37 +40,37 @@ import { sasuConfig } from '../simulateurs/sasu/config'
|
|||
const getMetadataSrc = (params: SimulatorsDataParams) => {
|
||||
const data = {
|
||||
// simulateurs:
|
||||
...salariéConfig(params),
|
||||
...entrepriseIndividuelleConfig(params),
|
||||
...eirlConfig(params),
|
||||
...sasuConfig(params),
|
||||
...eurlConfig(params),
|
||||
// ...salariéConfig(params),
|
||||
// ...entrepriseIndividuelleConfig(params),
|
||||
// ...eirlConfig(params),
|
||||
// ...sasuConfig(params),
|
||||
// ...eurlConfig(params),
|
||||
...autoEntrepreneurConfig(params),
|
||||
...indépendantConfig(params),
|
||||
...artisteAuteurConfig(params),
|
||||
...chômagePartielConfig(params),
|
||||
...comparaisonStatutsConfig(params),
|
||||
...économieCollaborativeConfig(params),
|
||||
...pharmacienConfig(params),
|
||||
...médecinConfig(params),
|
||||
...chirurgienDentisteConfig(params),
|
||||
...sageFemmeConfig(params),
|
||||
...auxiliaireMédicalConfig(params),
|
||||
...avocatConfig(params),
|
||||
...expertComptableConfig(params),
|
||||
...professionLibéraleConfig(params),
|
||||
...pamcConfig(params),
|
||||
...dividendesConfig(params),
|
||||
...coûtCréationEntrepriseConfig(params),
|
||||
...impôtSociétéConfig(params),
|
||||
...cipavConfig(params),
|
||||
// ...indépendantConfig(params),
|
||||
// ...artisteAuteurConfig(params),
|
||||
// ...chômagePartielConfig(params),
|
||||
// ...comparaisonStatutsConfig(params),
|
||||
// ...économieCollaborativeConfig(params),
|
||||
// ...pharmacienConfig(params),
|
||||
// ...médecinConfig(params),
|
||||
// ...chirurgienDentisteConfig(params),
|
||||
// ...sageFemmeConfig(params),
|
||||
// ...auxiliaireMédicalConfig(params),
|
||||
// ...avocatConfig(params),
|
||||
// ...expertComptableConfig(params),
|
||||
// ...professionLibéraleConfig(params),
|
||||
// ...pamcConfig(params),
|
||||
// ...dividendesConfig(params),
|
||||
// ...coûtCréationEntrepriseConfig(params),
|
||||
// ...impôtSociétéConfig(params),
|
||||
// ...cipavConfig(params),
|
||||
|
||||
// assistants:
|
||||
...choixStatutJuridiqueConfig(params),
|
||||
...déclarationChargesSocialesIndépendantConfig(params),
|
||||
...demandeMobilitéConfig(params),
|
||||
...pourMonEntrepriseConfig(params),
|
||||
...rechercheCodeApeConfig(params),
|
||||
// // assistants:
|
||||
// ...choixStatutJuridiqueConfig(params),
|
||||
// ...déclarationChargesSocialesIndépendantConfig(params),
|
||||
// ...demandeMobilitéConfig(params),
|
||||
// ...pourMonEntrepriseConfig(params),
|
||||
// ...rechercheCodeApeConfig(params),
|
||||
} as const
|
||||
|
||||
return data satisfies ImmutableType<Record<string, PageConfig>>
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
import { Condition, WhenAlreadyDefined } from '@/components/EngineValue'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
// import { Article } from '@/design-system/card'
|
||||
// import { Emoji } from '@/design-system/emoji'
|
||||
import { Grid, Spacing } from '@/design-system/layout'
|
||||
import { H2 } from '@/design-system/typography/heading'
|
||||
import {
|
||||
|
@ -12,12 +9,16 @@ import {
|
|||
} from '@/hooks/useCurrentSimulatorData'
|
||||
import { GuideURSSAFCard } from '@/pages/simulateurs/cards/GuideURSSAFCard'
|
||||
import { IframeIntegrationCard } from '@/pages/simulateurs/cards/IframeIntegrationCard'
|
||||
import { SimulatorRessourceCard } from '@/pages/simulateurs/cards/SimulatorRessourceCard'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
import {
|
||||
usePromiseOnSituationChange,
|
||||
useWorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
import { AnnuaireEntreprises } from '../assistants/pour-mon-entreprise/AnnuaireEntreprises'
|
||||
import { AutoEntrepreneurCard } from '../assistants/pour-mon-entreprise/AutoEntrepeneurCard'
|
||||
import { CodeDuTravailNumeriqueCard } from '../assistants/pour-mon-entreprise/CodeDuTravailNumeriqueCard'
|
||||
import { SimulatorRessourceCard } from './cards/SimulatorRessourceCard'
|
||||
|
||||
interface NextStepsProps {
|
||||
iframePath?: MergedSimulatorDataValues['iframePath']
|
||||
|
@ -27,11 +28,19 @@ interface NextStepsProps {
|
|||
export function NextSteps({ iframePath, nextSteps }: NextStepsProps) {
|
||||
const { absoluteSitePaths } = useSitePaths()
|
||||
const { language } = useTranslation().i18n
|
||||
const engine = useEngine()
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
const { key } = useCurrentSimulatorData()
|
||||
const guideUrssaf = guidesUrssaf.find(
|
||||
({ associatedRule }) => engine.evaluate(associatedRule).nodeValue
|
||||
const guideUrssaf = usePromiseOnSituationChange(
|
||||
async () =>
|
||||
(
|
||||
await Promise.all(
|
||||
guidesUrssaf.map(({ associatedRule }) =>
|
||||
workerEngine.asyncEvaluateWithEngineId(associatedRule)
|
||||
)
|
||||
)
|
||||
).find(({ nodeValue }) => nodeValue),
|
||||
[workerEngine]
|
||||
)
|
||||
|
||||
if (!iframePath && !guideUrssaf) {
|
||||
|
@ -58,7 +67,7 @@ export function NextSteps({ iframePath, nextSteps }: NextStepsProps) {
|
|||
{nextSteps &&
|
||||
nextSteps.map((simulatorId) => (
|
||||
<Grid item xs={12} sm={6} lg={4} key={simulatorId} role="listitem">
|
||||
<SimulatorRessourceCard simulatorId={simulatorId} />
|
||||
{/* <SimulatorRessourceCard simulatorId={simulatorId} /> */}
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
|
@ -70,14 +79,14 @@ export function NextSteps({ iframePath, nextSteps }: NextStepsProps) {
|
|||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{key === 'salarié' && (
|
||||
{/* {key === 'salarié' && (
|
||||
<Grid item xs={12} sm={6} lg={4} role="listitem">
|
||||
<CodeDuTravailNumeriqueCard />
|
||||
</Grid>
|
||||
)}
|
||||
)} */}
|
||||
{guideUrssaf && language === 'fr' && (
|
||||
<Grid item xs={12} sm={6} lg={4} role="listitem">
|
||||
<GuideURSSAFCard guideUrssaf={guideUrssaf} />
|
||||
{/* <GuideURSSAFCard guideUrssaf={guideUrssaf} /> */}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type { TFunction } from 'i18next'
|
||||
import { DottedName } from 'modele-social'
|
||||
import { ASTNode, PublicodesExpression } from 'publicodes'
|
||||
import type { DottedName } from 'modele-social'
|
||||
import type { ASTNode, PublicodesExpression } from 'publicodes'
|
||||
|
||||
import { AbsoluteSitePaths } from '@/sitePaths'
|
||||
import type { AbsoluteSitePaths } from '@/sitePaths'
|
||||
|
||||
export type Situation = Partial<
|
||||
Record<DottedName, PublicodesExpression | ASTNode>
|
||||
|
|
|
@ -19,8 +19,11 @@ import { H2 } from '@/design-system/typography/heading'
|
|||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
export default function AutoEntrepreneur() {
|
||||
console.log('ok')
|
||||
|
||||
return (
|
||||
<>
|
||||
aaaaaaaaaaaaaaa
|
||||
<Simulation
|
||||
explanations={<Explanation />}
|
||||
afterQuestionsSlot={<SelectSimulationYear />}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, { utils } from 'publicodes'
|
||||
import { utils } from 'publicodes'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { createSelector } from 'reselect'
|
||||
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
// import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { RootState, Situation } from '@/store/reducers/rootReducer'
|
||||
import {
|
||||
useWorkerEngine,
|
||||
WorkerEngine,
|
||||
} from '@/worker/socialWorkerEngineClient'
|
||||
|
||||
export const configSelector = (state: RootState) =>
|
||||
state.simulation?.config ?? {}
|
||||
|
@ -22,22 +27,34 @@ export const configObjectifsSelector = createSelector(
|
|||
|
||||
const emptySituation: Situation = {}
|
||||
|
||||
export const useMissingVariables = ({
|
||||
engines,
|
||||
}: {
|
||||
engines: Array<Engine<DottedName>>
|
||||
}): Partial<Record<DottedName, number>> => {
|
||||
export const useMissingVariables = (
|
||||
workerEngines?: WorkerEngine[]
|
||||
): Partial<Record<DottedName, number>> => {
|
||||
const objectifs = useSelector(configObjectifsSelector)
|
||||
const workerEngine = useWorkerEngine()
|
||||
|
||||
return treatAPIMissingVariables(
|
||||
objectifs
|
||||
.flatMap((objectif) =>
|
||||
engines.map((e) => e.evaluate(objectif).missingVariables ?? {})
|
||||
return usePromise(
|
||||
async () => {
|
||||
const evaluates = await Promise.all(
|
||||
objectifs.flatMap((objectif) =>
|
||||
(workerEngines ?? [workerEngine]).map(
|
||||
async (e) =>
|
||||
(await e.asyncEvaluateWithEngineId(objectif)).missingVariables ??
|
||||
{}
|
||||
)
|
||||
)
|
||||
)
|
||||
.reduce(mergeMissing, {}),
|
||||
useEngine()
|
||||
|
||||
return await treatAPIMissingVariables(
|
||||
evaluates.reduce(mergeMissing, {}),
|
||||
workerEngine
|
||||
)
|
||||
},
|
||||
[objectifs, workerEngine, workerEngines],
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
export const situationSelector = (state: RootState) =>
|
||||
state.simulation?.situation ?? emptySituation
|
||||
|
||||
|
@ -75,14 +92,26 @@ export const shouldFocusFieldSelector = (state: RootState) =>
|
|||
*
|
||||
* For instance, the commune field (API) will fill `commune . nom` `commune . taux versement transport`, `commune . département`, etc.
|
||||
*/
|
||||
function treatAPIMissingVariables<Name extends string>(
|
||||
missingVariables: Partial<Record<Name, number>>,
|
||||
engine: Engine<Name>
|
||||
): Partial<Record<Name, number>> {
|
||||
return (Object.entries(missingVariables) as Array<[Name, number]>).reduce(
|
||||
(missings, [name, value]: [Name, number]) => {
|
||||
const parentName = utils.ruleParent(name) as Name
|
||||
if (parentName && engine.getRule(parentName).rawNode.API) {
|
||||
async function treatAPIMissingVariables(
|
||||
missingVariables: Partial<Record<DottedName, number>>,
|
||||
workerEngine: WorkerEngine
|
||||
): Promise<Partial<Record<DottedName, number>>> {
|
||||
return (
|
||||
await Promise.all(
|
||||
(Object.entries(missingVariables) as [DottedName, number][]).map(
|
||||
async ([name, value]) => {
|
||||
const parentName = utils.ruleParent(name) as DottedName
|
||||
const rule =
|
||||
parentName &&
|
||||
(await workerEngine.asyncGetRuleWithEngineId(parentName))
|
||||
|
||||
return [name, value, parentName, rule.rawNode.API] as const
|
||||
}
|
||||
)
|
||||
)
|
||||
).reduce(
|
||||
(missings, [name, value, parentName, API]) => {
|
||||
if (API) {
|
||||
missings[parentName] = (missings[parentName] ?? 0) + value
|
||||
|
||||
return missings
|
||||
|
@ -91,9 +120,10 @@ function treatAPIMissingVariables<Name extends string>(
|
|||
|
||||
return missings
|
||||
},
|
||||
{} as Partial<Record<Name, number>>
|
||||
{} as Partial<Record<DottedName, number>>
|
||||
)
|
||||
}
|
||||
|
||||
const mergeMissing = (
|
||||
left: Record<string, number> | undefined = {},
|
||||
right: Record<string, number> | undefined = {}
|
||||
|
|
|
@ -13,6 +13,13 @@ type ImmutableIndex<T> = Readonly<{
|
|||
[K in keyof T]: ImmutableType<T[K]>
|
||||
}>
|
||||
|
||||
/**
|
||||
* Mutable type
|
||||
*/
|
||||
export type Mutable<T> = {
|
||||
-readonly [K in keyof T]: Mutable<T[K]>
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge union of object
|
||||
*
|
||||
|
@ -44,3 +51,11 @@ export type ToOptional<T> = Partial<Pick<T, UndefinedProperties<T>>> &
|
|||
type UndefinedProperties<T> = {
|
||||
[P in keyof T]-?: undefined extends T[P] ? P : never
|
||||
}[keyof T]
|
||||
|
||||
/**
|
||||
* Replace the return type of a function
|
||||
*/
|
||||
export type ReplaceReturnType<
|
||||
T extends (...a: never) => unknown,
|
||||
TNewReturn,
|
||||
> = (...a: Parameters<T>) => TNewReturn
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine, {
|
||||
import {
|
||||
formatValue,
|
||||
isPublicodesError,
|
||||
PublicodesExpression,
|
||||
|
@ -234,21 +234,6 @@ export async function getIframeOffset(): Promise<number> {
|
|||
})
|
||||
}
|
||||
|
||||
export function evaluateQuestion(
|
||||
engine: Engine,
|
||||
rule: RuleNode
|
||||
): string | undefined {
|
||||
const question = rule.rawNode.question as Exclude<
|
||||
number,
|
||||
PublicodesExpression
|
||||
>
|
||||
if (question && typeof question === 'object') {
|
||||
return engine.evaluate(question as PublicodesExpression).nodeValue as string
|
||||
}
|
||||
|
||||
return question
|
||||
}
|
||||
|
||||
export function buildSituationFromObject<Names extends string = DottedName>(
|
||||
contextDottedName: Names,
|
||||
situationObject: Record<string, PublicodesExpression>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import rawRules, { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
|
||||
import type { ProviderProps } from '@/components/Provider'
|
||||
import i18n from '@/locales/i18n'
|
||||
import ruleTranslations from '@/locales/rules-en.yaml'
|
||||
import translateRules from '@/locales/translateRules'
|
||||
|
||||
import { createWorkerEngine, WorkerEngineActions } from './workerEngine'
|
||||
|
||||
function getUnitKey(unit: string): string {
|
||||
const units = i18n.getResourceBundle('fr', 'units') as Record<string, string>
|
||||
const key = Object.entries(units)
|
||||
.find(([, trans]) => trans === unit)?.[0]
|
||||
.replace(/_plural$/, '')
|
||||
|
||||
return key || unit
|
||||
}
|
||||
|
||||
let warnCount = 0
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
const logger = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
warn: (message: string) => {
|
||||
// console.warn(message)
|
||||
|
||||
warnCount++
|
||||
timeout !== null && clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('⚠️', warnCount, 'warnings in the engine')
|
||||
warnCount = 0
|
||||
}, 1000)
|
||||
},
|
||||
error: (message: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message)
|
||||
},
|
||||
log: (message: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message)
|
||||
},
|
||||
}
|
||||
|
||||
const init = ({ basename }: Pick<ProviderProps, 'basename'>) => {
|
||||
let rules = rawRules
|
||||
if (basename === 'infrance') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
rules = translateRules('en', ruleTranslations, rules)
|
||||
}
|
||||
|
||||
const engine = new Engine(rules, { getUnitKey, logger })
|
||||
|
||||
return engine
|
||||
}
|
||||
|
||||
export type Actions = WorkerEngineActions<Parameters<typeof init>, DottedName>
|
||||
|
||||
console.time('[createWorkerEngine]')
|
||||
createWorkerEngine(init)
|
||||
console.timeEnd('[createWorkerEngine]')
|
|
@ -0,0 +1,329 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import {
|
||||
createContext,
|
||||
DependencyList,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react'
|
||||
|
||||
import { ProviderProps } from '@/components/Provider'
|
||||
import { useSetupSafeSituation } from '@/components/utils/EngineContext'
|
||||
import { useLazyPromise, usePromise } from '@/hooks/usePromise'
|
||||
|
||||
import { Actions } from './socialWorkerEngine.worker'
|
||||
import SocialeWorkerEngine from './socialWorkerEngine.worker?worker'
|
||||
import {
|
||||
createWorkerEngineClient,
|
||||
WorkerEngineClient,
|
||||
} from './workerEngineClient'
|
||||
|
||||
export type WorkerEngine = NonNullable<ReturnType<typeof useCreateWorkerEngine>>
|
||||
|
||||
// @ts-expect-error
|
||||
const WorkerEngineContext = createContext<WorkerEngine>()
|
||||
|
||||
// export const useWorkerEngineContext = () => {
|
||||
// const context = useContext(WorkerEngineContext)
|
||||
// if (!context) {
|
||||
// throw new Error(
|
||||
// 'You are trying to use the worker engine outside of its provider'
|
||||
// )
|
||||
// }
|
||||
|
||||
// return context
|
||||
// }
|
||||
|
||||
export const useWorkerEngine = () => {
|
||||
const context = useContext(WorkerEngineContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'You are trying to use the worker engine outside of its provider'
|
||||
)
|
||||
}
|
||||
|
||||
// if (!context) {
|
||||
// throw new Error(
|
||||
// 'You are trying to use the worker engine before it is ready'
|
||||
// )
|
||||
// }
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export const WorkerEngineProvider = ({
|
||||
children,
|
||||
basename,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
basename: ProviderProps['basename']
|
||||
}) => {
|
||||
const workerEngine = useCreateWorkerEngine(basename)
|
||||
|
||||
useSetupSafeSituation(workerEngine)
|
||||
|
||||
if (workerEngine === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkerEngineContext.Provider value={workerEngine}>
|
||||
{children}
|
||||
</WorkerEngineContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// export type WorkerEngine = WorkerEngineClient<Actions>
|
||||
// let workerClient: | null = null
|
||||
// setTimeout(() => {
|
||||
// const preparedWorker = new SocialeWorkerEngine()
|
||||
// const workerClient: WorkerEngineClient<Actions> =
|
||||
// createWorkerEngineClient<Actions>(
|
||||
// new SocialeWorkerEngine(),
|
||||
// () => {},
|
||||
// // (engineId) =>
|
||||
// // setSituationVersion((situationVersion) => {
|
||||
// // // console.log('??? setSituationVersion original')
|
||||
|
||||
// // // situationVersion[engineId] =
|
||||
// // // typeof situationVersion[engineId] !== 'number'
|
||||
// // // ? 0
|
||||
// // // : situationVersion[engineId]++
|
||||
|
||||
// // // return situationVersion
|
||||
// // return situationVersion + 1
|
||||
// // }),
|
||||
// { basename: 'mon-entreprise' }
|
||||
// )
|
||||
// workerClient.test.onSituationChange = function (engineId) {
|
||||
// console.log('original onSituationChange')
|
||||
// }
|
||||
// // }, 50)
|
||||
// console.time('loading')
|
||||
|
||||
/**
|
||||
* This hook is used to create a worker engine.
|
||||
* @param basename
|
||||
*/
|
||||
export const useCreateWorkerEngine = (basename: ProviderProps['basename']) => {
|
||||
const [situationVersion, setSituationVersion] = useState(0)
|
||||
const [workerEngine, setWorkerEngine] =
|
||||
useState<WorkerEngineClient<Actions>>()
|
||||
// console.log('llllllpppppppppppppppppppppppppp', workerClient)
|
||||
|
||||
const [transition, startTransition] = useTransition()
|
||||
|
||||
useEffect(() => {
|
||||
// workerClient.test.onSituationChange = function (engineId) {
|
||||
// console.log('transition...')
|
||||
|
||||
// startTransition(() => {
|
||||
// setSituationVersion((situationVersion) => {
|
||||
// // console.log('??? setSituationVersion original')
|
||||
|
||||
// // situationVersion[engineId] =
|
||||
// // typeof situationVersion[engineId] !== 'number'
|
||||
// // ? 0
|
||||
// // : situationVersion[engineId]++
|
||||
|
||||
// // return situationVersion
|
||||
// return situationVersion + 1
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
const workerClient = createWorkerEngineClient<Actions>(
|
||||
new SocialeWorkerEngine(),
|
||||
// () => {},
|
||||
(engineId) =>
|
||||
startTransition(() => {
|
||||
setSituationVersion((situationVersion) => {
|
||||
// console.log('??? setSituationVersion original')
|
||||
|
||||
// situationVersion[engineId] =
|
||||
// typeof situationVersion[engineId] !== 'number'
|
||||
// ? 0
|
||||
// : situationVersion[engineId]++
|
||||
|
||||
// return situationVersion
|
||||
return situationVersion + 1
|
||||
})
|
||||
}),
|
||||
{ basename: 'mon-entreprise' }
|
||||
)
|
||||
|
||||
console.log('{init worker}', workerClient)
|
||||
setWorkerEngine(workerClient)
|
||||
|
||||
void workerClient.asyncSetSituationWithEngineId({})
|
||||
console.time('{init}')
|
||||
let init = false
|
||||
void workerClient.isWorkerReady.finally(() => {
|
||||
init = true
|
||||
console.timeEnd('{init}')
|
||||
})
|
||||
|
||||
// example of usage
|
||||
// void Promise.all([
|
||||
// workerClient
|
||||
// .asyncEvaluate('SMIC')
|
||||
// .then((result) => console.log('{result}', result)),
|
||||
// workerClient
|
||||
// .asyncEvaluate('date')
|
||||
// .then((result) => console.log('{result}', result)),
|
||||
// ])
|
||||
|
||||
return () => {
|
||||
!init && console.timeEnd('{init}')
|
||||
console.log('{terminate worker}', workerClient)
|
||||
|
||||
// workerClient.terminate()
|
||||
}
|
||||
}, [basename])
|
||||
|
||||
// return workerEngine ? { ...workerEngine, situationVersion } : null
|
||||
const memo = useMemo(
|
||||
() => (workerEngine ? { ...workerEngine, situationVersion } : undefined),
|
||||
[situationVersion, workerEngine]
|
||||
)
|
||||
|
||||
return memo
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
// const useSituationVersion = (workerEngineCtx: WorkerEngineCtx) =>
|
||||
// workerEngineCtx.situationVersion
|
||||
// [engineId]
|
||||
|
||||
interface Options<Default> {
|
||||
defaultValue?: Default
|
||||
workerEngine?: WorkerEngine
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around usePromise that adds the situation version to the dependencies
|
||||
* @example const date = usePromiseOnSituationChange(() => asyncEvaluate('date'), []) // date will be updated when the situation changes
|
||||
* @deprecated
|
||||
*/
|
||||
export const usePromiseOnSituationChange = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
deps: DependencyList,
|
||||
{ defaultValue, workerEngine: workerEngineOption }: Options<Default> = {}
|
||||
): T | Default => {
|
||||
const defaultWorkerEngineCtx = useWorkerEngine()
|
||||
const { situationVersion } = workerEngineOption ?? defaultWorkerEngineCtx
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const state = usePromise(promise, [...deps, situationVersion], defaultValue)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const useLazyPromiseOnSituationChange = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
deps: DependencyList,
|
||||
{ defaultValue, workerEngine: workerEngineOption }: Options<Default> = {}
|
||||
): [T | Default, () => Promise<T>] => {
|
||||
const defaultWorkerEngineCtx = useWorkerEngine()
|
||||
const { situationVersion } = workerEngineOption ?? defaultWorkerEngineCtx
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const tuple = useLazyPromise(
|
||||
promise,
|
||||
[...deps, situationVersion],
|
||||
defaultValue
|
||||
)
|
||||
|
||||
return tuple
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is used to get a rule in the worker.
|
||||
* @param dottedName
|
||||
* @param options
|
||||
*/
|
||||
export const useAsyncGetRule = <
|
||||
// T extends unknown = undefined,
|
||||
Default = undefined,
|
||||
>(
|
||||
dottedName: DottedName,
|
||||
{ defaultValue, workerEngine: workerEngineOption }: Options<Default> = {}
|
||||
) => {
|
||||
const defaultWorkerEngine = useWorkerEngine()
|
||||
const workerEngine = workerEngineOption ?? defaultWorkerEngine
|
||||
|
||||
return usePromiseOnSituationChange(
|
||||
async () => workerEngine.asyncGetRuleWithEngineId(dottedName),
|
||||
[dottedName, workerEngine],
|
||||
{ defaultValue, workerEngine }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is used to get parsed rules in the worker.
|
||||
* @param engineId
|
||||
*/
|
||||
export const useAsyncParsedRules = <
|
||||
Default = undefined, //
|
||||
>({
|
||||
defaultValue,
|
||||
workerEngine: workerEngineOption,
|
||||
}: Options<Default> = {}) => {
|
||||
const defaultWorkerEngine = useWorkerEngine()
|
||||
const workerEngine = workerEngineOption ?? defaultWorkerEngine
|
||||
|
||||
return usePromiseOnSituationChange(
|
||||
async () => workerEngine.asyncGetParsedRulesWithEngineId(),
|
||||
[workerEngine],
|
||||
{ defaultValue, workerEngine }
|
||||
)
|
||||
}
|
||||
|
||||
export const useShallowCopy = (
|
||||
workerEngine: WorkerEngine
|
||||
): WorkerEngine | undefined => {
|
||||
const [situationVersion, setSituationVersion] = useState(0)
|
||||
|
||||
// const defaultWorkerEngine = useWorkerEngine()
|
||||
// const workerEngine = workerEngineParam
|
||||
// ?? defaultWorkerEngine
|
||||
|
||||
// console.log('??? situ version', situationVersion)
|
||||
|
||||
const workerEngineCopy = usePromiseOnSituationChange(
|
||||
async () => {
|
||||
const copy = await workerEngine.asyncShallowCopyWithEngineId(() => {
|
||||
// console.log('??? onSituationChange', copy)
|
||||
|
||||
setSituationVersion((x) => x + 1)
|
||||
})
|
||||
|
||||
// copy.onSituationChange = (x) => {
|
||||
// console.log('??? onSituationChange', copy)
|
||||
|
||||
// setSituationVersion(x)
|
||||
// }
|
||||
|
||||
// console.log('??? xxxxxxxxxxxxxxxxxxxxxxxxxxx', copy)
|
||||
|
||||
return copy
|
||||
},
|
||||
[workerEngine],
|
||||
{ defaultValue: undefined, workerEngine }
|
||||
)
|
||||
|
||||
const memo = useMemo(
|
||||
() =>
|
||||
workerEngineCopy ? { ...workerEngineCopy, situationVersion } : undefined,
|
||||
[situationVersion, workerEngineCopy]
|
||||
)
|
||||
|
||||
return memo
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
import Engine from 'publicodes'
|
||||
|
||||
/**
|
||||
* This file run any publicodes engine in a web worker.
|
||||
*/
|
||||
|
||||
export type WorkerEngineActions<
|
||||
InitParams extends unknown[],
|
||||
Name extends string,
|
||||
> =
|
||||
| {
|
||||
action: 'init'
|
||||
params: InitParams
|
||||
result: number
|
||||
}
|
||||
| {
|
||||
action: 'setSituation'
|
||||
params: Parameters<Engine<Name>['setSituation']>
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'evaluate'
|
||||
params: Parameters<Engine<Name>['evaluate']>
|
||||
result: ReturnType<Engine<Name>['evaluate']>
|
||||
}
|
||||
| {
|
||||
action: 'getRule'
|
||||
params: Parameters<Engine<Name>['getRule']>
|
||||
result: ReturnType<Engine<Name>['getRule']>
|
||||
}
|
||||
| {
|
||||
action: 'getParsedRules'
|
||||
params: []
|
||||
result: ReturnType<Engine<Name>['getParsedRules']>
|
||||
}
|
||||
| {
|
||||
action: 'shallowCopy'
|
||||
params: [] // no params cause we use engineId
|
||||
result: number
|
||||
}
|
||||
| {
|
||||
action: 'deleteShallowCopy'
|
||||
params: [] // no params cause we use engineId
|
||||
result: void
|
||||
}
|
||||
|
||||
type DistributiveOmit<T, K extends keyof T> = T extends unknown
|
||||
? Omit<T, K>
|
||||
: never
|
||||
|
||||
type GenericParams = {
|
||||
/**
|
||||
* The id of the engine to use, the default engine is 0
|
||||
*/
|
||||
engineId?: number
|
||||
|
||||
/**
|
||||
* The id of the message, used to identify the response
|
||||
*/
|
||||
id: number
|
||||
}
|
||||
|
||||
export type WorkerEngineAction<
|
||||
Acts extends WorkerEngineActions<unknown[], string>,
|
||||
T extends Acts['action'],
|
||||
> = Extract<Acts, { action: T }>
|
||||
|
||||
export const createWorkerEngine = <
|
||||
Name extends string,
|
||||
EngineType extends Engine<Name>,
|
||||
InitParams extends unknown[] = unknown[],
|
||||
>(
|
||||
init: (...params: InitParams) => EngineType
|
||||
) => {
|
||||
type Params = DistributiveOmit<
|
||||
WorkerEngineActions<InitParams, Name> & GenericParams,
|
||||
'result'
|
||||
>
|
||||
|
||||
let engines: (EngineType | undefined)[] = []
|
||||
let queue: (Params & { engineId: number })[] = []
|
||||
|
||||
let setDefaultEngineReady: (() => void) | null = null
|
||||
const isDefaultEngineReady = new Promise(
|
||||
(resolve) => (setDefaultEngineReady = resolve as () => void)
|
||||
)
|
||||
|
||||
const actions = (
|
||||
data: Params
|
||||
// & { engines: EngineType[] }
|
||||
) => {
|
||||
const { engineId = 0, id, action, params } = data
|
||||
|
||||
const engine = engines[engineId]
|
||||
if (!engine) {
|
||||
throw new Error('Engine does not exist')
|
||||
}
|
||||
|
||||
if (action === 'setSituation') {
|
||||
// safeSetSituation(
|
||||
// engine,
|
||||
// ({ situation, <faultyDottedName> }) => {
|
||||
// console.error('setSituation', { situation, faultyDottedName })
|
||||
// },
|
||||
// ...params
|
||||
// )
|
||||
engine.setSituation(...params)
|
||||
|
||||
return { id }
|
||||
} else if (action === 'evaluate') {
|
||||
const result = engine.evaluate(...params)
|
||||
console.log('[result]', result)
|
||||
|
||||
return { id, result }
|
||||
} else if (action === 'getRule') {
|
||||
const result = engine.getRule(...params)
|
||||
|
||||
return { id, result }
|
||||
} else if (action === 'getParsedRules') {
|
||||
const result = engine.getParsedRules()
|
||||
|
||||
return { id, result }
|
||||
} else if (action === 'shallowCopy') {
|
||||
engines.push(engine.shallowCopy() as EngineType)
|
||||
|
||||
return { id, result: engines.length - 1 }
|
||||
} else if (action === 'deleteShallowCopy') {
|
||||
if (engineId === 0) {
|
||||
throw new Error('Cannot delete the default engine')
|
||||
}
|
||||
|
||||
delete engines[engineId]
|
||||
engines = engines.splice(engineId, 1)
|
||||
|
||||
console.log('[engines]', engines)
|
||||
|
||||
return { id }
|
||||
} else {
|
||||
console.log('[unknow message]', data)
|
||||
|
||||
return { id }
|
||||
}
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
onmessage = async (e) => {
|
||||
console.log('[onmessage]', e.data)
|
||||
|
||||
const { engineId = 0, id, action, params } = e.data as Params
|
||||
|
||||
try {
|
||||
if (action === 'init') {
|
||||
// console.log('[init engine]')
|
||||
// const [{ basename }] = params
|
||||
try {
|
||||
// let rules = rawRules
|
||||
// if (basename === 'infrance') {
|
||||
// // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
// rules = translateRules('en', ruleTranslations, rules)
|
||||
// }
|
||||
// engineFactory(rules)
|
||||
|
||||
const engine = init(...params)
|
||||
|
||||
const engineId = engines.length
|
||||
engines.push(engine)
|
||||
|
||||
postMessage({ id, result: engineId })
|
||||
setDefaultEngineReady?.()
|
||||
console.log('[engine ready]', engines[engineId])
|
||||
} catch (e) {
|
||||
console.error('[error]', e)
|
||||
// postMessage('error')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await isDefaultEngineReady
|
||||
|
||||
queue.push({ engineId, id, action, params } as Params & {
|
||||
engineId: number
|
||||
})
|
||||
|
||||
if (timeout !== null) {
|
||||
return
|
||||
}
|
||||
|
||||
// timeout !== null && clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
const aborts: number[] = []
|
||||
const setSituationEncountered: boolean[] = []
|
||||
const filteredQueue = [...queue]
|
||||
.reverse()
|
||||
.filter(({ action, engineId, id }) => {
|
||||
if (action === 'setSituation')
|
||||
setSituationEncountered[engineId] = true
|
||||
|
||||
const keep =
|
||||
!setSituationEncountered[engineId] ||
|
||||
(setSituationEncountered[engineId] && action !== 'evaluate')
|
||||
|
||||
if (!keep) aborts.push(id)
|
||||
|
||||
return keep
|
||||
})
|
||||
.reverse()
|
||||
console.log('[start queue]', queue, filteredQueue)
|
||||
|
||||
console.time('bench')
|
||||
postMessage({
|
||||
batch: filteredQueue.map((params) => {
|
||||
try {
|
||||
const res = actions(params)
|
||||
|
||||
return res
|
||||
} catch (error) {
|
||||
return { id: params.id, error }
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
console.timeEnd('bench')
|
||||
const error = new Error(
|
||||
'aborts the action because the situation has changed'
|
||||
)
|
||||
postMessage({ batch: aborts.map((id) => ({ id, error })) })
|
||||
|
||||
queue = []
|
||||
timeout = null
|
||||
}, 50)
|
||||
} catch (error) {
|
||||
return postMessage({ id, error })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,347 @@
|
|||
import type { WorkerEngineAction, WorkerEngineActions } from './workerEngine'
|
||||
|
||||
if (import.meta.env.SSR || !window.Worker) {
|
||||
throw new Error('Worker is not supported in this browser')
|
||||
}
|
||||
|
||||
// const sleepMs = (ms: number) =>
|
||||
// new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
/**
|
||||
* This file is a client to communicate with workerEngine.
|
||||
*/
|
||||
|
||||
export type WorkerEngineClient<
|
||||
Actions extends WorkerEngineActions<InitParams, Name>,
|
||||
InitParams extends unknown[] = unknown[],
|
||||
Name extends string = string,
|
||||
> = ReturnType<typeof createWorkerEngineClient<Actions, InitParams, Name>>
|
||||
|
||||
export const createWorkerEngineClient = <
|
||||
Actions extends WorkerEngineActions<InitParams, Name>,
|
||||
InitParams extends unknown[] = unknown[],
|
||||
Name extends string = string,
|
||||
>(
|
||||
worker: Worker,
|
||||
onSituationChange: (engineId: number) => void = () => {},
|
||||
...initParams: WorkerEngineAction<Actions, 'init'>['params']
|
||||
) => {
|
||||
type Action<T extends Actions['action']> = WorkerEngineAction<Actions, T>
|
||||
|
||||
const test = {
|
||||
onSituationChange: (engineId: number) => {},
|
||||
}
|
||||
|
||||
console.log('{createWorker}')
|
||||
|
||||
type WorkerEnginePromise<T extends Actions['action'] = Actions['action']> = {
|
||||
engineId: number
|
||||
action: T
|
||||
resolve: (value: unknown) => void
|
||||
reject: (value: unknown) => void
|
||||
}
|
||||
|
||||
let promises: WorkerEnginePromise[] = []
|
||||
let lastCleanup: null | NodeJS.Timeout = null
|
||||
|
||||
const postMessage = async <T extends Actions['action'], U extends Action<T>>(
|
||||
engineId: number,
|
||||
action: T,
|
||||
...params: U['params']
|
||||
// ...params: U['params'] extends [] ? [] : U['params']
|
||||
) => {
|
||||
console.log('{postMessage}', action, params)
|
||||
const promiseTimeout = 100000
|
||||
const warning = setTimeout(() => {
|
||||
console.log('{promise waiting for too long, aborting!}', action, params)
|
||||
promises[id].reject?.(new Error('timeout'))
|
||||
}, promiseTimeout)
|
||||
|
||||
lastCleanup !== null && clearInterval(lastCleanup)
|
||||
lastCleanup = setTimeout(() => {
|
||||
if (promises.length) {
|
||||
console.log('{cleanup}', promises.length)
|
||||
promises = []
|
||||
lastCleanup = null
|
||||
}
|
||||
}, 200000)
|
||||
|
||||
const id = promises.length
|
||||
console.time(`execute-${id}`)
|
||||
|
||||
const stack = new Error().stack
|
||||
|
||||
const promise = new Promise<U['result']>((resolve, reject) => {
|
||||
promises[id] = {
|
||||
engineId,
|
||||
action,
|
||||
resolve: (...params: unknown[]) => {
|
||||
clearTimeout(warning)
|
||||
|
||||
return resolve(...(params as Parameters<typeof resolve>))
|
||||
},
|
||||
reject: (err) => {
|
||||
clearTimeout(warning)
|
||||
|
||||
console.error(err)
|
||||
console.error(stack)
|
||||
console.error(new Error((err as Error).message, { cause: stack }))
|
||||
|
||||
return reject(err)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
worker.postMessage({ engineId, action, params, id })
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
worker.onmessageerror = function (e) {
|
||||
console.log('{onmessageerror}', e)
|
||||
}
|
||||
|
||||
worker.onerror = function (e) {
|
||||
console.log('{onerror}', e)
|
||||
}
|
||||
|
||||
const ppp = (data) => {
|
||||
console.timeEnd(`execute-${data.id}`)
|
||||
if (data.id === 0) {
|
||||
console.timeEnd('loading')
|
||||
}
|
||||
|
||||
if ('error' in data) {
|
||||
return promises[data.id].reject?.(data.error)
|
||||
}
|
||||
promises[data.id].resolve?.(data.result)
|
||||
}
|
||||
|
||||
worker.onmessage = function (e) {
|
||||
console.log('{msg}', e.data)
|
||||
|
||||
if ('batch' in e.data) {
|
||||
e.data.batch.forEach((data) => {
|
||||
ppp(data)
|
||||
})
|
||||
} else {
|
||||
ppp(e.data)
|
||||
}
|
||||
}
|
||||
|
||||
const engineId = 0
|
||||
const isWorkerReady = postMessage(engineId, 'init', ...initParams)
|
||||
|
||||
const workerEngineConstruct = (
|
||||
engineId: number,
|
||||
onSituationChange: (engineId: number) => void = () => {}
|
||||
) => ({
|
||||
test,
|
||||
engineId,
|
||||
worker,
|
||||
isWorkerReady,
|
||||
onSituationChange,
|
||||
// promises,
|
||||
postMessage,
|
||||
terminate: () => {
|
||||
workerEngine.worker.terminate()
|
||||
promises.forEach((promise) => promise.reject?.('worker terminated'))
|
||||
promises = []
|
||||
},
|
||||
|
||||
// withEngineId: (engineId: number, promise: Promise) => {
|
||||
// const tmp = workerEngine.engineId
|
||||
// workerEngine.engineId = engineId
|
||||
|
||||
// promise()
|
||||
|
||||
// workerEngine.engineId = tmp
|
||||
// },
|
||||
|
||||
// asynchronous setSituation function
|
||||
|
||||
/**
|
||||
* This function is used to set the situation in the worker with a specific engineId.
|
||||
*/
|
||||
asyncSetSituationWithEngineId: async (
|
||||
// engineId: number,
|
||||
...params: Action<'setSituation'>['params']
|
||||
): Promise<Action<'setSituation'>['result']> => {
|
||||
// abort every action "evaluate"
|
||||
|
||||
console.log(')=>', promises)
|
||||
|
||||
// promises.forEach((promise) => {
|
||||
// if (engineId === promise.engineId && promise.action === 'evaluate') {
|
||||
// promise.reject?.('abort')
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (!workerEngine.worker) {
|
||||
// await sleepMs(10)
|
||||
|
||||
// return workerEngine.asyncSetSituationWithEngineId(engineId, ...params)
|
||||
// }
|
||||
|
||||
// console.log('??? engideid', engineId, workerEngine, onSituationChange)
|
||||
|
||||
const ret = await workerEngine.postMessage(
|
||||
engineId,
|
||||
'setSituation',
|
||||
...params
|
||||
)
|
||||
|
||||
console.log('testtesttesttesttest', test)
|
||||
|
||||
test.onSituationChange(engineId)
|
||||
|
||||
return ret
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is used to set the situation in the worker.
|
||||
*/
|
||||
// asyncSetSituation: async (...params: Action<'setSituation'>['params']) =>
|
||||
// workerEngine.asyncSetSituationWithEngineId(defaultEngineId, ...params),
|
||||
|
||||
// asynchronous evaluate function
|
||||
|
||||
/**
|
||||
* This function is used to evaluate a publicodes expression in the worker with a specific engineId.
|
||||
*/
|
||||
asyncEvaluateWithEngineId: async (
|
||||
// engineId: number,
|
||||
...params: Action<'evaluate'>['params']
|
||||
): Promise<Action<'evaluate'>['result']> => {
|
||||
// if (!workerEngine.worker) {
|
||||
// await sleepMs(10)
|
||||
|
||||
// return workerEngine.asyncEvaluateWithEngineId(engineId, ...params)
|
||||
// }
|
||||
|
||||
const promise = await workerEngine.postMessage(
|
||||
engineId,
|
||||
'evaluate',
|
||||
...params
|
||||
)
|
||||
|
||||
// console.trace('{asyncEvaluateWithEngineId}')
|
||||
|
||||
return promise
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is used to evaluate a publicodes expression in the worker.
|
||||
*/
|
||||
// asyncEvaluate: async (...params: Action<'evaluate'>['params']) =>
|
||||
// workerEngine.asyncEvaluateWithEngineId(defaultEngineId, ...params),
|
||||
|
||||
// asynchronous getRule function:
|
||||
|
||||
/**
|
||||
* This function is used to get a publicodes rule that is in the worker with a specific EngineId.
|
||||
*/
|
||||
asyncGetRuleWithEngineId: async (
|
||||
// engineId: number,
|
||||
...params: Action<'getRule'>['params']
|
||||
): Promise<Action<'getRule'>['result']> => {
|
||||
// if (!workerEngine.worker) {
|
||||
// await sleepMs(10)
|
||||
|
||||
// return workerEngine.asyncGetRuleWithEngineId(engineId, ...params)
|
||||
// }
|
||||
|
||||
return await workerEngine.postMessage(engineId, 'getRule', ...params)
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is used to get a rule in the worker.
|
||||
*/
|
||||
// asyncGetRule: async (...params: Action<'getRule'>['params']) =>
|
||||
// workerEngine.asyncGetRuleWithEngineId(defaultEngineId, ...params),
|
||||
|
||||
// asynchronous getParsedRules function
|
||||
|
||||
/**
|
||||
* This function is used to get all the parsed rules in the worker with a specific engineId.
|
||||
*/
|
||||
asyncGetParsedRulesWithEngineId: async () // engineId: number
|
||||
: Promise<Action<'getParsedRules'>['result']> => {
|
||||
// if (!workerEngine.worker) {
|
||||
// await sleepMs(10)
|
||||
|
||||
// return workerEngine.asyncGetParsedRulesWithEngineId(engineId)
|
||||
// }
|
||||
|
||||
return await workerEngine.postMessage(engineId, 'getParsedRules')
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is used to get all the parsed rules in the worker.
|
||||
*/
|
||||
// asyncGetParsedRules: async () =>
|
||||
// workerEngine.asyncGetParsedRulesWithEngineId(defaultEngineId),
|
||||
|
||||
// asynchronous shallowCopy function
|
||||
|
||||
/**
|
||||
* This function is used to shallow copy an engine in the worker with a specific engineId.
|
||||
*/
|
||||
asyncShallowCopyWithEngineId: async (
|
||||
onSituationChange: () => void = () => {}
|
||||
) => {
|
||||
// engineId: number
|
||||
// if (!workerEngine.worker) {
|
||||
// await sleepMs(10)
|
||||
|
||||
// return workerEngine.asyncShallowCopyWithEngineId(engineId)
|
||||
// }
|
||||
|
||||
const newEngineId = await workerEngine.postMessage(
|
||||
engineId,
|
||||
'shallowCopy'
|
||||
)
|
||||
|
||||
// return {
|
||||
// ...workerEngine,
|
||||
// engineId: newEngineId,
|
||||
// }
|
||||
|
||||
// const uu = Object.assign({}, workerEngine)
|
||||
// uu.engineId = newEngineId
|
||||
|
||||
// console.log('???[newEngineId]', newEngineId, engineId)
|
||||
|
||||
return workerEngineConstruct(newEngineId, onSituationChange)
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is used to shallow copy an engine in the worker.
|
||||
*/
|
||||
// asyncShallowCopy: async () => ({
|
||||
// ...context,
|
||||
// engineId: await workerEngine.asyncShallowCopyWithEngineId(
|
||||
// defaultEngineId
|
||||
// ),
|
||||
// }),
|
||||
|
||||
// asynchronous deleteShallowCopy function
|
||||
|
||||
/**
|
||||
* * This function is used to delete a shallow copy of an engine in the worker.
|
||||
*/
|
||||
asyncDeleteShallowCopy: async () // engineId: number
|
||||
: Promise<Action<'deleteShallowCopy'>['result']> => {
|
||||
// if (!workerEngine.worker) {
|
||||
// await sleepMs(10)
|
||||
|
||||
// return workerEngine.asyncDeleteShallowCopy(engineId)
|
||||
// }
|
||||
|
||||
return await workerEngine.postMessage(engineId, 'deleteShallowCopy')
|
||||
},
|
||||
})
|
||||
const workerEngine = workerEngineConstruct(engineId, onSituationChange)
|
||||
|
||||
return workerEngine
|
||||
}
|
|
@ -1,350 +0,0 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import React, { DependencyList, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { ProviderProps } from './components/Provider'
|
||||
import type { Action, Actions } from './engine.worker'
|
||||
import EngineWorker from './engine.worker?worker'
|
||||
import { usePromise } from './hooks/usePromise'
|
||||
|
||||
if (!window.Worker) {
|
||||
throw new Error('Worker is not supported in this browser')
|
||||
}
|
||||
|
||||
interface WorkerEngine {
|
||||
worker: Worker
|
||||
postMessage: <T extends Actions['action'], U extends Action<T>>(
|
||||
engineId: number,
|
||||
action: T,
|
||||
...params: U['params']
|
||||
) => Promise<U['result']>
|
||||
isWorkerReady: Promise<void>
|
||||
promises: {
|
||||
resolve: (value: unknown) => void
|
||||
reject: (value: unknown) => void
|
||||
}[]
|
||||
}
|
||||
|
||||
const initWorkerEngine = (
|
||||
basename: ProviderProps['basename'],
|
||||
setSituationVersion: (updater: (situationVersion: number) => number) => void
|
||||
) => {
|
||||
const newWorker: Worker = new EngineWorker()
|
||||
|
||||
const promises: WorkerEngine['promises'] = []
|
||||
|
||||
const postMessage = async <T extends Actions['action'], U extends Action<T>>(
|
||||
engineId: number,
|
||||
action: T,
|
||||
...params: U['params']
|
||||
) => {
|
||||
if (action === 'setSituation') {
|
||||
setSituationVersion((situationVersion) => situationVersion + 1)
|
||||
}
|
||||
|
||||
const warning = setTimeout(() => {
|
||||
console.log('promise waiting for too long, aborting!', action, params)
|
||||
promises[id].reject?.(new Error('timeout'))
|
||||
}, 5000)
|
||||
|
||||
const id = promises.length
|
||||
const promise = new Promise<U['result']>((resolve, reject) => {
|
||||
promises[id] = {
|
||||
resolve(...params: unknown[]) {
|
||||
clearTimeout(warning)
|
||||
|
||||
return resolve(...(params as Parameters<typeof resolve>))
|
||||
},
|
||||
reject(err) {
|
||||
clearTimeout(warning)
|
||||
|
||||
return reject(err)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
newWorker.postMessage({ engineId, action, params, id })
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
newWorker.onmessage = function (e) {
|
||||
console.log('msg:', e.data)
|
||||
if ('error' in e.data) {
|
||||
return promises[e.data.id].reject?.(e.data.error)
|
||||
}
|
||||
promises[e.data.id].resolve?.(e.data.result)
|
||||
}
|
||||
|
||||
const isWorkerReady = postMessage(0, 'init', { basename })
|
||||
|
||||
const workerEngine = {
|
||||
worker: newWorker,
|
||||
postMessage,
|
||||
isWorkerReady,
|
||||
promises,
|
||||
}
|
||||
|
||||
return workerEngine
|
||||
}
|
||||
|
||||
const sleepMs = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
let worker: ReturnType<typeof initWorkerEngine> | null = null
|
||||
|
||||
/**
|
||||
* This hook is used to create a worker engine.
|
||||
* @param basename
|
||||
*/
|
||||
const useCreateWorkerEngine = (basename: ProviderProps['basename']) => {
|
||||
const [situationVersion, setSituationVersion] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
worker = initWorkerEngine(basename, setSituationVersion)
|
||||
|
||||
console.time('init')
|
||||
void worker.isWorkerReady.then(() => console.timeEnd('init'))
|
||||
|
||||
// example of usage
|
||||
void Promise.all([
|
||||
asyncEvaluate('SMIC').then((result) => console.log('result', result)),
|
||||
asyncEvaluate('date').then((result) => console.log('result', result)),
|
||||
])
|
||||
|
||||
return () => {
|
||||
console.log('worker terminated!')
|
||||
|
||||
worker?.worker.terminate()
|
||||
worker?.promises.forEach((promise) =>
|
||||
promise.reject?.('worker terminated')
|
||||
)
|
||||
worker = null
|
||||
}
|
||||
}, [basename])
|
||||
|
||||
return situationVersion
|
||||
}
|
||||
|
||||
// asynchronous setSituation function
|
||||
|
||||
/**
|
||||
* This function is used to set the situation in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncSetSituationWithEngineId = async (
|
||||
engineId: number,
|
||||
...params: Action<'setSituation'>['params']
|
||||
): Promise<Action<'setSituation'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncSetSituationWithEngineId(engineId, ...params)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'setSituation', ...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* * This function is used to set the situation in the worker.
|
||||
*/
|
||||
export const asyncSetSituation = async (
|
||||
...params: Action<'setSituation'>['params']
|
||||
) => asyncSetSituationWithEngineId(0, ...params)
|
||||
|
||||
// asynchronous evaluate function
|
||||
|
||||
/**
|
||||
* This function is used to evaluate a publicodes expression in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncEvaluateWithEngineId = async (
|
||||
engineId: number,
|
||||
...params: Action<'evaluate'>['params']
|
||||
): Promise<Action<'evaluate'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncEvaluateWithEngineId(engineId, ...params)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'evaluate', ...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to evaluate a publicodes expression in the worker.
|
||||
*/
|
||||
export const asyncEvaluate = async (...params: Action<'evaluate'>['params']) =>
|
||||
asyncEvaluateWithEngineId(0, ...params)
|
||||
|
||||
/**
|
||||
* This hook is used to evaluate a publicodes expression in the worker.
|
||||
* @param defaultValue
|
||||
*/
|
||||
// export const useAsyncEvaluate = <T extends unknown = undefined>(
|
||||
// defaultValue?: T
|
||||
// ) => {
|
||||
// const [response, setResponse] = useState<Action<'evaluate'>['result'] | T>(
|
||||
// defaultValue as T
|
||||
// )
|
||||
|
||||
// const evaluate = useCallback(async (value: PublicodesExpression) => {
|
||||
// const result = await asyncEvaluate(value)
|
||||
// setResponse(result)
|
||||
|
||||
// return result
|
||||
// }, [])
|
||||
|
||||
// return [response, evaluate] as const
|
||||
// }
|
||||
|
||||
// asynchronous getRule function:
|
||||
|
||||
/**
|
||||
* This function is used to get a publicodes rule that is in the worker with a specific EngineId.
|
||||
*/
|
||||
export const asyncGetRuleWithEngineId = async (
|
||||
engineId: number,
|
||||
...params: Action<'getRule'>['params']
|
||||
): Promise<Action<'getRule'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncGetRuleWithEngineId(engineId, ...params)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'getRule', ...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to get a rule in the worker.
|
||||
*/
|
||||
export const asyncGetRule = async (...params: Action<'getRule'>['params']) =>
|
||||
asyncGetRuleWithEngineId(0, ...params)
|
||||
|
||||
/**
|
||||
* This hook is used to get a rule in the worker.
|
||||
* @param defaultValue
|
||||
*/
|
||||
export const useAsyncGetRule = <
|
||||
Names extends DottedName,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
|
||||
T extends unknown = undefined
|
||||
>(
|
||||
dottedName: Names,
|
||||
defaultValue?: T
|
||||
) =>
|
||||
usePromiseOnSituationChange(
|
||||
() => asyncGetRule(dottedName),
|
||||
[dottedName],
|
||||
defaultValue
|
||||
)
|
||||
|
||||
// asynchronous getParsedRules function
|
||||
|
||||
/**
|
||||
* This function is used to get all the parsed rules in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncGetParsedRulesWithEngineId = async (
|
||||
engineId: number
|
||||
): Promise<Action<'getParsedRules'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncGetParsedRulesWithEngineId(engineId)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'getParsedRules')
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to get all the parsed rules in the worker.
|
||||
*/
|
||||
export const asyncGetParsedRules = async () =>
|
||||
asyncGetParsedRulesWithEngineId(0)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const useParsedRules = (engineId = 0) =>
|
||||
usePromiseOnSituationChange(
|
||||
async () => await asyncGetParsedRulesWithEngineId(engineId),
|
||||
[engineId]
|
||||
)
|
||||
|
||||
// asynchronous shallowCopy function
|
||||
|
||||
/**
|
||||
* This function is used to shallow copy an engine in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncShallowCopyWithEngineId = async (
|
||||
engineId: number
|
||||
): Promise<Action<'shallowCopy'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncShallowCopyWithEngineId(engineId)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'shallowCopy')
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to shallow copy an engine in the worker.
|
||||
*/
|
||||
export const asyncShallowCopy = async () => asyncShallowCopyWithEngineId(0)
|
||||
|
||||
// asynchronous deleteShallowCopy function
|
||||
|
||||
/**
|
||||
* * This function is used to delete a shallow copy of an engine in the worker.
|
||||
*/
|
||||
export const asyncDeleteShallowCopy = async (
|
||||
engineId: number
|
||||
): Promise<Action<'deleteShallowCopy'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncDeleteShallowCopy(engineId)
|
||||
}
|
||||
|
||||
return await worker.postMessage(0, 'deleteShallowCopy', { engineId })
|
||||
}
|
||||
|
||||
const SituationUpdated = React.createContext<number>(0)
|
||||
|
||||
export const SituationUpdatedProvider = ({
|
||||
children,
|
||||
basename,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
basename: ProviderProps['basename']
|
||||
}) => {
|
||||
const situationVersion = useCreateWorkerEngine(basename)
|
||||
|
||||
return (
|
||||
<SituationUpdated.Provider value={situationVersion}>
|
||||
{children}
|
||||
</SituationUpdated.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSituationUpdated = () => {
|
||||
const situationVersion = useContext(SituationUpdated)
|
||||
|
||||
return situationVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around usePromise that adds the situation version to the dependencies
|
||||
* @example const date = usePromiseOnSituationChange(() => asyncEvaluate('date'), []) // date will be updated when the situation changes
|
||||
*/
|
||||
export const usePromiseOnSituationChange = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
deps: DependencyList,
|
||||
defaultValue?: Default
|
||||
): T | Default => {
|
||||
const situationVersion = useSituationUpdated()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const state = usePromise(promise, [...deps, situationVersion], defaultValue)
|
||||
|
||||
return state
|
||||
}
|
|
@ -41,6 +41,17 @@ export default defineConfig(({ command, mode }) => ({
|
|||
IS_STAGING: mode === 'production' && !isProductionBranch(mode),
|
||||
IS_PRODUCTION: mode === 'production' && isProductionBranch(mode),
|
||||
},
|
||||
worker: {
|
||||
plugins: [
|
||||
yaml({
|
||||
transform(data, filePath) {
|
||||
return filePath.endsWith('/rules-en.yaml')
|
||||
? cleanAutomaticTag(data)
|
||||
: data
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'run-script-on-file-change',
|
||||
|
|
Loading…
Reference in New Issue