data-primals-engine 1.0.10 → 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 +5390 -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,75 @@
1
+ // src/components/FlexBuilderModal.jsx
2
+ import React, { useState, useEffect, useCallback } from 'react'; // Ajout de useCallback
3
+ import PropTypes from 'prop-types';
4
+ import { useTranslation } from 'react-i18next';
5
+ import FlexBuilder from './FlexBuilder';
6
+ import './FlexBuilderModal.scss';
7
+ import {useAuthContext} from "./contexts/AuthContext.jsx";
8
+
9
+ const FlexBuilderModal = ({ isOpen, onClose, onSave, models, data, initialConfig = null }) => {
10
+ const { t } = useTranslation();
11
+ const [currentFlexConfig, setCurrentFlexConfig] = useState(initialConfig);
12
+ const {me} = useAuthContext();
13
+
14
+ useEffect(() => {
15
+ // Met à jour currentFlexConfig si initialConfig (venant du parent DashboardView) change
16
+ // ou si la modale est (ré)ouverte.
17
+ // Cela est utile si l'utilisateur ferme la modale puis la rouvre pour éditer un autre item
18
+ // ou le même item avec des données potentiellement mises à jour de l'extérieur.
19
+ setCurrentFlexConfig(initialConfig);
20
+ }, [isOpen, initialConfig]); // S'assurer de réagir aux changements de initialConfig
21
+
22
+ // Stabiliser handleFlexConfigChange avec useCallback
23
+ const handleFlexConfigChange = useCallback((newConfig) => {
24
+ setCurrentFlexConfig(newConfig);
25
+ }, []); // Les dépendances sont vides car setCurrentFlexConfig est stable
26
+
27
+ const handleSave = () => {
28
+ if (currentFlexConfig) {
29
+ onSave(currentFlexConfig);
30
+ }
31
+ // onClose(); // La fermeture est gérée par le parent (DashboardView) après la sauvegarde.
32
+ };
33
+
34
+ if (!isOpen) {
35
+ return null;
36
+ }
37
+
38
+ return (
39
+ <div className="flex-builder-config-modal-overlay" onClick={onClose}>
40
+ <div className="flex-builder-config-modal-content" onClick={(e) => e.stopPropagation()}>
41
+ <div className="flex-builder-config-modal-header">
42
+ <h3>{t('dashboard.configureFlexView', 'Configurer la Vue Flex')}</h3>
43
+ <button onClick={onClose} className="btn-close-x">&times;</button>
44
+ </div>
45
+ <div className="flex-builder-config-modal-body">
46
+ <FlexBuilder
47
+ initialConfig={currentFlexConfig}
48
+ models={(models || []).filter(f => f._user === me?.username)}
49
+ data={data}
50
+ onChange={handleFlexConfigChange} // Maintenant, cette prop est stable
51
+ />
52
+ </div>
53
+ <div className="flex-builder-config-modal-footer">
54
+ <button onClick={onClose} className="btn-secondary">
55
+ {t('btns.cancel', 'Annuler')}
56
+ </button>
57
+ <button onClick={handleSave} className="btn-primary">
58
+ {t('btns.save', 'Enregistrer la Vue')}
59
+ </button>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ );
64
+ };
65
+
66
+ FlexBuilderModal.propTypes = {
67
+ isOpen: PropTypes.bool.isRequired,
68
+ onClose: PropTypes.func.isRequired,
69
+ onSave: PropTypes.func.isRequired,
70
+ models: PropTypes.array.isRequired,
71
+ data: PropTypes.array,
72
+ initialConfig: PropTypes.object,
73
+ };
74
+
75
+ export default FlexBuilderModal;
@@ -0,0 +1,109 @@
1
+ // src/components/FlexBuilderModal.scss
2
+
3
+ .flex-builder-config-modal-overlay {
4
+ position: fixed;
5
+ top: 0;
6
+ left: 0;
7
+ right: 0;
8
+ bottom: 0;
9
+ background-color: rgba(0, 0, 0, 0.7);
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ z-index: 1040; // Un peu moins que la modale de type de widget si elle reste ouverte
14
+ padding: 10px; // Pour petits écrans
15
+ }
16
+
17
+ .flex-builder-config-modal-content {
18
+ background-color: #fff;
19
+ border-radius: 8px;
20
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
21
+ display: flex;
22
+ flex-direction: column;
23
+ width: 95vw; // Très large pour accommoder FlexBuilder
24
+ height: 90vh; // Très haut
25
+ max-width: 1400px; // Limite maximale
26
+ overflow: hidden; // Le scroll sera géré par les enfants
27
+
28
+ .flex-builder-config-modal-header {
29
+ display: flex;
30
+ justify-content: space-between;
31
+ align-items: center;
32
+ padding: 15px 20px;
33
+ border-bottom: 1px solid #e0e0e0;
34
+ background-color: #f7f7f7;
35
+
36
+ h3 {
37
+ margin: 0;
38
+ font-size: 1.4em;
39
+ color: #333;
40
+ }
41
+
42
+ .btn-close-x {
43
+ background: none;
44
+ border: none;
45
+ font-size: 1.8em;
46
+ cursor: pointer;
47
+ color: #777;
48
+ padding: 0 5px;
49
+ line-height: 1;
50
+ &:hover {
51
+ color: #333;
52
+ }
53
+ }
54
+ }
55
+
56
+ .flex-builder-config-modal-body {
57
+ flex-grow: 1;
58
+ overflow-y: auto; // Permettre le scroll si FlexBuilder est plus grand
59
+ padding: 0; // FlexBuilder a son propre padding
60
+
61
+ // Le composant FlexBuilder prendra toute la place ici
62
+ .flex-builder {
63
+ height: 100%; // Essayer de faire en sorte que FlexBuilder remplisse la modale
64
+ border: none; // Enlever la bordure par défaut de FlexBuilder s'il est dans une modale
65
+ border-radius: 0;
66
+ box-shadow: none;
67
+
68
+ .flex-builder-controls {
69
+ // Ajustements potentiels pour les contrôles dans la modale
70
+ height: 100%; // S'assurer qu'il prend toute la hauteur
71
+ }
72
+ .flex-builder-preview-area {
73
+ // Ajustements potentiels pour l'aperçu dans la modale
74
+ height: 100%;
75
+ }
76
+ }
77
+ }
78
+
79
+ .flex-builder-config-modal-footer {
80
+ padding: 15px 20px;
81
+ border-top: 1px solid #e0e0e0;
82
+ display: flex;
83
+ justify-content: flex-end;
84
+ gap: 10px;
85
+ background-color: #f7f7f7;
86
+
87
+ button {
88
+ padding: 10px 20px;
89
+ font-size: 1em;
90
+ border-radius: 5px;
91
+ cursor: pointer;
92
+ border: none;
93
+ }
94
+ .btn-secondary {
95
+ background-color: #6c757d;
96
+ color: white;
97
+ &:hover {
98
+ background-color: #5a6268;
99
+ }
100
+ }
101
+ .btn-primary {
102
+ background-color: #007bff;
103
+ color: white;
104
+ &:hover {
105
+ background-color: #0056b3;
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { FlexNodeRenderer } from "./FlexNode.jsx";
5
+
6
+ const FlexBuilderPreview = ({
7
+ flexStructure,
8
+ onSelectNode,
9
+ selectedNodeId,
10
+ filteredData,
11
+ dataIndexRef,
12
+ modelFields,
13
+ dnd,
14
+ onMoveNode
15
+ }) => {
16
+ const { t } = useTranslation();
17
+
18
+ return (
19
+ <div className="flex-builder-preview-area">
20
+ <h3>{t('preview', 'Aperçu')}</h3>
21
+ <FlexNodeRenderer
22
+ node={flexStructure}
23
+ path={[]}
24
+ t={t}
25
+ onSelectNode={onSelectNode}
26
+ selectedNodeId={selectedNodeId}
27
+ data={filteredData}
28
+ dataIndexRef={dataIndexRef}
29
+ modelFields={modelFields}
30
+ dnd={dnd}
31
+ onMoveNode={onMoveNode}
32
+ />
33
+ </div>
34
+ );
35
+ };
36
+
37
+ FlexBuilderPreview.propTypes = {
38
+ flexStructure: PropTypes.object.isRequired,
39
+ onSelectNode: PropTypes.func.isRequired,
40
+ selectedNodeId: PropTypes.string,
41
+ filteredData: PropTypes.array.isRequired,
42
+ dataIndexRef: PropTypes.shape({ current: PropTypes.number }).isRequired,
43
+ modelFields: PropTypes.array.isRequired,
44
+ dnd: PropTypes.object.isRequired,
45
+ onMoveNode: PropTypes.func.isRequired,
46
+ };
47
+
48
+ export default FlexBuilderPreview;
@@ -0,0 +1,84 @@
1
+
2
+
3
+
4
+ // Helper to recursively remove a node. Returns { newStructure: object, removedNode: object | null }
5
+ export const deepRemoveNode = (current, nodeIdToRemove) => {
6
+ let removedNodeInstance = null;
7
+
8
+ if (!current.children) {
9
+ return { newStructure: current, removedNode: null };
10
+ }
11
+
12
+ const newChildren = [];
13
+ let modifiedChildren = false;
14
+
15
+ for (const child of current.children) {
16
+ if (child.id === nodeIdToRemove) {
17
+ removedNodeInstance = child;
18
+ modifiedChildren = true;
19
+ } else {
20
+ if (child.type === 'container' || (child.type === 'item' && child.content?.type === 'nestedContainer')) {
21
+ const containerNode = child.type === 'container' ? child : child.content.nestedContainer;
22
+ const result = deepRemoveNode(containerNode, nodeIdToRemove);
23
+
24
+ if (result.removedNode) {
25
+ removedNodeInstance = result.removedNode;
26
+ modifiedChildren = true;
27
+ if (child.type === 'container') {
28
+ newChildren.push(result.newStructure);
29
+ } else {
30
+ newChildren.push({ ...child, content: { ...child.content, nestedContainer: result.newStructure } });
31
+ }
32
+ } else {
33
+ newChildren.push(child);
34
+ }
35
+ } else {
36
+ newChildren.push(child);
37
+ }
38
+ }
39
+ }
40
+
41
+ if (modifiedChildren) {
42
+ return { newStructure: { ...current, children: newChildren }, removedNode: removedNodeInstance };
43
+ }
44
+ return { newStructure: current, removedNode: null };
45
+ };
46
+
47
+ // Helper to recursively insert a node. Returns { newStructure: object, inserted: boolean }
48
+ export const deepInsertNode = (current, targetParentId, nodeToInsert, index) => {
49
+ if (current.id === targetParentId) {
50
+ const currentChildren = current.children || [];
51
+ const newTargetChildren = [...currentChildren];
52
+ newTargetChildren.splice(index, 0, nodeToInsert);
53
+ return { newStructure: { ...current, children: newTargetChildren }, inserted: true };
54
+ }
55
+
56
+ if (!current.children) {
57
+ return { newStructure: current, inserted: false };
58
+ }
59
+
60
+ let opCompleted = false;
61
+ const newChildren = current.children.map(child => {
62
+ if (opCompleted) return child;
63
+
64
+ if (child.type === 'container' || (child.type === 'item' && child.content?.type === 'nestedContainer')) {
65
+ const containerNode = child.type === 'container' ? child : child.content.nestedContainer;
66
+ const result = deepInsertNode(containerNode, targetParentId, nodeToInsert, index);
67
+
68
+ if (result.inserted) {
69
+ opCompleted = true;
70
+ if (child.type === 'container') {
71
+ return result.newStructure;
72
+ } else {
73
+ return { ...child, content: { ...child.content, nestedContainer: result.newStructure } };
74
+ }
75
+ }
76
+ }
77
+ return child;
78
+ });
79
+
80
+ if (opCompleted) {
81
+ return { newStructure: { ...current, children: newChildren }, inserted: true };
82
+ }
83
+ return { newStructure: current, inserted: false };
84
+ };
@@ -0,0 +1,170 @@
1
+ // src/components/FlexDataRenderer.jsx
2
+ import React from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import { useTranslation } from 'react-i18next';
5
+ import {FaCheckSquare, FaCopy, FaExternalLinkAlt, FaImage, FaRegSquare} from 'react-icons/fa';
6
+ import './FlexDataRenderer.scss';
7
+ import {useModelContext} from "./contexts/ModelContext.jsx";
8
+ import RelationValue from "./RelationValue.jsx";
9
+ import {CodeField, ColorField} from "./Field.jsx";
10
+ import {useAuthContext} from "./contexts/AuthContext.jsx";
11
+ import {DialogProvider} from "./Dialog.jsx";
12
+
13
+ // Helper for date formatting
14
+ const formatDate = (value, type, t) => {
15
+ if (!value) return '';
16
+ try {
17
+ const date = new Date(value);
18
+ if (isNaN(date.getTime())) return t('flexDataRenderer.invalidDate', 'Date invalide');
19
+
20
+ // Adjust formatting as needed, or use a library like date-fns or moment
21
+ if (type === 'datetime-local' || type === 'datetime') {
22
+ return date.toLocaleString();
23
+ }
24
+ if (type === 'date') {
25
+ return date.toLocaleDateString();
26
+ }
27
+ return date.toISOString(); // Fallback
28
+ } catch (e) {
29
+ console.error("Error formatting date:", e);
30
+ return t('flexDataRenderer.invalidDate', 'Date invalide');
31
+ }
32
+ };
33
+
34
+ const FlexDataRenderer = ({ value, fieldDefinition,data }) => {
35
+ const { t, i18n } = useTranslation();
36
+ const {models} = useModelContext()
37
+ const {me} = useAuthContext()
38
+ // 'translatedLabel' is added by getFieldDefinitionByPath
39
+ const field = fieldDefinition;
40
+ const translatedLabel = t('model_'+field.name, field.name);
41
+
42
+ const renderContent = () => {
43
+
44
+ if (value === undefined || value === null) {
45
+ return <span className="data-value-empty">({t('flexDataRenderer.noValue', 'N/A')})</span>;
46
+ }
47
+ if( field.type === 'relation' && !models.find(f => f.name === field.relation && f._user === me?.username ))
48
+ return '';
49
+ if (field.type === "relation" && typeof field.relation === "string") {
50
+ return <RelationValue field={field} data={data}/>;
51
+ }
52
+ if ((field.type === "date" || field.type === "datetime") && value) {
53
+ return new Date(value).toLocaleDateString(i18n.resolvedLanguage || i18n.language, {
54
+ day: "numeric",
55
+ month: "numeric",
56
+ year: "numeric",
57
+ hour: "numeric",
58
+ minute: "numeric"
59
+ });
60
+ }
61
+ if (field.type === "enum") {
62
+ return field.items.includes(value) ? `${t(value, value)}` : `${value || ''}`;
63
+ }
64
+ if (field.type === "code") {
65
+ const v = typeof(value) === "string" ? value : (value ? JSON.stringify(value) : '');
66
+ return <CodeField language={field.language} name={field.name} value={v} disabled={true} />
67
+ }
68
+ if (field.type === "object") {
69
+ return <CodeField language={'json'} name={field.name} value={value ? JSON.stringify(value) : ''} disabled={true} />
70
+ }
71
+ if (field.type === 'email') {
72
+ return <a href={"mailto:"+value}>{value}</a>;
73
+ }
74
+ if (field.type === 'phone') {
75
+ return <a href={"tel:"+value}>{value}</a>;
76
+ }
77
+ if (field.type === 'model') {
78
+ return value;
79
+ }
80
+ if (field.type === 'password') {
81
+ return <></>;
82
+ }
83
+ if (field.type === 'array') {
84
+ let t;
85
+ if (field.itemsType === 'file') {
86
+ t = (value || []).map(it =>
87
+ it &&
88
+ <a key={it.guid} href={`/resources/${it.guid}`} target="_blank"
89
+ rel="noopener noreferrer"><img src={`/resources/${it.guid}`}
90
+ alt={`${it.name} (${it.guid})`}/></a>
91
+ );
92
+ return {t};
93
+ }
94
+ return value.join(', ');
95
+ }
96
+ if (field.type === 'url') {
97
+ return value && (<><a href={value}
98
+ title={value} style={{color: field.color}}
99
+ className={"link-value"}
100
+ target="_blank">{value}</a>
101
+ <button title={"Copy URL"} onClick={(e) => {
102
+ navigator.clipboard.writeText(value).then(function () {
103
+ console.log('Async: Copying to clipboard was successful!');
104
+ }, function (err) {
105
+ console.error('Async: Could not copy text: ', err);
106
+ });
107
+ }}><FaCopy/></button>
108
+ </>)
109
+ }
110
+ if (field.type === "file" && value) {
111
+ if (['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/svg+xml', 'image/webp', 'image/bmp', 'image/tiff', 'image/x-icon', 'image/x-windows-bmp'].includes(value.type))
112
+ return <a
113
+ href={`/resources/${value.guid}`} target="_blank"
114
+ rel="noopener noreferrer"><img
115
+ src={`/resources/${value.guid}`}
116
+ alt={`${value.name} (${value.guid})`}/></a>;
117
+ return <a style={{color: field.color}}
118
+ href={`/resources/${value.guid}`}
119
+ target="_blank"
120
+ rel="noopener noreferrer">{value.name} ({value.guid})</a>;
121
+ }
122
+ if (field.type === 'number') {
123
+ let val = value;
124
+ if (val && field.unit) {
125
+ let formatter = new Intl.NumberFormat(lang);
126
+ val = formatter.format(value);
127
+ }
128
+ return val ? `${val} ${field.unit || ''}` : '';
129
+ }
130
+ if (field.type === "boolean") {
131
+ return value ? t('yes') : t('no');
132
+ }
133
+ if (field.type === 'string_t') {
134
+ return t(value.value, '') || value.key || (typeof(value) === 'string' ? value : '');
135
+ }
136
+ if (field.type === 'richtext') {
137
+ return <div className="rte-value"
138
+ dangerouslySetInnerHTML={{__html: value}}></div>
139
+ }
140
+ if (field.type === 'color') {
141
+ return <ColorField name={field.name} disabled={true}
142
+ value={value}/>;
143
+ }
144
+ return value;
145
+ };
146
+
147
+ return (
148
+ renderContent()
149
+ );
150
+ };
151
+
152
+ FlexDataRenderer.propTypes = {
153
+ value: PropTypes.any,
154
+ fieldDefinition: PropTypes.shape({
155
+ name: PropTypes.string.isRequired,
156
+ type: PropTypes.string.isRequired,
157
+ labelKey: PropTypes.string,
158
+ label: PropTypes.string,
159
+ translatedLabel: PropTypes.string,
160
+ options: PropTypes.shape({
161
+ items: PropTypes.arrayOf(PropTypes.shape({
162
+ value: PropTypes.any.isRequired,
163
+ label: PropTypes.string.isRequired,
164
+ labelKey: PropTypes.string,
165
+ }))
166
+ }),
167
+ }).isRequired,
168
+ };
169
+
170
+ export default FlexDataRenderer;
@@ -0,0 +1,80 @@
1
+ // src/components/FlexDataRenderer.scss
2
+
3
+ // Using the same overall structure as .data-badge for consistency.
4
+ // Specific styles for the values rendered by FlexDataRenderer:
5
+
6
+ .flex-data-renderer { // This class is on the same element as .data-badge
7
+ .data-badge-value {
8
+ // General container for the rendered value, if specific styling is needed here
9
+ }
10
+
11
+ .data-value-empty {
12
+ color: #9ca3af; // Tailwind gray-400
13
+ font-style: italic;
14
+ }
15
+
16
+ .data-value-boolean {
17
+ font-size: 1.1em; // Make icons slightly larger
18
+ &.true {
19
+ color: #10b981; // Tailwind green-500
20
+ }
21
+ &.false {
22
+ color: #6b7280; // Tailwind gray-500
23
+ }
24
+ }
25
+
26
+ .data-value-date {
27
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
28
+ font-size: 0.95em;
29
+ }
30
+
31
+ .data-value-email,
32
+ .data-value-url {
33
+ color: #3b82f6; // Tailwind blue-500
34
+ text-decoration: none;
35
+ &:hover {
36
+ text-decoration: underline;
37
+ }
38
+ svg { // For the external link icon
39
+ margin-left: 0.25em;
40
+ font-size: 0.8em;
41
+ display: inline-block;
42
+ vertical-align: middle;
43
+ }
44
+ }
45
+
46
+ .data-value-image {
47
+ max-width: 120px; // Adjust as needed
48
+ max-height: 80px; // Adjust as needed
49
+ height: auto;
50
+ display: block;
51
+ border: 1px solid #e5e7eb; // Tailwind gray-200
52
+ border-radius: 3px;
53
+ margin-top: 2px; // Small space if label is above
54
+ }
55
+
56
+ .data-value-color {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ .color-swatch {
60
+ width: 1.2em;
61
+ height: 1.2em;
62
+ border: 1px solid #d1d5db; // Tailwind gray-300
63
+ margin-right: 0.5em;
64
+ display: inline-block;
65
+ border-radius: 2px;
66
+ }
67
+ }
68
+
69
+ .data-value-array {
70
+ // Custom styling for arrays if needed, e.g., tags
71
+ font-style: italic;
72
+ color: #4b5563; // Tailwind gray-600
73
+ }
74
+
75
+ .data-value-select,
76
+ .data-value-text,
77
+ .data-value-default {
78
+ // General text styling, if different from default .data-badge-value
79
+ }
80
+ }