Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRAFT / POC : Mode reprise #1047

Open
wants to merge 1 commit into
base: 3.0
Choose a base branch
from
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

Check warning on line 90 in src/stories/behaviour/reprise/RepriseOrchestrator.tsx

View workflow job for this annotation

GitHub Actions / test_lint

'_' is assigned a value but never used

Check warning on line 90 in src/stories/behaviour/reprise/RepriseOrchestrator.tsx

View workflow job for this annotation

GitHub Actions / test_lint

'_' is assigned a value but never used
useEffect(() => {
store.setCollected(data);
setState((v) => v + 1);
}, []);

Check warning on line 94 in src/stories/behaviour/reprise/RepriseOrchestrator.tsx

View workflow job for this annotation

GitHub Actions / test_lint

React Hook useEffect has a missing dependency: 'data'. Either include it or remove the dependency array

Check warning on line 94 in src/stories/behaviour/reprise/RepriseOrchestrator.tsx

View workflow job for this annotation

GitHub Actions / test_lint

React Hook useEffect has a missing dependency: 'data'. Either include it or remove the dependency array
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 @@
getData,
getChangedData,
resetChangedData,
variables: state.variables, // L'orchestrateur de reprise pourra avoir besoin de voir une valeur dans le store

Check failure on line 195 in src/use-lunatic/use-lunatic.ts

View workflow job for this annotation

GitHub Actions / Test with Node v18 on ubuntu-latest

Object literal may only specify known properties, and 'variables' does not exist in type 'LunaticState'.

Check failure on line 195 in src/use-lunatic/use-lunatic.ts

View workflow job for this annotation

GitHub Actions / Test with Node v20 on ubuntu-latest

Object literal may only specify known properties, and 'variables' does not exist in type 'LunaticState'.

Check failure on line 195 in src/use-lunatic/use-lunatic.ts

View workflow job for this annotation

GitHub Actions / Test with Node v18 on ubuntu-latest

Object literal may only specify known properties, and 'variables' does not exist in type 'LunaticState'.

Check failure on line 195 in src/use-lunatic/use-lunatic.ts

View workflow job for this annotation

GitHub Actions / Test with Node v20 on ubuntu-latest

Object literal may only specify known properties, and 'variables' does not exist in type 'LunaticState'.
hasPageResponse: usePageHasResponse(components, state.executeExpression),
// Components
Provider,
Expand Down
Loading