Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions src/stories/behaviour/reprise/RepriseOrchestrator.tsx
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
};

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<string, VariableChange[]> = {};

setCollected(data: Record<string, unknown>) {
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 (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 200px' }}>
<div>
<LunaticComponents components={components} slots={slots} />
</div>
<div>
<div>
<Button onClick={goPreviousPage} disabled={isFirstPage}>
Previous
</Button>
<Button onClick={goNextPage} disabled={isLastPage}>
Next
</Button>
</div>

<h2>Changements</h2>

{Object.keys(store.variables).map((name) => (
<details key={name} style={{ marginBottom: '1rem' }}>
<summary style={{ cursor: 'pointer' }}>{name}</summary>
<div
style={{
display: 'grid',
gap: '1rem',
padding: '1rem',
gridTemplateColumns: '1fr',
}}
>
{store.variables[name].map((change, k) => (
<div key={k}>
<div>
{change.type}
<br />
{JSON.stringify(change.value)}
<br />
{dateFormatter.format(new Date(change.timestamp))}
</div>
<hr style={{ width: '100%' }} />
</div>
))}
</div>
</details>
))}
</div>
</div>
);
}

/**
* 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 (
<div className="lunatic-component">
<div style={{ display: 'flex', gap: '1rem' }}>
<div style={{ width: 60 }}>
{updateValue && (
<>
<button onClick={() => updateValue('nombrut', 'BRUT')}>B</button>
<button
onClick={() =>
updateValue(
store.getCollectedVariable(
props.response.name,
props.iteration
),
'COLLECTED'
)
}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={12}
hanging={12}
>
<g fill="none">
<path d="M24 0v24H0V0zM12.6 23.3h-.2v.5h.2v-.5m.3-.2-.2.1v.5l.2.1v-.6m-.8 0v.6h.2v-.5z" />
<path
fill="black"
d="M7.2 11a7 7 0 0 1 12.3 4.5 1.5 1.5 0 1 0 3 0 10 10 0 0 0-10-10 10 10 0 0 0-7.4 3.3l-.4-2a1.5 1.5 0 1 0-3 .6l1.1 5.9c0 .4.3.7.6 1 .4.2 1 .3 1.4.1l5.7-1a1.5 1.5 0 1 0-.6-3l-2.7.6Z"
/>
</g>
</svg>
</button>
</>
)}
</div>
<div>{props.children}</div>
</div>
</div>
);
}

function objectToData(obj: Record<string, unknown>) {
return {
COLLECTED: Object.fromEntries(
Object.entries(obj).map(([name, value]) => [
name,
{
EDITED: null,
FORCED: null,
INPUTTED: null,
PREVIOUS: null,
COLLECTED: value,
},
])
),
};
}
22 changes: 22 additions & 0 deletions src/stories/behaviour/reprise/reprise.stories.tsx
Original file line number Diff line number Diff line change
@@ -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: () => <div>Hello</div>,
};
90 changes: 90 additions & 0 deletions src/stories/behaviour/reprise/source.json
Original file line number Diff line number Diff line change
@@ -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": []
}
}
]
}
1 change: 1 addition & 0 deletions src/use-lunatic/use-lunatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down