From f4a6ac21f2e185f4024dce84a0387cebca061711 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 24 Jun 2024 16:11:01 +0200 Subject: [PATCH] poc: reprise mode Fix #1003 --- .../behaviour/reprise/RepriseOrchestrator.tsx | 239 ++++++++++++++++++ .../behaviour/reprise/reprise.stories.tsx | 22 ++ src/stories/behaviour/reprise/source.json | 90 +++++++ src/use-lunatic/use-lunatic.ts | 1 + 4 files changed, 352 insertions(+) create mode 100644 src/stories/behaviour/reprise/RepriseOrchestrator.tsx create mode 100644 src/stories/behaviour/reprise/reprise.stories.tsx create mode 100644 src/stories/behaviour/reprise/source.json diff --git a/src/stories/behaviour/reprise/RepriseOrchestrator.tsx b/src/stories/behaviour/reprise/RepriseOrchestrator.tsx new file mode 100644 index 000000000..d7e3f1250 --- /dev/null +++ b/src/stories/behaviour/reprise/RepriseOrchestrator.tsx @@ -0,0 +1,239 @@ +// @ts-nocheck +import type { LunaticSource } from '../../../type.source'; +import { + Button, + type LunaticComponentProps, + LunaticComponents, + useLunatic, +} from '../../../index'; +import { objectMap } from '../../../utils/object'; +import React, { useEffect, useState } from 'react'; + +type Props = { + source: LunaticSource; + data: Record; +}; + +type VariableChange = { + type: 'COLLECTED' | 'EDITED'; + value: unknown; + timestamp: number; +}; + +const slots = { + ComponentWrapper: ComponentWrapper, +}; + +const dateFormatter = new Intl.DateTimeFormat(undefined, { + dateStyle: 'short', + timeStyle: 'medium', +}); + +/** + * Classe démontrant une première structure pour sauvegarder les changements + * Dans les faits, pour que le valeur soit réactive, il faudra utiliser un gestionnaire d'état. + */ +class VariableChangeStore { + public variables: Record = {}; + + setCollected(data: Record) { + this.variables = objectMap(data, (k, v) => [ + k, + [ + { + type: 'COLLECTED', + value: v, + timestamp: new Date().getTime(), + }, + ], + ]); + } + + updateVariable( + name: string, + value: unknown, + mode: VariableChange['type'] = 'EDITED' + ) { + if (!(name in this.variables)) { + this.variables[name] = []; + } + const changes = this.variables[name]; + const change = { + type: mode, + value, + timestamp: new Date().getTime(), + }; + // The change has the same type has the last change, update it instead + if (changes.length > 0 && changes[changes.length - 1].type === mode) { + changes[changes.length - 1] = change; + } else { + changes.push(change); + } + } + + getCollectedVariable(name: string, iteration?: number) { + const changes = this.variables[name]; + if (!changes || changes.length === 0) { + return null; + } + const value = changes[0].value; + if (iteration !== undefined && Array.isArray(value)) { + return value[iteration]; + } + return value; + } +} + +const store = new VariableChangeStore(); + +export function RepriseOrchestrator({ source, data }: Props) { + const [_, setState] = useState(0); // Use to force render just for the demo + useEffect(() => { + store.setCollected(data); + setState((v) => v + 1); + }, []); + const { + getComponents, + variables, + goNextPage, + goPreviousPage, + isFirstPage, + isLastPage, + } = useLunatic(source, objectToData(data), { + onChange: (changes) => { + setTimeout(() => { + for (const variable of changes) { + store.updateVariable( + variable.name, + variables.get(variable.name), + variable.mode + ); + } + setState((v) => v + 1); + }, 0); + }, + }); + const components = getComponents(); + return ( +
+
+ +
+
+
+ + +
+ +

Changements

