data-primals-engine 1.0.11 → 1.1.0

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 (166) hide show
  1. package/client/README.md +8 -0
  2. package/client/index.html +31 -0
  3. package/client/index.js +25 -0
  4. package/client/package-lock.json +11664 -0
  5. package/client/package.json +70 -0
  6. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  7. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  8. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  9. package/client/public/doc/API.postman_collection.json +214 -0
  10. package/client/public/github.svg +1 -0
  11. package/client/public/profilCompany.jpg +0 -0
  12. package/client/public/profilDeveloper.jpg +0 -0
  13. package/client/public/profilEngineer.jpg +0 -0
  14. package/client/public/profilPersonal.jpg +0 -0
  15. package/client/public/profils .psd +0 -0
  16. package/client/public/react.svg +9 -0
  17. package/client/src/APIInfo.jsx +328 -0
  18. package/client/src/AddWidgetTypeModal.jsx +44 -0
  19. package/client/src/AddWidgetTypeModal.scss +87 -0
  20. package/client/src/App.css +42 -0
  21. package/client/src/App.jsx +586 -0
  22. package/client/src/App.scss +1466 -0
  23. package/client/src/AssistantChat.jsx +280 -0
  24. package/client/src/AssistantChat.scss +229 -0
  25. package/client/src/Button.jsx +12 -0
  26. package/client/src/Button.scss +197 -0
  27. package/client/src/CalculationBuilder.jsx +338 -0
  28. package/client/src/CalculationStepRow.jsx +199 -0
  29. package/client/src/CalendarConfigModal.jsx +49 -0
  30. package/client/src/ChartConfigModal.jsx +402 -0
  31. package/client/src/ConditionBuilder.jsx +622 -0
  32. package/client/src/ConditionBuilder.scss +672 -0
  33. package/client/src/ConditionBuilder2.jsx +757 -0
  34. package/client/src/ContentView.jsx +274 -0
  35. package/client/src/CronBuilder.jsx +126 -0
  36. package/client/src/CronBuilder.scss +128 -0
  37. package/client/src/CronPartBuilder.jsx +150 -0
  38. package/client/src/Dashboard.jsx +210 -0
  39. package/client/src/Dashboard.scss +445 -0
  40. package/client/src/DashboardChart.jsx +362 -0
  41. package/client/src/DashboardFlexViewItem.jsx +129 -0
  42. package/client/src/DashboardView.jsx +554 -0
  43. package/client/src/DataChart.jsx +349 -0
  44. package/client/src/DataEditor.jsx +556 -0
  45. package/client/src/DataLayout.jsx +709 -0
  46. package/client/src/DataTable.jsx +996 -0
  47. package/client/src/DataTable.scss +73 -0
  48. package/client/src/Dialog.jsx +90 -0
  49. package/client/src/Dialog.scss +117 -0
  50. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  51. package/client/src/DocumentationPageLayout.jsx +93 -0
  52. package/client/src/DocumentationPageLayout.scss +262 -0
  53. package/client/src/Draggable.jsx +222 -0
  54. package/client/src/Draggable.scss +49 -0
  55. package/client/src/ExportDialog.jsx +172 -0
  56. package/client/src/Field.jsx +1558 -0
  57. package/client/src/FlexBuilder.jsx +269 -0
  58. package/client/src/FlexBuilder.scss +226 -0
  59. package/client/src/FlexBuilderControls.jsx +170 -0
  60. package/client/src/FlexBuilderModal.jsx +75 -0
  61. package/client/src/FlexBuilderModal.scss +109 -0
  62. package/client/src/FlexBuilderPreview.jsx +48 -0
  63. package/client/src/FlexBuilderUtils.js +84 -0
  64. package/client/src/FlexDataRenderer.jsx +170 -0
  65. package/client/src/FlexDataRenderer.scss +80 -0
  66. package/client/src/FlexNode.jsx +290 -0
  67. package/client/src/FlexTreeUtils.js +129 -0
  68. package/client/src/HiddenableCell.jsx +46 -0
  69. package/client/src/HiddenableCell.scss +36 -0
  70. package/client/src/KPIDialog.jsx +39 -0
  71. package/client/src/KPIWidget.jsx +139 -0
  72. package/client/src/KanbanCard.jsx +92 -0
  73. package/client/src/KanbanColumn.jsx +29 -0
  74. package/client/src/KanbanConfigModal.jsx +115 -0
  75. package/client/src/KanbanView.jsx +113 -0
  76. package/client/src/KanbanView.scss +107 -0
  77. package/client/src/MessageRotator.jsx +77 -0
  78. package/client/src/MessageRotator.scss +14 -0
  79. package/client/src/ModelCreator.jsx +640 -0
  80. package/client/src/ModelCreator.scss +154 -0
  81. package/client/src/ModelCreatorField.jsx +810 -0
  82. package/client/src/ModelImporter.jsx +120 -0
  83. package/client/src/ModelList.jsx +205 -0
  84. package/client/src/Notification.jsx +137 -0
  85. package/client/src/Notification.scss +126 -0
  86. package/client/src/NotificationProvider.jsx +73 -0
  87. package/client/src/PackGallery.jsx +211 -0
  88. package/client/src/PackGallery.scss +156 -0
  89. package/client/src/Pagination.jsx +141 -0
  90. package/client/src/RTE.jsx +415 -0
  91. package/client/src/RTETrans.jsx +103 -0
  92. package/client/src/RelationField.jsx +247 -0
  93. package/client/src/RelationValue.jsx +230 -0
  94. package/client/src/RestoreConfirmationModal.jsx +183 -0
  95. package/client/src/RestoreDialog.jsx +134 -0
  96. package/client/src/RestoreDialog.scss +67 -0
  97. package/client/src/RichTextEditorModal.jsx +34 -0
  98. package/client/src/RichTextEditorModal.scss +54 -0
  99. package/client/src/TourSpotlight.jsx +234 -0
  100. package/client/src/TourSpotlight.scss +100 -0
  101. package/client/src/TutorialsMenu.jsx +83 -0
  102. package/client/src/TutorialsMenu.scss +217 -0
  103. package/client/src/TutorialsRewardModal.jsx +65 -0
  104. package/client/src/Utils.jsx +78 -0
  105. package/client/src/ViewSwitcher.jsx +54 -0
  106. package/client/src/ViewSwitcher.scss +74 -0
  107. package/client/src/Webpage.jsx +69 -0
  108. package/client/src/_variables.scss +21 -0
  109. package/client/src/assets/react.svg +1 -0
  110. package/client/src/constants.js +68 -0
  111. package/client/src/contexts/AuthContext.jsx +28 -0
  112. package/client/src/contexts/ModelContext.jsx +328 -0
  113. package/client/src/contexts/UIContext.jsx +52 -0
  114. package/client/src/core/data.js +35 -0
  115. package/client/src/entry-client.jsx +15 -0
  116. package/client/src/entry-server.jsx +13 -0
  117. package/client/src/filter.js +222 -0
  118. package/client/src/hooks/data.js +101 -0
  119. package/client/src/hooks/useDebounce.js +20 -0
  120. package/client/src/hooks/useDragAndDrop.js +59 -0
  121. package/client/src/hooks/useLocalStorage.js +100 -0
  122. package/client/src/hooks/useMount.js +42 -0
  123. package/client/src/hooks/useTutorials.jsx +227 -0
  124. package/client/src/hooks/useWindowSize.js +24 -0
  125. package/client/src/i18n.js +47 -0
  126. package/client/src/index.css +90 -0
  127. package/client/src/index.js +10 -0
  128. package/client/src/main.jsx +11 -0
  129. package/client/src/translations.js +15940 -0
  130. package/client/src/tutorials.js +112 -0
  131. package/client/vite.config.js +30 -0
  132. package/eslint.config.js +50 -0
  133. package/package.json +48 -27
  134. package/server.js +9 -0
  135. package/src/constants.js +12 -23
  136. package/src/core.js +9 -8
  137. package/src/data.js +81 -80
  138. package/src/defaultModels.js +114 -115
  139. package/src/email.js +8 -9
  140. package/src/engine.js +5 -5
  141. package/src/events.js +1 -2
  142. package/src/i18n.js +212 -211
  143. package/src/middlewares/middleware-mongodb.js +94 -93
  144. package/src/migrate.js +24 -24
  145. package/src/modules/assistant.js +55 -55
  146. package/src/modules/bucket.js +10 -9
  147. package/src/modules/data.js +387 -357
  148. package/src/modules/file.js +3 -6
  149. package/src/modules/mongodb.js +6 -7
  150. package/src/modules/user.js +24 -47
  151. package/src/modules/workflow.js +68 -67
  152. package/src/openai.jobs.js +2 -2
  153. package/src/packs.js +30 -31
  154. package/src/providers.js +101 -3
  155. package/src/setenv.js +1 -1
  156. package/src/user.js +0 -17
  157. package/src/workers/crypto-worker.js +21 -20
  158. package/src/workers/import-export-worker.js +18 -18
  159. package/test/data.backup.integration.test.js +7 -12
  160. package/test/data.integration.test.js +27 -25
  161. package/test/globalSetup.js +2 -2
  162. package/test/import_export.integration.test.js +6 -6
  163. package/test/model.integration.test.js +21 -22
  164. package/test/workflow.integration.test.js +2 -5
  165. package/test/workflow.robustness.test.js +6 -13
  166. package/vitest.config.js +2 -2
