@inseefr/lunatic 3.5.7 → 3.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/components/shared/Combobox/Combobox.stories.d.ts +3 -2
  2. package/components/shared/Combobox/Combobox.stories.js +26 -21
  3. package/components/shared/Combobox/Combobox.stories.js.map +1 -1
  4. package/esm/components/shared/Combobox/Combobox.stories.d.ts +3 -2
  5. package/esm/components/shared/Combobox/Combobox.stories.js +28 -24
  6. package/esm/components/shared/Combobox/Combobox.stories.js.map +1 -1
  7. package/esm/hooks/useLocalStorage.d.ts +1 -0
  8. package/esm/hooks/useLocalStorage.js +31 -0
  9. package/esm/hooks/useLocalStorage.js.map +1 -0
  10. package/esm/type.source.d.ts +6 -0
  11. package/esm/use-lunatic/commons/compile-controls.js +22 -14
  12. package/esm/use-lunatic/commons/compile-controls.js.map +1 -1
  13. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.js +4 -1
  14. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.js.map +1 -1
  15. package/esm/use-lunatic/hooks/use-page-has-response.js +1 -1
  16. package/esm/use-lunatic/hooks/use-page-has-response.js.map +1 -1
  17. package/esm/use-lunatic/hooks/useOverview.spec.js +2 -2
  18. package/esm/use-lunatic/hooks/useOverview.spec.js.map +1 -1
  19. package/esm/utils/number.d.ts +4 -0
  20. package/esm/utils/number.js +12 -0
  21. package/esm/utils/number.js.map +1 -1
  22. package/esm/utils/object.d.ts +10 -0
  23. package/esm/utils/object.js +26 -0
  24. package/esm/utils/object.js.map +1 -1
  25. package/hooks/useLocalStorage.d.ts +1 -0
  26. package/hooks/useLocalStorage.js +34 -0
  27. package/hooks/useLocalStorage.js.map +1 -0
  28. package/package.json +76 -137
  29. package/src/components/shared/Combobox/Combobox.stories.tsx +48 -49
  30. package/src/hooks/useLocalStorage.ts +37 -0
  31. package/src/json.d.ts +16 -0
  32. package/src/stories/accordion/accordion.stories.tsx +21 -0
  33. package/src/stories/behaviour/cleaning/cleaning.stories.tsx +40 -0
  34. package/src/stories/behaviour/controls/controls.stories.tsx +51 -0
  35. package/src/stories/behaviour/disabled/disabled.stories.tsx +29 -0
  36. package/src/stories/behaviour/filter/filter.stories.tsx +34 -0
  37. package/src/stories/behaviour/missing/missing.stories.tsx +31 -0
  38. package/src/stories/behaviour/overview/overview.stories.tsx +61 -0
  39. package/src/stories/{overview/sourceWithHierarchy.json → behaviour/overview/source.json} +1 -1
  40. package/src/stories/{overview → behaviour/overview}/sourceLoop.json +1 -1
  41. package/src/stories/behaviour/performance/performance.stories.tsx +29 -0
  42. package/src/stories/behaviour/resizing/resizing.stories.tsx +34 -0
  43. package/src/stories/behaviour/slots.stories.tsx +34 -0
  44. package/src/stories/checkbox/checkbox.stories.tsx +75 -0
  45. package/src/stories/{checkbox-group/sourceLoop.json → checkbox/sourceGroupLoop.json} +1 -1
  46. package/src/stories/datepicker/datepicker.stories.tsx +21 -0
  47. package/src/stories/declaration/declaration.stories.tsx +27 -0
  48. package/src/stories/dropdown/dropdown.stories.tsx +27 -0
  49. package/src/stories/duration/duration.stories.tsx +27 -0
  50. package/src/stories/filter-description/filter-description.stories.tsx +28 -0
  51. package/src/stories/input/input.stories.tsx +21 -0
  52. package/src/stories/input-number/input-number.stories.tsx +35 -0
  53. package/src/stories/loop/loop.stories.tsx +35 -0
  54. package/src/stories/loop/roster-for-loop.stories.tsx +59 -0
  55. package/src/stories/pairwise/pairwise.stories.tsx +30 -0
  56. package/src/stories/paste.stories.tsx +85 -0
  57. package/src/stories/question/question.stories.tsx +21 -0
  58. package/src/stories/questionnaires/logement/logement.stories.tsx +26 -0
  59. package/src/stories/questionnaires/recensement/recensement.stories.tsx +28 -0
  60. package/src/stories/questionnaires/rp/rp.stories.tsx +21 -0
  61. package/src/stories/questionnaires/simpsons/simpsons.stories.tsx +31 -0
  62. package/src/stories/radio/radio.stories.tsx +53 -0
  63. package/src/stories/roundabout/roundabout.stories.tsx +37 -0
  64. package/src/stories/roundabout/source.json +1 -0
  65. package/src/stories/sequence/sequence.stories.tsx +28 -0
  66. package/src/stories/suggester/source-option-responses.json +1 -1
  67. package/src/stories/suggester/suggester.stories.tsx +68 -0
  68. package/src/stories/summary/source.json +22 -1
  69. package/src/stories/summary/summary.stories.tsx +31 -0
  70. package/src/stories/table/table.stories.tsx +35 -0
  71. package/src/stories/text/text.stories.tsx +38 -0
  72. package/src/stories/textarea/textarea.stories.tsx +21 -0
  73. package/src/stories/utils/Orchestrator.tsx +310 -0
  74. package/src/stories/utils/OrchestratorData.tsx +176 -0
  75. package/src/stories/utils/OrchestratorOverview.tsx +70 -0
  76. package/src/stories/utils/OrchestratorSidebar.tsx +119 -0
  77. package/src/stories/utils/SchemaValidator.tsx +29 -0
  78. package/src/stories/utils/referentiel.ts +9 -0
  79. package/src/type.source.ts +6 -0
  80. package/src/use-lunatic/commons/compile-controls.ts +36 -18
  81. package/src/use-lunatic/commons/fill-components/fill-component-expressions.ts +4 -1
  82. package/src/use-lunatic/hooks/use-page-has-response.ts +1 -1
  83. package/src/use-lunatic/hooks/useOverview.spec.ts +3 -2
  84. package/src/use-lunatic/use-lunatic.test.ts +53 -40
  85. package/src/utils/number.ts +13 -0
  86. package/src/utils/object.ts +40 -0
  87. package/tsconfig.build.tsbuildinfo +1 -1
  88. package/type.source.d.ts +6 -0
  89. package/use-lunatic/commons/compile-controls.js +21 -13
  90. package/use-lunatic/commons/compile-controls.js.map +1 -1
  91. package/use-lunatic/commons/fill-components/fill-component-expressions.js +5 -2
  92. package/use-lunatic/commons/fill-components/fill-component-expressions.js.map +1 -1
  93. package/use-lunatic/hooks/use-page-has-response.js +2 -2
  94. package/use-lunatic/hooks/use-page-has-response.js.map +1 -1
  95. package/use-lunatic/hooks/useOverview.spec.js +8 -8
  96. package/use-lunatic/hooks/useOverview.spec.js.map +1 -1
  97. package/utils/number.d.ts +4 -0
  98. package/utils/number.js +13 -0
  99. package/utils/number.js.map +1 -1
  100. package/utils/object.d.ts +10 -0
  101. package/utils/object.js +30 -0
  102. package/utils/object.js.map +1 -1
  103. package/esm/stories/overview/sourceWithHierarchy.json +0 -5151
  104. package/esm/tests/utils/lunatic.d.ts +0 -15
  105. package/esm/tests/utils/lunatic.js +0 -27
  106. package/esm/tests/utils/lunatic.js.map +0 -1
  107. package/esm/use-lunatic/replace-component-sequence.d.ts +0 -36
  108. package/esm/use-lunatic/replace-component-sequence.js +0 -19
  109. package/esm/use-lunatic/replace-component-sequence.js.map +0 -1
  110. package/esm/use-lunatic/test.utils.d.ts +0 -2
  111. package/esm/use-lunatic/test.utils.js +0 -13
  112. package/esm/use-lunatic/test.utils.js.map +0 -1
  113. package/esm/utils/is-object.d.ts +0 -4
  114. package/esm/utils/is-object.js +0 -7
  115. package/esm/utils/is-object.js.map +0 -1
  116. package/esm/utils/to-number.d.ts +0 -4
  117. package/esm/utils/to-number.js +0 -13
  118. package/esm/utils/to-number.js.map +0 -1
  119. package/src/stories/accordion/accordion.stories.jsx +0 -17
  120. package/src/stories/behaviour/cleaning/cleaning.stories.jsx +0 -69
  121. package/src/stories/behaviour/controls/controls.stories.jsx +0 -81
  122. package/src/stories/behaviour/filter/dataLoop.json +0 -14
  123. package/src/stories/behaviour/filter/filter.stories.jsx +0 -36
  124. package/src/stories/behaviour/missing/missing.stories.jsx +0 -69
  125. package/src/stories/behaviour/others/V2_DeclarationsSimples.json +0 -908
  126. package/src/stories/behaviour/others/V2_MinMaxSum_Boucles.json +0 -489
  127. package/src/stories/behaviour/others/V2_QuestSimple_Boucles.json +0 -3919
  128. package/src/stories/behaviour/others/V2_TCMRallyeGames.json +0 -2760
  129. package/src/stories/behaviour/others/test-dylan.json +0 -538
  130. package/src/stories/behaviour/others/test.stories.jsx +0 -78
  131. package/src/stories/behaviour/paste/source.json +0 -32
  132. package/src/stories/behaviour/paste/test.stories.jsx +0 -62
  133. package/src/stories/behaviour/performance/performance.stories.jsx +0 -26
  134. package/src/stories/behaviour/resizing/resizing.stories.jsx +0 -60
  135. package/src/stories/behaviour/slots.stories.jsx +0 -32
  136. package/src/stories/checkbox-boolean/checkboxBoolean.stories.jsx +0 -17
  137. package/src/stories/checkbox-group/checkbox-group.stories.jsx +0 -77
  138. package/src/stories/checkbox-one/checkboxOne.stories.jsx +0 -53
  139. package/src/stories/date-picker/data.json +0 -3
  140. package/src/stories/date-picker/datepicker.stories.jsx +0 -26
  141. package/src/stories/declaration/data.json +0 -1
  142. package/src/stories/declaration/input.stories.jsx +0 -18
  143. package/src/stories/disabled/data.json +0 -16
  144. package/src/stories/disabled/disabled.stories.jsx +0 -18
  145. package/src/stories/dropdown/data.json +0 -8
  146. package/src/stories/dropdown/dropdown.stories.jsx +0 -25
  147. package/src/stories/duration/duration.stories.jsx +0 -25
  148. package/src/stories/filter-description/filter-description.stories.jsx +0 -37
  149. package/src/stories/input/data.json +0 -1
  150. package/src/stories/input/input.stories.jsx +0 -18
  151. package/src/stories/input-number/input-number.stories.jsx +0 -23
  152. package/src/stories/loop/loop.stories.jsx +0 -29
  153. package/src/stories/loop/roster-for-loop.stories.jsx +0 -46
  154. package/src/stories/markdown/markdown.stories.jsx +0 -20
  155. package/src/stories/overview/data.json +0 -1
  156. package/src/stories/overview/dataLoop.json +0 -93
  157. package/src/stories/overview/overview.stories.jsx +0 -44
  158. package/src/stories/overview/source.json +0 -25
  159. package/src/stories/pairwise/data.json +0 -12
  160. package/src/stories/pairwise/pairwise-links.stories.jsx +0 -48
  161. package/src/stories/question/question.stories.jsx +0 -16
  162. package/src/stories/questionnaires/logement/logement.stories.jsx +0 -59
  163. package/src/stories/questionnaires/recensement/data.json +0 -12
  164. package/src/stories/questionnaires/recensement/recensement.stories.jsx +0 -35
  165. package/src/stories/questionnaires/rp/data.json +0 -5
  166. package/src/stories/questionnaires/rp/rp.stories.jsx +0 -23
  167. package/src/stories/questionnaires/simpsons/simpsons.stories.jsx +0 -246
  168. package/src/stories/radio/radio.stories.jsx +0 -78
  169. package/src/stories/roundabout/data1.json +0 -13
  170. package/src/stories/roundabout/data2.json +0 -16
  171. package/src/stories/roundabout/roundabout.stories.jsx +0 -32
  172. package/src/stories/sequence/sequence.stories.jsx +0 -29
  173. package/src/stories/suggester/suggester.stories.jsx +0 -71
  174. package/src/stories/summary/data.json +0 -16
  175. package/src/stories/summary/summary.stories.jsx +0 -23
  176. package/src/stories/switch/README.md +0 -29
  177. package/src/stories/switch/data-forced.json +0 -40
  178. package/src/stories/switch/source.json +0 -64
  179. package/src/stories/switch/switch.stories.jsx +0 -17
  180. package/src/stories/table/data.json +0 -1
  181. package/src/stories/table/table.stories.jsx +0 -30
  182. package/src/stories/text/data-roster.json +0 -5
  183. package/src/stories/text/text.stories.jsx +0 -20
  184. package/src/stories/textarea/data.json +0 -1
  185. package/src/stories/textarea/textarea.stories.jsx +0 -18
  186. package/src/stories/utils/SchemaValidator.jsx +0 -40
  187. package/src/stories/utils/default-arg-types.js +0 -39
  188. package/src/stories/utils/default-args.js +0 -3
  189. package/src/stories/utils/options.js +0 -19
  190. package/src/stories/utils/orchestrator.jsx +0 -267
  191. package/src/stories/utils/orchestrator.scss +0 -66
  192. package/src/stories/utils/overview.jsx +0 -39
  193. package/src/stories/utils/overview.scss +0 -37
  194. package/src/stories/utils/referentiel.js +0 -7
  195. package/src/tests/utils/e2e.js +0 -91
  196. package/src/tests/utils/lunatic.ts +0 -33
  197. package/src/use-lunatic/replace-component-sequence.ts +0 -25
  198. package/src/use-lunatic/test.utils.ts +0 -17
  199. package/src/utils/is-object.ts +0 -6
  200. package/src/utils/to-number.ts +0 -12
  201. package/stories/overview/sourceWithHierarchy.json +0 -5151
  202. package/tests/utils/lunatic.d.ts +0 -15
  203. package/tests/utils/lunatic.js +0 -31
  204. package/tests/utils/lunatic.js.map +0 -1
  205. package/use-lunatic/replace-component-sequence.d.ts +0 -36
  206. package/use-lunatic/replace-component-sequence.js +0 -22
  207. package/use-lunatic/replace-component-sequence.js.map +0 -1
  208. package/use-lunatic/test.utils.d.ts +0 -2
  209. package/use-lunatic/test.utils.js +0 -17
  210. package/use-lunatic/test.utils.js.map +0 -1
  211. package/utils/is-object.d.ts +0 -4
  212. package/utils/is-object.js +0 -10
  213. package/utils/is-object.js.map +0 -1
  214. package/utils/to-number.d.ts +0 -4
  215. package/utils/to-number.js +0 -16
  216. package/utils/to-number.js.map +0 -1
  217. /package/src/stories/behaviour/cleaning/{loop.json → source-loop-scopes.json} +0 -0
  218. /package/src/stories/behaviour/controls/{boucles-n.json → source-boucles-n.json} +0 -0
  219. /package/src/stories/behaviour/controls/{loop.json → source-loop.json} +0 -0
  220. /package/src/stories/behaviour/controls/{roundabout.json → source-roundabout.json} +0 -0
  221. /package/src/stories/behaviour/controls/{simple-numeric.json → source-simple-numeric.json} +0 -0
  222. /package/src/stories/behaviour/controls/{simple.json → source-simple.json} +0 -0
  223. /package/src/stories/{disabled → behaviour/disabled}/source.json +0 -0
  224. /package/src/stories/{checkbox-boolean → checkbox}/source.json +0 -0
  225. /package/src/stories/{checkbox-group/source.json → checkbox/sourceGroup.json} +0 -0
  226. /package/src/stories/{checkbox-group/sourceCondition.json → checkbox/sourceGroupCondition.json} +0 -0
  227. /package/src/stories/{checkbox-group/sourceDetail.json → checkbox/sourceGroupDetail.json} +0 -0
  228. /package/src/stories/{checkbox-one/source.json → checkbox/sourceOne.json} +0 -0
  229. /package/src/stories/{checkbox-one/sourceDetail.json → checkbox/sourceOneDetail.json} +0 -0
  230. /package/src/stories/{date-picker → datepicker}/source.json +0 -0
  231. /package/src/stories/{markdown/source.json → declaration/sourceMarkdown.json} +0 -0
  232. /package/src/stories/duration/{mois.json → sourceMonths.json} +0 -0
  233. /package/src/stories/duration/{time.json → sourceTime.json} +0 -0
  234. /package/src/stories/filter-description/{source-options.json → sourceOptions.json} +0 -0
  235. /package/src/stories/table/{table-dynamique.json → source-dynamic.json} +0 -0