+ + {Object.keys(store.variables).map((name) => ( +
+ {name} +
+ {store.variables[name].map((change, k) => ( +
+
+ {change.type} +
+ {JSON.stringify(change.value)} +
+ {dateFormatter.format(new Date(change.timestamp))} +
+
+
+ ))} +
+
+ ))} +
+
+ ); +} + +/** + * Entoure les composants de formulaire pour rajouter les boutons de contrôles + */ +function ComponentWrapper(props: LunaticComponentProps<'Input'>) { + const updateValue = + 'response' in props + ? (v: unknown, mode: string) => { + props.handleChanges([ + { + name: props.response.name, + value: v, + mode: mode, + }, + ]); + } + : null; + + return ( +
+
+
+ {updateValue && ( + <> + + + + )} +
+
{props.children}
+
+
+ ); +} + +function objectToData(obj: Record) { + return { + COLLECTED: Object.fromEntries( + Object.entries(obj).map(([name, value]) => [ + name, + { + EDITED: null, + FORCED: null, + INPUTTED: null, + PREVIOUS: null, + COLLECTED: value, + }, + ]) + ), + }; +} diff --git a/src/stories/behaviour/reprise/reprise.stories.tsx b/src/stories/behaviour/reprise/reprise.stories.tsx new file mode 100644 index 000000000..852f3dcc8 --- /dev/null +++ b/src/stories/behaviour/reprise/reprise.stories.tsx @@ -0,0 +1,22 @@ +import { RepriseOrchestrator } from './RepriseOrchestrator'; +import source from './source.json'; + +export default { + title: 'Behaviour/Reprise', + component: RepriseOrchestrator, +}; + +export const Basic = { + args: { + source, + data: { + NOM: 'Doe', + PRENOMS: ['John', 'Jane'], + MAJEUR: [true, false], + }, + }, +}; + +export const WithProp = { + render: () =>
Hello
, +}; diff --git a/src/stories/behaviour/reprise/source.json b/src/stories/behaviour/reprise/source.json new file mode 100644 index 000000000..365747134 --- /dev/null +++ b/src/stories/behaviour/reprise/source.json @@ -0,0 +1,90 @@ +{ + "$schema": "../../../../lunatic-schema.json", + "components": [ + { + "componentType": "Input", + "label": { + "value": "\"Nom de l'occupant principal.\"", + "type": "VTL|MD" + }, + "maxLength": 30, + "id": "surname", + "description": { + "value": "\"For example, Bob.\"", + "type": "VTL|MD" + }, + "response": { + "name": "NOM" + }, + "page": "1" + }, + { + "componentType": "Loop", + "bindingDependencies": ["PRENOMS"], + "lines": { + "min": { "value": "1", "type": "VTL" }, + "max": { "value": "10", "type": "VTL" } + }, + "components": [ + { + "id": "prenoms", + "componentType": "Input", + "label": { "value": "\"Prénom\"", "type": "VTL|MD" }, + "maxLength": 30, + "bindingDependencies": ["PRENOM"], + "response": { + "name": "PRENOMS" + } + }, + { + "id": "majeur", + "componentType": "CheckboxBoolean", + "label": { "value": "Majeur ?", "type": "TXT" }, + "bindingDependencies": ["MAJEUR"], + "options": [ + { "value": "1", "label": { "value": "\"oui\"", "type": "VTL|MD" } }, + { "value": "0", "label": { "value": "\"non\"", "type": "VTL|MD" } } + ], + "response": { + "name": "MAJEUR" + } + } + ] + } + ], + "variables": [ + { + "variableType": "COLLECTED", + "name": "NOM", + "values": { + "PREVIOUS": null, + "COLLECTED": null, + "FORCED": null, + "EDITED": null, + "INPUTTED": null + } + }, + { + "variableType": "COLLECTED", + "name": "PRENOMS", + "values": { + "PREVIOUS": [], + "COLLECTED": [], + "FORCED": [], + "EDITED": [], + "INPUTTED": [] + } + }, + { + "variableType": "COLLECTED", + "name": "MAJEUR", + "values": { + "PREVIOUS": [], + "COLLECTED": [], + "FORCED": [], + "EDITED": [], + "INPUTTED": [] + } + } + ] +} diff --git a/src/use-lunatic/use-lunatic.ts b/src/use-lunatic/use-lunatic.ts index 0e1799ba6..8e215ed0d 100644 --- a/src/use-lunatic/use-lunatic.ts +++ b/src/use-lunatic/use-lunatic.ts @@ -192,6 +192,7 @@ function useLunatic( getData, getChangedData, resetChangedData, + variables: state.variables, // L'orchestrateur de reprise pourra avoir besoin de voir une valeur dans le store hasPageResponse: usePageHasResponse(components, state.executeExpression), // Components Provider,