@@ -0,0 +1,415 @@
1
+ import 'react'
2
+ import { Color } from '@tiptap/extension-color'
3
+ import ListItem from '@tiptap/extension-list-item'
4
+ import TextStyle from '@tiptap/extension-text-style'
5
+ import HardBreak from '@tiptap/extension-hard-break'
6
+ import CodeBlock from '@tiptap/extension-code-block'
7
+ import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
8
+ import Image from '@tiptap/extension-image'
9
+ import Link from '@tiptap/extension-link'
10
+ import {EditorContent, EditorProvider, ReactNodeViewRenderer, useCurrentEditor, useEditor} from '@tiptap/react'
11
+ import StarterKit from '@tiptap/starter-kit'
12
+ import {
13
+ FaBold,
14
+ FaCode,
15
+ FaHeading,
16
+ FaItalic, FaLine, FaListOl,
17
+ FaListUl,
18
+ FaParagraph, FaQuoteLeft, FaRedo,
19
+ FaRemoveFormat, FaRulerHorizontal,
20
+ FaStrikethrough, FaTrash, FaUndo
21
+ } from "react-icons/fa";
22
+ import React, {useEffect, useState} from "react";
23
+ import {FaFileLines} from "react-icons/fa6";
24
+
25
+ // load all languages with "all" or common languages with "common"
26
+ import { all, createLowlight } from 'lowlight'
27
+ import css from 'highlight.js/lib/languages/css'
28
+ import js from 'highlight.js/lib/languages/javascript'
29
+ import ts from 'highlight.js/lib/languages/typescript'
30
+ import html from 'highlight.js/lib/languages/xml'
31
+ // create a lowlight instance
32
+ const lowlight = createLowlight(all)
33
+
34
+ // you can also register individual languages
35
+ lowlight.register('html', html)
36
+ lowlight.register('css', css)
37
+ lowlight.register('js', js)
38
+ lowlight.register('ts', ts)
39
+
40
+ const code = (c) => {
41
+ if( !c)
42
+ return '';
43
+ const doc = document.createElement('div');
44
+ doc.innerHTML = c;
45
+ doc.querySelectorAll('code').forEach(cc =>{
46
+ cc.innerHTML = cc.textContent
47
+ .replace(/\r\n|\n/g, "<br />")
48
+ .replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;");
49
+ })
50
+ return doc.innerHTML;
51
+ }
52
+
53
+ const MenuBar = ({showSource, setShowSource, editor, field, onChange}) => {
54
+
55
+ const showSourceFunc = () => {
56
+ if( !editor)
57
+ return;
58
+ editor.commands.setContent(`<textarea>${code(editor.getHTML())}</textarea>`)
59
+ onChange?.({name: field.name, value: code(editor.getHTML())});
60
+ }
61
+
62
+ const showHTMLFunc = () => {
63
+ if( !editor)
64
+ return;
65
+ editor.commands.setContent(editor.getText());
66
+ onChange?.({name: field.name, value: code(editor.getHTML())});
67
+ }
68
+
69
+ useEffect(() =>{
70
+ if( showSource )
71
+ showSourceFunc()
72
+ else
73
+ showHTMLFunc();
74
+ }, [showSource])
75
+ return (
76
+ <div className="control-group">
77
+ <div className="flex flex-centered">
78
+ <button
79
+ tabIndex={-1}
80
+ type={"button"}
81
+ onClick={() => editor.chain().focus().toggleBold().run()}
82
+ disabled={
83
+ !editor.can()
84
+ .chain()
85
+ .focus()
86
+ .toggleBold()
87
+ .run()
88
+ }
89
+ className={editor.isActive('bold') ? 'is-active' : ''}
90
+ >
91
+ <FaBold/>
92
+ </button>
93
+ <button
94
+ tabIndex={-1}
95
+ type={"button"}
96
+ onClick={() => editor.chain().focus().toggleItalic().run()}
97
+ disabled={
98
+ !editor.can()
99
+ .chain()
100
+ .focus()
101
+ .toggleItalic()
102
+ .run()
103
+ }
104
+ className={editor.isActive('italic') ? 'is-active' : ''}
105
+ >
106
+ <FaItalic/>
107
+ </button>
108
+ <button
109
+ tabIndex={-1}
110
+ type={"button"}
111
+ onClick={() => editor.chain().focus().toggleStrike().run()}
112
+ disabled={
113
+ !editor.can()
114
+ .chain()
115
+ .focus()
116
+ .toggleStrike()
117
+ .run()
118
+ }
119
+ className={editor.isActive('strike') ? 'is-active' : ''}
120
+ >
121
+ <FaStrikethrough/>
122
+ </button>
123
+ <button
124
+ tabIndex={-1}
125
+ type={"button"}
126
+ onClick={() => editor.chain().focus().toggleCode().run()}
127
+ disabled={
128
+ !editor.can()
129
+ .chain()
130
+ .focus()
131
+ .toggleCode()
132
+ .run()
133
+ }
134
+ className={editor.isActive('code') ? 'is-active' : ''}
135
+ >
136
+ <FaFileLines/>
137
+ </button>
138
+ <button
139
+ tabIndex={-1}
140
+ type={"button"} onClick={() => editor.chain().focus().unsetAllMarks().run()}>
141
+ <FaRemoveFormat/>
142
+ </button>
143
+ <button
144
+ type={"button"}
145
+ tabIndex={-1}
146
+ onClick={() => editor.chain().focus().setParagraph().run()}
147
+ className={editor.isActive('paragraph') ? 'is-active' : ''}
148
+ >
149
+ <FaParagraph/>
150
+ </button>
151
+ <button
152
+ type={"button"}
153
+ tabIndex={-1}
154
+ onClick={() => editor.chain().focus().toggleHeading({level: 1}).run()}
155
+ className={editor.isActive('heading', {level: 1}) ? 'is-active' : ''}
156
+ >
157
+ <FaHeading/>1
158
+ </button>
159
+ <button
160
+ type={"button"}
161
+ tabIndex={-1}
162
+ onClick={() => editor.chain().focus().toggleHeading({level: 2}).run()}
163
+ className={editor.isActive('heading', {level: 2}) ? 'is-active' : ''}
164
+ >
165
+ <FaHeading/>2
166
+ </button>
167
+ <button
168
+ type={"button"}
169
+ tabIndex={-1}
170
+ onClick={() => editor.chain().focus().toggleHeading({level: 3}).run()}
171
+ className={editor.isActive('heading', {level: 3}) ? 'is-active' : ''}
172
+ >
173
+ <FaHeading/>3
174
+ </button>
175
+ <button
176
+ type={"button"}
177
+ tabIndex={-1}
178
+ onClick={() => editor.chain().focus().toggleHeading({level: 4}).run()}
179
+ className={editor.isActive('heading', {level: 4}) ? 'is-active' : ''}
180
+ >
181
+ <FaHeading/>4
182
+ </button>
183
+ <button
184
+ type={"button"}
185
+ tabIndex={-1}
186
+ onClick={() => editor.chain().focus().toggleHeading({level: 5}).run()}
187
+ className={editor.isActive('heading', {level: 5}) ? 'is-active' : ''}
188
+ >
189
+ <FaHeading/>5
190
+ </button>
191
+ <button
192
+ type={"button"}
193
+ tabIndex={-1}
194
+ onClick={() => editor.chain().focus().toggleHeading({level: 6}).run()}
195
+ className={editor.isActive('heading', {level: 6}) ? 'is-active' : ''}
196
+ >
197
+ <FaHeading/>6
198
+ </button>
199
+ <button
200
+ type={"button"}
201
+ tabIndex={-1}
202
+ onClick={() => editor.chain().focus().toggleBulletList().run()}
203
+ className={editor.isActive('bulletList') ? 'is-active' : ''}
204
+ >
205
+ <FaListUl/>
206
+ </button>
207
+ <button
208
+ type={"button"}
209
+ tabIndex={-1}
210
+ onClick={() => editor.chain().focus().toggleOrderedList().run()}
211
+ className={editor.isActive('orderedList') ? 'is-active' : ''}
212
+ >
213
+ <FaListOl/>
214
+ </button>
215
+ <button
216
+ type={"button"}
217
+ tabIndex={-1}
218
+ onClick={() => setShowSource(!showSource)}
219
+ className={showSource ? 'is-active' : ''}
220
+ >
221
+ <FaCode/>
222
+ </button>
223
+ <button
224
+ type={"button"}
225
+ tabIndex={-1}
226
+ onClick={() => editor.chain().focus().toggleBlockquote().run()}
227
+ className={editor.isActive('blockquote') ? 'is-active' : ''}
228
+ >
229
+ <FaQuoteLeft/>
230
+ </button>
231
+ <button
232
+ type={"button"}
233
+ tabIndex={-1}
234
+ onClick={() => editor.chain().focus().undo().run()}
235
+ disabled={
236
+ !editor.can()
237
+ .chain()
238
+ .focus()
239
+ .undo()
240
+ .run()
241
+ }
242
+ >
243
+ <FaUndo/>
244
+ </button>
245
+ <button
246
+ type={"button"}
247
+ tabIndex={-1}
248
+ onClick={() => editor.chain().focus().redo().run()}
249
+ disabled={
250
+ !editor.can()
251
+ .chain()
252
+ .focus()
253
+ .redo()
254
+ .run()
255
+ }
256
+ >
257
+ <FaRedo/>
258
+ </button>
259
+ <button
260
+ tabIndex={-1}
261
+ type={"button"} onClick={() => editor.chain().focus().clearNodes().run()}>
262
+ <FaTrash />
263
+ </button>
264
+ </div>
265
+ </div>
266
+ )
267
+ }
268
+
269
+
270
+ export const ExtendedImage = Image.extend({
271
+ addAttributes() {
272
+ return {
273
+ src: {
274
+ default: '',
275
+ },
276
+ alt: {
277
+ default: undefined,
278
+ },
279
+ title: {
280
+ default: undefined,
281
+ },
282
+ width: {
283
+ default: undefined,
284
+ },
285
+ height: {
286
+ default: undefined,
287
+ },
288
+ style: {
289
+ default: undefined,
290
+ },
291
+ }
292
+ }
293
+ })
294
+
295
+ const CodeBlockComponent = ({children, ...rest}) => {
296
+ console.log(rest);
297
+ return children;
298
+ };
299
+
300
+ const extensions = [
301
+ HardBreak,
302
+ CodeBlock,
303
+ CodeBlockLowlight
304
+ .configure({ lowlight }),
305
+ Color.configure({types: [TextStyle.name, ListItem.name]}),
306
+ TextStyle.configure({types: [ListItem.name]}),
307
+ StarterKit.configure({
308
+ bulletList: {
309
+ keepMarks: true,
310
+ keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
311
+ },
312
+ orderedList: {
313
+ keepMarks: true,
314
+ keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
315
+ },
316
+ }),
317
+ ExtendedImage.configure(),
318
+ Link.configure({
319
+ openOnClick: false,
320
+ autolink: true,
321
+ defaultProtocol: 'https',
322
+ protocols: ['http', 'https'],
323
+ HTMLAttributes: {
324
+ // Change rel to different value
325
+ // Allow search engines to follow links(remove nofollow)
326
+ rel: 'noopener noreferrer',
327
+ // Remove target entirely so links open in current tab
328
+ target: null,
329
+ },
330
+ isAllowedUri: (url, ctx) => {
331
+ try {
332
+ // construct URL
333
+ const parsedUrl = url.includes(':') ? new URL(url) : new URL(`${ctx.defaultProtocol}://${url}`)
334
+
335
+ // use default validation
336
+ if (!ctx.defaultValidate(parsedUrl.href)) {
337
+ return false
338
+ }
339
+
340
+ // disallowed protocols
341
+ const disallowedProtocols = ['ftp', 'file', 'mailto']
342
+ const protocol = parsedUrl.protocol.replace(':', '')
343
+
344
+ if (disallowedProtocols.includes(protocol)) {
345
+ return false
346
+ }
347
+
348
+ // only allow protocols specified in ctx.protocols
349
+ const allowedProtocols = ctx.protocols.map(p => (typeof p === 'string' ? p : p.scheme))
350
+
351
+ if (!allowedProtocols.includes(protocol)) {
352
+ return false
353
+ }
354
+
355
+ // disallowed domains
356
+ const disallowedDomains = [];//['example-phishing.com', 'malicious-site.net']
357
+ const domain = parsedUrl.hostname
358
+
359
+ if (disallowedDomains.includes(domain)) {
360
+ return false
361
+ }
362
+
363
+ // all checks have passed
364
+ return true
365
+ } catch {
366
+ return false
367
+ }
368
+ },
369
+ shouldAutoLink: url => {
370
+ try {
371
+ // construct URL
372
+ const parsedUrl = url.includes(':') ? new URL(url) : new URL(`https://${url}`)
373
+
374
+ // only auto-link if the domain is not in the disallowed list
375
+ const disallowedDomains = [];
376
+ const domain = parsedUrl.hostname
377
+
378
+ return !disallowedDomains.includes(domain)
379
+ } catch {
380
+ return false
381
+ }
382
+ },
383
+
384
+ }),
385
+ ]
386
+
387
+
388
+ export const RTE = ({name, field, value, onChange, help, ...rest}) => {
389
+
390
+ const [showSource, setShowSource] = useState(false);
391
+ const editor = useEditor({
392
+ extensions,
393
+ content: value,
394
+ onUpdate : ({editor}) => {
395
+ onChange?.({name: field.name, value: code(editor.getHTML())});
396
+ }
397
+ })
398
+ const [delay , setDelay] = useState(setTimeout(() => {}))
399
+
400
+ useEffect(() => {
401
+ editor.commands.setContent(value);
402
+ setShowSource(false);
403
+ onChange?.({name: field.name, value: code(editor.getHTML())});
404
+ }, [name]);
405
+
406
+ return (
407
+ <>
408
+ {help &&<div className="flex help">{help}</div>}
409
+ <div className="rte">
410
+ <MenuBar editor={editor} showSource={showSource} setShowSource={setShowSource} onChange={onChange} field={field} />
411
+ <EditorContent {...rest} editor={editor} defaultValue={value} value={value} />
412
+ </div>
413
+ </>
414
+ )
415
+ }
@@ -0,0 +1,103 @@
1
+ // Dans C:/Dev/hackersonline-engine/client/src/RTETrans.jsx (Nouveau fichier)
2
+ import React, { useState, useEffect } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { RTE } from './RTE.jsx';
5
+ import {useModelContext} from "./contexts/ModelContext.jsx";
6
+ import {useQuery} from "react-query";
7
+ import {getUserId} from "data-primals-engine/data";
8
+ import {useAuthContext} from "./contexts/AuthContext.jsx";
9
+
10
+ const RTETrans = ({ value, onChange, field }) => {
11
+ const { t } = useTranslation();
12
+ const { me } = useAuthContext();
13
+
14
+ const { data: langs } = useQuery("langsz", ({signal}) => {
15
+
16
+ const params = new URLSearchParams();
17
+ params.append('model', 'lang');
18
+ params.append("_user", getUserId(me));
19
+ params.append("depth", '1');
20
+ return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', body: JSON.stringify({}), headers: { "Content-Type": "application/json"}})
21
+ .then((res) => res.json())
22
+ .then((data) => {
23
+ setAvailableLangs(data.data || []);
24
+ })
25
+ },{ enabled: true })
26
+ // La valeur est un objet comme { fr: "...", en: "..." }
27
+ const existingLangs = Object.keys(value || {});
28
+ const [activeTab, setActiveTab] = useState(existingLangs[0] || null);
29
+ const [availableLangs, setAvailableLangs] = useState([]);
30
+
31
+ // Mettre à jour l'onglet actif si la valeur change de l'extérieur
32
+ useEffect(() => {
33
+ const currentLangs = Object.keys(value || {});
34
+ if (!currentLangs.includes(activeTab)) {
35
+ setActiveTab(currentLangs[0] || null);
36
+ }
37
+ }, [value, activeTab]);
38
+
39
+ const handleRteChange = (htmlContent) => {
40
+ const newValue = {
41
+ ...value,
42
+ [activeTab]: htmlContent.value,
43
+ };
44
+ onChange(newValue);
45
+ };
46
+
47
+ const handleAddLanguage = (langCode) => {
48
+ if (!langCode || (value && value.hasOwnProperty(langCode))) return;
49
+ const newValue = {
50
+ ...(value || {}),
51
+ [langCode]: '', // Initialiser avec un contenu vide
52
+ };
53
+ onChange(newValue);
54
+ setActiveTab(langCode); // Activer le nouvel onglet
55
+ };
56
+ const languagesToAdd = availableLangs.filter(lang => !existingLangs.includes(lang.code));
57
+
58
+ return (
59
+ <div className="rte-trans-container">
60
+ <div className="tabs-container flex items-center border-b border-gray-200">
61
+ {(existingLangs).map(lang => (
62
+ <button
63
+ key={lang}
64
+ type="button"
65
+ onClick={() => setActiveTab(lang)}
66
+ title={availableLangs.find(f=>f.code===lang)?.name.value || ''}
67
+ >
68
+ {lang.toUpperCase()}
69
+ </button>
70
+ ))}
71
+ {languagesToAdd.length > 0 && (
72
+ <div>
73
+ <select
74
+ onChange={(e) => handleAddLanguage(e.target.value)}
75
+ value=""
76
+ >
77
+ <option value="" disabled>{t('add_language', 'Ajouter...')}</option>
78
+ {languagesToAdd.map(lang => (
79
+ <option key={lang.code} value={lang.code}>
80
+ {lang.name.value} ({lang.code})
81
+ </option>
82
+ ))}
83
+ </select>
84
+ </div>
85
+ )}
86
+ </div>
87
+ <div className="rte-content mt-2">
88
+ {activeTab ? (
89
+ <RTE
90
+ key={activeTab}
91
+ value={value?.[activeTab] || ''}
92
+ onChange={handleRteChange}
93
+ field={field}
94
+ />
95
+ ) : (
96
+ <div>{t('select_or_add_language', 'Sélectionnez ou ajoutez une langue pour commencer.')}</div>
97
+ )}
98
+ </div>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export default RTETrans;