@@ -0,0 +1,310 @@
1
+ import {
2
+ LunaticComponents,
3
+ type LunaticData,
4
+ type LunaticError,
5
+ type LunaticSlotComponents,
6
+ type LunaticSource,
7
+ ModalControls,
8
+ useLunatic,
9
+ } from '../..';
10
+ import React, {
11
+ memo,
12
+ type ReactNode,
13
+ useCallback,
14
+ useEffect,
15
+ useMemo,
16
+ useState,
17
+ } from 'react';
18
+ import Ajv from 'ajv/dist/2020.js';
19
+ import LunaticSchema from '../../../lunatic-schema.json';
20
+ import { Logger } from '../../utils/logger';
21
+ import { OrchestratorOverview } from './OrchestratorOverview';
22
+ import { SchemaValidator } from './SchemaValidator';
23
+ import { OrchestratorData } from './OrchestratorData';
24
+ import type { PageTag } from '../../use-lunatic/type';
25
+ import type { IndexEntry } from '../../utils/search/SearchInterface';
26
+ import { OrchestratorSidebar } from './OrchestratorSidebar';
27
+ import type { Meta, StoryObj } from '@storybook/react';
28
+
29
+ type Props = {
30
+ source: LunaticSource;
31
+ data: LunaticData;
32
+ // Starting page
33
+ initialPage?: PageTag;
34
+ // Function used to load a list of option in a suggester
35
+ getReferentiel: (name: string) => Promise<IndexEntry[]>;
36
+ // Display detail input even if the corresponding checkbox is not checked
37
+ detailAlwaysDisplayed?: boolean;
38
+ // Enable missing buttons
39
+ missing?: boolean;
40
+ // Readonly mode
41
+ readOnly: boolean;
42
+ // List of custom components
43
+ slots: Partial<LunaticSlotComponents>;
44
+ // Last reached paged (used in the overview)
45
+ lastReachedPage: PageTag;
46
+ // Preload suggester data at the start of the form
47
+ autoSuggesterLoading: boolean;
48
+ disableFiltersDescription?: boolean;
49
+ disableFilters?: boolean;
50
+ management?: boolean;
51
+ shortcut?: boolean;
52
+ activeControls?: boolean;
53
+ missingStrategy?: () => void;
54
+ showOverview?: boolean;
55
+ disabled?: boolean;
56
+ extraTabs?: TabEntry[];
57
+ };
58
+
59
+ type TabEntry = { label: ReactNode; children: ReactNode };
60
+
61
+ function OrchestratorForStories(props: Readonly<Props>) {
62
+ const {
63
+ source,
64
+ data,
65
+ disableFilters,
66
+ disableFiltersDescription,
67
+ management,
68
+ shortcut,
69
+ activeControls,
70
+ initialPage,
71
+ missing,
72
+ missingStrategy,
73
+ slots,
74
+ showOverview,
75
+ getReferentiel,
76
+ readOnly,
77
+ disabled,
78
+ detailAlwaysDisplayed,
79
+ autoSuggesterLoading,
80
+ extraTabs = [],
81
+ } = props;
82
+
83
+ const componentsOptions = { detailAlwaysDisplayed };
84
+
85
+ const {
86
+ getComponents,
87
+ goPreviousPage,
88
+ goNextPage,
89
+ goToPage,
90
+ pager,
91
+ pageTag,
92
+ isFirstPage,
93
+ isLastPage,
94
+ overview,
95
+ compileControls,
96
+ getData,
97
+ Provider,
98
+ hasPageResponse,
99
+ } = useLunatic(source, data, {
100
+ initialPage,
101
+ disableFilters,
102
+ disableFiltersDescription,
103
+ onChange: onLogChange,
104
+ getReferentiel,
105
+ management,
106
+ missing,
107
+ missingStrategy,
108
+ lastReachedPage: props.lastReachedPage,
109
+ missingShortcut: { dontKnow: 'f2', refused: 'f4' },
110
+ shortcut,
111
+ activeControls,
112
+ withOverview: showOverview,
113
+ autoSuggesterLoading,
114
+ componentsOptions,
115
+ });
116
+
117
+ const components = getComponents();
118
+
119
+ const [errorActive, setErrorActive] = useState<
120
+ Record<PageTag, LunaticError[]>
121
+ >({});
122
+ const [errorsForModal, setErrorsForModal] = useState<null | ReturnType<
123
+ typeof compileControls
124
+ >>(null);
125
+
126
+ const skip = useCallback(() => {
127
+ setErrorsForModal(null);
128
+ goNextPage();
129
+ }, [goNextPage]);
130
+
131
+ const closeModal = useCallback(() => setErrorsForModal(null), []);
132
+
133
+ const handleGoNext = useCallback(() => {
134
+ const { currentErrors, isCritical } = compileControls();
135
+ setErrorActive((v) => ({ ...v, [pageTag]: currentErrors || {} }));
136
+ if (currentErrors && Object.keys(currentErrors).length > 0) {
137
+ setErrorsForModal({ currentErrors, isCritical });
138
+ } else {
139
+ goNextPage();
140
+ }
141
+ }, [compileControls, goNextPage, pageTag]);
142
+
143
+ // Allow PageDown / PageUp shortcut to ease navigation
144
+ useEffect(() => {
145
+ const listener = (e: KeyboardEvent) => {
146
+ let stopPropagation = false;
147
+ if (e.key === 'PageDown') {
148
+ handleGoNext();
149
+ stopPropagation = true;
150
+ }
151
+ if (e.key === 'PageUp') {
152
+ goPreviousPage();
153
+ stopPropagation = true;
154
+ }
155
+ if (stopPropagation) {
156
+ e.preventDefault();
157
+ e.stopPropagation();
158
+ }
159
+ };
160
+ document.addEventListener('keydown', listener);
161
+ return () => {
162
+ document.removeEventListener('keydown', listener);
163
+ };
164
+ }, [handleGoNext, goPreviousPage]);
165
+
166
+ // Check that the source is valid against the schema
167
+ const errors = useMemo(() => {
168
+ const ajv = new Ajv({
169
+ removeAdditional: true,
170
+ strict: false,
171
+ allErrors: true,
172
+ discriminator: true,
173
+ });
174
+ const validator = ajv.compile(LunaticSchema);
175
+ const isSourceValid = validator(structuredClone(source)); // ajv mutate the object, send a clone
176
+ if (!isSourceValid) {
177
+ return validator.errors;
178
+ }
179
+ return [];
180
+ }, [source]);
181
+
182
+ const [tab, setTab] = useState(0);
183
+
184
+ const tabs = [
185
+ {
186
+ label: 'Form',
187
+ children: (
188
+ <LunaticComponents
189
+ slots={slots}
190
+ autoFocusKey={pageTag}
191
+ components={components}
192
+ componentProps={(p) => ({
193
+ errors: errorActive[pageTag],
194
+ disabled: disabled,
195
+ readOnly: 'readOnly' in p ? p.readOnly : readOnly,
196
+ })}
197
+ />
198
+ ),
199
+ },
200
+ {
201
+ label: 'Data',
202
+ children: <OrchestratorData getData={getData} source={source} />,
203
+ },
204
+ ...extraTabs,
205
+ ] as TabEntry[];
206
+
207
+ if (errors && errors.length > 0) {
208
+ tabs.push({
209
+ label: (
210
+ <>
211
+ Errors{' '}
212
+ <span className="badge badge-xs badge-error text-white">
213
+ {errors.length}
214
+ </span>
215
+ </>
216
+ ),
217
+ children: <SchemaValidator errors={errors} />,
218
+ });
219
+ }
220
+
221
+ return (
222
+ <Provider>
223
+ <div className="container grid grid-cols-[1fr_300px] gap-4">
224
+ <div>
225
+ <div tabIndex={-1} role="tablist" className="tabs tabs-box mb-4">
226
+ {tabs.map((t, k) => (
227
+ <button
228
+ tabIndex={-1}
229
+ key={JSON.stringify(t.label)}
230
+ role="tab"
231
+ onClick={() => setTab(k)}
232
+ className={`tab gap-2 ${tab === k ? 'tab-active' : ''}`}
233
+ >
234
+ {t.label}
235
+ </button>
236
+ ))}
237
+ </div>
238
+ <div>{tabs[tab].children}</div>
239
+ </div>
240
+ <OrchestratorSidebar
241
+ goPreviousPage={goPreviousPage}
242
+ goNextPage={handleGoNext}
243
+ goToPage={goToPage}
244
+ isLastPage={isLastPage}
245
+ isFirstPage={isFirstPage}
246
+ pageTag={pageTag}
247
+ pager={pager}
248
+ hasPageResponse={hasPageResponse()}
249
+ onLogData={() => console.log('Data', getData(true))}
250
+ onLogComponents={() => console.log('Components', components)}
251
+ >
252
+ {showOverview && (
253
+ <OrchestratorOverview overview={overview} goToPage={goToPage} />
254
+ )}
255
+ </OrchestratorSidebar>
256
+ {errorsForModal?.currentErrors && (
257
+ <ModalControls
258
+ errors={errorsForModal.currentErrors}
259
+ goNext={skip}
260
+ onClose={closeModal}
261
+ isCritical={errorsForModal.isCritical}
262
+ />
263
+ )}
264
+ </div>
265
+ </Provider>
266
+ );
267
+ }
268
+
269
+ function onLogChange(args: unknown) {
270
+ Logger.log('onChange', args);
271
+ }
272
+
273
+ function logMissingStrategy() {
274
+ Logger.log('no missing strategy');
275
+ }
276
+
277
+ export const Orchestrator = memo(OrchestratorForStories);
278
+
279
+ export const OrchestratorMeta: Meta<typeof Orchestrator> = {
280
+ component: Orchestrator,
281
+ argTypes: {
282
+ // Do not display controls for these args
283
+ source: { table: { disable: true } },
284
+ data: { table: { disable: true } },
285
+ getReferentiel: { table: { disable: true } },
286
+ initialPage: { table: { disable: true } },
287
+ slots: { table: { disable: true } },
288
+ lastReachedPage: { table: { disable: true } },
289
+ autoSuggesterLoading: { table: { disable: true } },
290
+ missingStrategy: { table: { disable: true } },
291
+ extraTabs: { table: { disable: true } },
292
+ },
293
+ args: {
294
+ disableFilters: false,
295
+ disableFiltersDescription: false,
296
+ management: false,
297
+ shortcut: false,
298
+ activeControls: true,
299
+ initialPage: '1',
300
+ missing: false,
301
+ missingStrategy: logMissingStrategy,
302
+ showOverview: false,
303
+ readOnly: false,
304
+ disabled: false,
305
+ detailAlwaysDisplayed: false,
306
+ autoSuggesterLoading: false,
307
+ },
308
+ };
309
+
310
+ export type OrchestratorStory = StoryObj<typeof Orchestrator>;
@@ -0,0 +1,176 @@
1
+ import { useMemo, useState } from 'react';
2
+ import type { LunaticSource, LunaticState } from '../../use-lunatic/type';
3
+ import { isObject, objectFilter, objectKeys } from '../../utils/object';
4
+
5
+ type Props = {
6
+ getData: LunaticState['getData'];
7
+ source: LunaticSource;
8
+ };
9
+
10
+ export function OrchestratorData({ getData, source }: Readonly<Props>) {
11
+ const data = useMemo(() => getData(true), [getData]);
12
+ const [search, setSearch] = useState('');
13
+ const [tab, setTab] = useState(0);
14
+
15
+ const tabs = [
16
+ {
17
+ label: 'Collected',
18
+ variables: objectFilter(data.COLLECTED ?? {}, (k) =>
19
+ search ? k.toLowerCase().includes(search.toLowerCase()) : true
20
+ ),
21
+ },
22
+ {
23
+ label: 'Calculated',
24
+ variables: objectFilter(data.CALCULATED ?? {}, (k) =>
25
+ search ? k.startsWith(search) : true
26
+ ),
27
+ },
28
+ ];
29
+
30
+ const variables = tabs[tab].variables;
31
+ return (
32
+ <div>
33
+ <div className="flex justify-between">
34
+ <div role="tablist" className="tabs tabs-border mb-4">
35
+ {tabs.map((t, k) => (
36
+ <button
37
+ key={`variables-${t.label}`}
38
+ role="tab"
39
+ onClick={() => setTab(k)}
40
+ className={`tab gap-2 ${tab === k ? 'tab-active' : ''}`}
41
+ >
42
+ {t.label}
43
+ </button>
44
+ ))}
45
+ </div>
46
+ <input
47
+ type="text"
48
+ className="input"
49
+ placeholder="Rechercher"
50
+ onInput={(e) => setSearch(e.currentTarget.value)}
51
+ value={search}
52
+ />
53
+ </div>
54
+ <div className="overflow-x-auto rounded-box border border-base-content/5 bg-base-100">
55
+ <table className="table table-zebra">
56
+ <thead>
57
+ <tr>
58
+ <th>Variable</th>
59
+ <th>Infos</th>
60
+ <th>Value</th>
61
+ </tr>
62
+ </thead>
63
+ <tbody>
64
+ {Object.entries(variables).map(([key, value]) => (
65
+ <tr key={key}>
66
+ <td>{key}</td>
67
+ <td>
68
+ <div className="flex gap-2 flex-wrap">
69
+ {source.resizing &&
70
+ key in source.resizing &&
71
+ 'variables' in source.resizing[key] ? (
72
+ <ResizeInfo resizing={source.resizing[key]} />
73
+ ) : null}
74
+ {source.cleaning && key in source.cleaning ? (
75
+ <CleaningInfo cleaning={source.cleaning[key]} />
76
+ ) : null}
77
+ </div>
78
+ </td>
79
+ <td>
80
+ {JSON.stringify(
81
+ isObject(value) && 'COLLECTED' in value
82
+ ? value.COLLECTED
83
+ : value
84
+ )}
85
+ </td>
86
+ </tr>
87
+ ))}
88
+ </tbody>
89
+ </table>
90
+ </div>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ const ResizeInfo = ({
96
+ resizing,
97
+ }: {
98
+ resizing: {
99
+ size: string;
100
+ variables: string[];
101
+ };
102
+ }) => {
103
+ const [expanded, setExpanded] = useState(false);
104
+ const toggle = () => setExpanded((v) => !v);
105
+ return (
106
+ <div className="space-y-2">
107
+ <button
108
+ className="flex gap-1 btn btn-primary rounded-full badge-sm whitespace-nowrap"
109
+ onClick={toggle}
110
+ >
111
+ {resizing.variables.length} resize(s)
112
+ <svg
113
+ className="size-4"
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ viewBox="0 0 24 24"
116
+ fill="currentColor"
117
+ >
118
+ <path d="M12 15.0006L7.75732 10.758L9.17154 9.34375L12 12.1722L14.8284 9.34375L16.2426 10.758L12 15.0006Z"></path>
119
+ </svg>
120
+ </button>
121
+ {expanded && (
122
+ <div className="flex flex-wrap gap-2">
123
+ {resizing.variables.map((variable) => (
124
+ <div key={variable} className="kbd">
125
+ {variable}
126
+ </div>
127
+ ))}
128
+ </div>
129
+ )}
130
+ </div>
131
+ );
132
+ };
133
+
134
+ const CleaningInfo = ({
135
+ cleaning,
136
+ }: {
137
+ cleaning: {
138
+ [p: string]:
139
+ | string
140
+ | {
141
+ expression: string;
142
+ shapeFrom?: string;
143
+ isAggregatorUsed: boolean;
144
+ }[];
145
+ };
146
+ }) => {
147
+ const [expanded, setExpanded] = useState(false);
148
+ const toggle = () => setExpanded((v) => !v);
149
+ return (
150
+ <div className="space-y-2">
151
+ <button
152
+ className="flex gap-1 btn btn-secondary rounded-full badge-sm whitespace-nowrap"
153
+ onClick={toggle}
154
+ >
155
+ {Object.keys(cleaning).length} cleaning(s)
156
+ <svg
157
+ className="size-4"
158
+ xmlns="http://www.w3.org/2000/svg"
159
+ viewBox="0 0 24 24"
160
+ fill="currentColor"
161
+ >
162
+ <path d="M12 15.0006L7.75732 10.758L9.17154 9.34375L12 12.1722L14.8284 9.34375L16.2426 10.758L12 15.0006Z"></path>
163
+ </svg>
164
+ </button>
165
+ {expanded && (
166
+ <div className="flex flex-wrap gap-2">
167
+ {objectKeys(cleaning).map((k) => (
168
+ <div key={k} className="kbd">
169
+ {k}
170
+ </div>
171
+ ))}
172
+ </div>
173
+ )}
174
+ </div>
175
+ );
176
+ };
@@ -0,0 +1,70 @@
1
+ import type { InterpretedLunaticOverviewItem } from '../../use-lunatic/hooks/useOverview';
2
+ import type { useLunatic } from '../../use-lunatic/use-lunatic';
3
+
4
+ type Props = {
5
+ overview: InterpretedLunaticOverviewItem[];
6
+ goToPage: ReturnType<typeof useLunatic>['goToPage'];
7
+ depth?: number;
8
+ };
9
+
10
+ export const OrchestratorOverview = ({
11
+ overview: stateOverview,
12
+ goToPage,
13
+ depth = 0,
14
+ }: Props) => {
15
+ return (
16
+ <div>
17
+ {depth === 0 && <h3 className="text-lg font-bold mb-2">Overview</h3>}
18
+ <ol style={{ paddingLeft: `${depth * 1.25}rem` }}>
19
+ {stateOverview.map((entry) => (
20
+ <OverviewItem
21
+ key={`view-${entry.id}-${entry.page}`}
22
+ overviewEntry={entry}
23
+ goToPage={goToPage}
24
+ depth={depth}
25
+ />
26
+ ))}
27
+ </ol>
28
+ </div>
29
+ );
30
+ };
31
+
32
+ type OverviewItemProps = {
33
+ overviewEntry: InterpretedLunaticOverviewItem;
34
+ goToPage: Props['goToPage'];
35
+ depth: number;
36
+ };
37
+
38
+ const OverviewItem = ({
39
+ overviewEntry,
40
+ goToPage,
41
+ depth,
42
+ }: OverviewItemProps) => {
43
+ let color = 'text-base-content/50';
44
+ if (overviewEntry.reached) {
45
+ color = 'text-base-content';
46
+ }
47
+ if (overviewEntry.current) {
48
+ color = 'text-success';
49
+ }
50
+ return (
51
+ <li className={`${color}`}>
52
+ <button
53
+ style={{ display: 'contents' }}
54
+ onClick={() => goToPage({ page: overviewEntry.page })}
55
+ >
56
+ <div className="flex">
57
+ <span>{overviewEntry.label}</span>
58
+ <span className="dot-leader">{overviewEntry.page}</span>
59
+ </div>
60
+ </button>
61
+ {overviewEntry.children.length > 0 && (
62
+ <OrchestratorOverview
63
+ overview={overviewEntry.children}
64
+ goToPage={goToPage}
65
+ depth={depth + 1}
66
+ />
67
+ )}
68
+ </li>
69
+ );
70
+ };
@@ -0,0 +1,119 @@
1
+ import type { useLunatic } from '../../use-lunatic/use-lunatic';
2
+ import { objectKeys } from '../../utils/object';
3
+ import type { PropsWithChildren } from 'react';
4
+
5
+ type Props = PropsWithChildren<
6
+ Pick<
7
+ ReturnType<typeof useLunatic>,
8
+ | 'goPreviousPage'
9
+ | 'goNextPage'
10
+ | 'goToPage'
11
+ | 'isLastPage'
12
+ | 'isFirstPage'
13
+ | 'pageTag'
14
+ | 'pager'
15
+ >
16
+ > & {
17
+ hasPageResponse: unknown;
18
+ onLogData: () => void;
19
+ onLogComponents: () => void;
20
+ };
21
+
22
+ export function OrchestratorSidebar({
23
+ goPreviousPage,
24
+ goNextPage,
25
+ goToPage,
26
+ isLastPage,
27
+ isFirstPage,
28
+ pageTag,
29
+ pager,
30
+ children,
31
+ hasPageResponse,
32
+ onLogData,
33
+ onLogComponents,
34
+ }: Props) {
35
+ return (
36
+ <aside className="space-y-4 card card-border border-base-300 p-2">
37
+ <div className="space-y-4">
38
+ {/* Next / Prev button */}
39
+ <div>
40
+ <div className="join mb-2 w-full">
41
+ <button
42
+ className="btn join-item btn-block shrink btn-primary"
43
+ onClick={goPreviousPage}
44
+ disabled={isFirstPage}
45
+ >
46
+ Previous
47
+ </button>
48
+ <button
49
+ className="btn join-item btn-block shrink btn-primary"
50
+ onClick={goNextPage}
51
+ disabled={isLastPage}
52
+ >
53
+ Next
54
+ </button>
55
+ </div>
56
+ <div className="textarea-sm text-base-content/70 text-center">
57
+ You can use PgDown / PgUp shortcut
58
+ </div>
59
+ </div>
60
+
61
+ {/* Reach a specific page */}
62
+ <form
63
+ className="join w-full"
64
+ onSubmit={(e) => {
65
+ e.preventDefault();
66
+ goToPage({
67
+ page: e.currentTarget.querySelector('input')!.valueAsNumber,
68
+ });
69
+ }}
70
+ >
71
+ <label className="input join-item">
72
+ <span className="label">Page</span>
73
+ <input type="number" placeholder="1" />
74
+ </label>
75
+ <button type="submit" className="join-item btn btn-neutral">
76
+ Reach
77
+ </button>
78
+ </form>
79
+
80
+ {/* Pager informations */}
81
+ <div>
82
+ <h3 className="text-lg font-bold mb-4">Pager</h3>
83
+ <ul className="flex flex-col">
84
+ <li className="flex gap-2">
85
+ <span>PageTag</span>
86
+ <span className="dot-leader">{JSON.stringify(pageTag)}</span>
87
+ </li>
88
+ {objectKeys(pager).map((key) => (
89
+ <li className="flex gap-2" key={key}>
90
+ <span>{key}</span>{' '}
91
+ <span className="dot-leader">{JSON.stringify(pager[key])}</span>
92
+ </li>
93
+ ))}
94
+ </ul>
95
+ </div>
96
+ </div>
97
+ <div className="flex gap-2 w-full">
98
+ <button className="btn btn-warning grow" onClick={onLogComponents}>
99
+ Log components
100
+ </button>
101
+ <button className="btn btn-warning grow" onClick={onLogData}>
102
+ Log data
103
+ </button>
104
+ </div>
105
+ <div>
106
+ <h3 className="text-lg font-bold mb-2">Misc</h3>
107
+ <ul className="flex flex-col">
108
+ <li className="flex gap-2">
109
+ <span>pageHasResponse:</span>{' '}
110
+ <span className="dot-leader">
111
+ {JSON.stringify(hasPageResponse)}
112
+ </span>
113
+ </li>
114
+ </ul>
115
+ </div>
116
+ {children}
117
+ </aside>
118
+ );
119
+ }
@@ -0,0 +1,29 @@
1
+ import type { ErrorObject } from 'ajv';
2
+
3
+ type Props = {
4
+ errors: ErrorObject<string, Record<string, any>, unknown>[];
5
+ };
6
+
7
+ export function SchemaValidator({ errors }: Readonly<Props>) {
8
+ if (!errors) {
9
+ return null;
10
+ }
11
+
12
+ return (
13
+ <div className="alert alert-error alert-outline text-xs">
14
+ <div>
15
+ <h4 className="mb-2">
16
+ <strong>{errors.length}</strong> erreurs
17
+ </h4>
18
+ <ul>
19
+ {errors.map((err) => (
20
+ <li key={err.instancePath}>
21
+ <strong>{err.instancePath}</strong> : {err.message}{' '}
22
+ <small>({err.schemaPath})</small>
23
+ </li>
24
+ ))}
25
+ </ul>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { IndexEntry } from '../../utils/search/SearchInterface';
2
+
3
+ export const getReferentiel = async (name: string) => {
4
+ return fetch(`./${name}.json`)
5
+ .then((r) => r.json())
6
+ .catch(() => {
7
+ throw new Error(`Unknown référentiel ${name}`);
8
+ }) as Promise<IndexEntry[]>;
9
+ };