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,210 @@
1
+ // C:/Dev/hackersonline-engine/client/src/Dashboard.jsx
2
+ import React, {useState, useMemo, useEffect} from 'react';
3
+ import { Trans, useTranslation } from 'react-i18next';
4
+ import {FaPlus, FaSpinner} from "react-icons/fa"; // Ajout FaTrash
5
+
6
+ import "./Dashboard.scss"
7
+ import {useMutation, useQuery, useQueryClient} from "react-query";
8
+ import { useAuthContext } from "./contexts/AuthContext.jsx";
9
+ import { SelectField } from "./Field.jsx";
10
+ import {DashboardView} from "./DashboardView.jsx";
11
+ import {useModelContext} from "./contexts/ModelContext.jsx";
12
+ import {useParams, useSearchParams} from "react-router-dom";
13
+ import {getObjectHash} from "data-primals-engine/core";
14
+ import {getUserHash, getUserId} from "data-primals-engine/data";
15
+
16
+ // --- DashboardsPage (Reste inchangé) ---
17
+ export function DashboardsPage() {
18
+ const { setSelectedModel } = useModelContext()
19
+ const { t } = useTranslation();
20
+ const { me } = useAuthContext();
21
+ const [selectedDashboardId, setSelectedDashboardId] = useState(null);
22
+
23
+ const [ searchParams, setSearchParams ] = useSearchParams();
24
+ const { hash } = useParams(); // 2. Récupérer le hash de l'URL
25
+
26
+ // ... après les autres hooks useState, useQuery, etc.
27
+ const queryClient = useQueryClient();
28
+ const [newDashboardName, setNewDashboardName] = useState('');
29
+ const [isCreating, setIsCreating] = useState(false); // <-- AJOUTEZ CETTE LIGNE
30
+
31
+ const createDashboardMutation = useMutation(
32
+ async (dashboardName) => {
33
+ const isFirstDashboard = !dashboardsData?.data || dashboardsData.data.length === 0;
34
+ const response = await fetch('/api/data', {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({
38
+ model: 'dashboard',
39
+ data: { name: dashboardName, isDefault: isFirstDashboard } // Le premier est défini par défaut
40
+ })
41
+ });
42
+ if (!response.ok) {
43
+ const errorData = await response.json();
44
+ throw new Error(errorData.error || t('dashboards.errorCreate', 'Impossible de créer le tableau de bord'));
45
+ }
46
+ return response.json();
47
+ },
48
+ {
49
+ onSuccess: () => {
50
+ // Rafraîchit la liste des dashboards après la création
51
+ queryClient.invalidateQueries(['userDashboards', me?.username]);
52
+ setNewDashboardName('');
53
+ setIsCreating(false);
54
+ },
55
+ onError: (error) => {
56
+ // Idéalement, afficher une notification à l'utilisateur
57
+ console.error("Erreur lors de la création du dashboard:", error);
58
+ }
59
+ }
60
+ );
61
+
62
+ const handleCreateDashboard = (e) => {
63
+ e.preventDefault();
64
+ if (newDashboardName.trim()) {
65
+ createDashboardMutation.mutate(newDashboardName.trim());
66
+ }
67
+ };
68
+ const { data: dashboardsData, isLoading: isLoadingDashboards, error: errorDashboards } = useQuery(
69
+ ['userDashboards', me?.username],
70
+ async () => {
71
+ if (!me?.username) return null;
72
+ const response = await fetch(
73
+ `/api/data/search?model=dashboard&_user=${me.username}`, {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ // body: JSON.stringify({ sort: { name: 1 } }) // Optionnel: trier
77
+ });
78
+ if (!response.ok) {
79
+ const res = await response.json();
80
+ throw new Error(res.error || t('dashboards.errorList', 'Erreur chargement des tableaux de bord'));
81
+ }
82
+ return await response.json();
83
+ },
84
+ {
85
+ enabled: !!me?.username
86
+ }
87
+ );
88
+ const [isLoading, setIsLoading] = useState(true);
89
+
90
+ // 3. Ce useEffect trouve le bon dashboard quand le hash ou la liste change
91
+ useEffect(() => {
92
+ if (dashboardsData?.data.length > 0) {
93
+ if( hash ) {
94
+ const foundDashboard = dashboardsData?.data.find(d => d._hash+'' === hash);
95
+ setSelectedDashboardId(foundDashboard?._id || null); // Met à jour avec le dashboard trouvé ou null
96
+ }else{
97
+ const defaultDashboard = dashboardsData?.data.find(db => db.isDefault === true);
98
+ if( !defaultDashboard) {
99
+ setSelectedDashboardId(dashboardsData.data[0]._id);
100
+ }else
101
+ setSelectedDashboardId(defaultDashboard._id || null);
102
+ }
103
+ }
104
+ setIsLoading(false);
105
+ }, [hash,dashboardsData]);
106
+ const selectedDashboard = useMemo(() => {
107
+ if (!selectedDashboardId || !dashboardsData?.data) return null;
108
+ return dashboardsData.data.find(db => db._id === selectedDashboardId);
109
+ }, [selectedDashboardId, dashboardsData]);
110
+
111
+ const dashboardOptions = useMemo(() => {
112
+ return (dashboardsData?.data || []).map(db => ({
113
+ label: db.name + (db.isDefault ? ` (${t('dashboards.default', 'Défaut')})` : ''),
114
+ value: db._id
115
+ }));
116
+ }, [dashboardsData, t]);
117
+
118
+ useEffect(() => {
119
+ setSelectedModel(null)
120
+ }, []);
121
+
122
+
123
+ if (isLoading) {
124
+ return <div>{t('loading', 'Chargement...')}</div>;
125
+ }
126
+
127
+ if (!selectedDashboardId && hash) {
128
+ return (
129
+ <div className="dashboard-not-found">
130
+ <h2>{t('dashboards.notFound', 'Dashboard non trouvé')}</h2>
131
+ <p>{t('dashboards.notFoundHelp', 'Le dashboard avec l\'identifiant "{{0}}" n\'existe pas ou vous n\'y avez pas accès.',[hash] )}</p>
132
+ </div>
133
+ );
134
+ }
135
+
136
+ const creationForm = (
137
+ <form onSubmit={handleCreateDashboard} className="create-dashboard-form flex flex-start mg-v-1">
138
+ <input
139
+ type="text"
140
+ className="input-fit"
141
+ value={newDashboardName}
142
+ onChange={(e) => setNewDashboardName(e.target.value)}
143
+ placeholder={t('dashboards.newName', 'Nom du nouveau tableau de bord')}
144
+ disabled={createDashboardMutation.isLoading}
145
+ autoFocus
146
+ />
147
+ <button type="submit" className="btn" disabled={createDashboardMutation.isLoading || !newDashboardName.trim()}>
148
+ {createDashboardMutation.isLoading
149
+ ? <><FaSpinner className="spin" /> <Trans i18nKey="creating">Création...</Trans></>
150
+ : <Trans i18nKey="btns.create">Créer</Trans>
151
+ }
152
+ </button>
153
+ {/* Bouton pour annuler quand on a cliqué sur le "+" */}
154
+ {isCreating && (
155
+ <button type="button" className="btn" onClick={() => setIsCreating(false)}>
156
+ <Trans i18nKey="btns.cancel">Annuler</Trans>
157
+ </button>
158
+ )}
159
+ </form>
160
+ );
161
+
162
+ return (
163
+ <div className="dashboards-page">
164
+ <h1>{t('dashboards.title', 'Mes Tableaux de Bord')}</h1>
165
+
166
+ {isLoadingDashboards && (
167
+ <p><FaSpinner className="spin" /> <Trans i18nKey="dashboards.loadingList">Chargement des tableaux de bord...</Trans></p>
168
+ )}
169
+
170
+ {errorDashboards && (
171
+ <p className="error">{errorDashboards.message}</p>
172
+ )}
173
+
174
+ {!isLoadingDashboards && !errorDashboards && (
175
+ <>
176
+ {dashboardsData?.data && dashboardsData.data.length > 0 ? (
177
+ <div className="dashboard-selector-container">
178
+ <div className="dashboard-selector flex" style={{ alignItems: 'flex-end', gap: '8px' }}>
179
+ <SelectField
180
+ name="dashboardSelection"
181
+ label={t('dashboards.select', 'Choisir un tableau de bord :')}
182
+ value={selectedDashboardId}
183
+ onChange={(selectedOption) => {
184
+ setSelectedDashboardId(selectedOption.value);
185
+ const d = dashboardsData.data.find(f => f._id === selectedOption.value);
186
+ history.pushState({}, null, '/user/'+getUserHash(me)+'/dashboards/'+d._hash);
187
+ }}
188
+ items={dashboardOptions}
189
+ />
190
+ <button onClick={() => setIsCreating(true)} className="btn" title={t('dashboards.add', 'Ajouter un tableau de bord')}>
191
+ <FaPlus />
192
+ </button>
193
+ </div>
194
+ {isCreating && creationForm}
195
+ </div>
196
+ ) : (
197
+ <>
198
+ <p><Trans i18nKey="dashboards.noDashboards">Aucun tableau de bord trouvé. Vous pouvez en créer un.</Trans></p>
199
+ {creationForm}
200
+ </>
201
+ )}
202
+
203
+ {/* Affiche la vue du dashboard sélectionné */}
204
+ {/* Passe l'objet dashboard complet */}
205
+ <DashboardView dashboard={selectedDashboard} />
206
+ </>
207
+ )}
208
+ </div>
209
+ );
210
+ }
@@ -0,0 +1,445 @@
1
+ @import "_variables.scss"; // Importer tes variables
2
+ .dashboards-page {
3
+ padding: 16px;
4
+ }
5
+ .dashboard-view {
6
+ .kpi-grid {
7
+ display: grid;
8
+ // Ajuste minmax pour la largeur minimale/maximale des KPIs
9
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
10
+ gap: 20px; // Espace entre les KPIs
11
+ margin-top: 20px;
12
+ }
13
+
14
+ .add-buttons-inline {
15
+ display: flex;
16
+ gap: 8px;
17
+ align-self: center;
18
+ }
19
+ .add-chart-button, .add-kpi-button {
20
+ // Style pour le bouton d'ajout
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ gap: 8px;
25
+ padding: 15px;
26
+ border: 2px dashed $medium-gray; // Style pointillé
27
+ border-radius: 8px;
28
+ background-color: $light-gray;
29
+ color: $dark-gray;
30
+ cursor: pointer;
31
+ transition: background-color 0.2s, border-color 0.2s;
32
+
33
+ &:hover {
34
+ background-color: darken($light-gray, 5%);
35
+ border-color: $primary-color;
36
+ color: $primary-color;
37
+ }
38
+ }
39
+ }
40
+
41
+ // ... (styles existants du KPIWidget) ...
42
+
43
+ .kpi-widget {
44
+ position: relative; // Nécessaire pour positionner le bouton remove
45
+ text-align: right;
46
+ padding: 24px;
47
+
48
+ min-width: 240px;
49
+ .kpi-title {
50
+ display: flex; /* Aligner nom et icône info */
51
+ align-items: center;
52
+ }
53
+ .kpi-content {
54
+ padding-left: 24px;
55
+ }
56
+ .kpi-title {
57
+ display: flex;
58
+ justify-content: right;
59
+ }
60
+ .kpi-info {
61
+ outline: 1px solid transparent;
62
+ margin-left: 5px;
63
+ cursor: help;
64
+ opacity: 0.7;
65
+ &:hover {
66
+ outline: 1px solid gray;
67
+ }
68
+ width: 24px;
69
+ height: 24px;
70
+ text-align: center;
71
+ }
72
+ .kpi-remove-button {
73
+ position: absolute;
74
+ top: 5px;
75
+ right: 5px;
76
+ background: none;
77
+ border: none;
78
+ color: inherit;
79
+ cursor: pointer;
80
+ padding: 3px;
81
+ line-height: 1;
82
+ border-radius: 50%;
83
+ opacity: 0.5; // Moins visible par défaut
84
+ transition: opacity 0.2s, color 0.2s;
85
+
86
+ &:hover {
87
+ color: $danger-color; // Rouge au survol
88
+ opacity: 1;
89
+ }
90
+ }
91
+
92
+ &:hover .kpi-remove-button {
93
+ opacity: 1; // Rendre visible au survol du widget
94
+ }
95
+
96
+ .kpi-value-number {
97
+ font-size: 240%;
98
+ padding: 4px;
99
+ }
100
+
101
+ .kpi-icon {
102
+ position: absolute;
103
+ font-size: 340%;
104
+ bottom: 6px;
105
+ left: 24px;
106
+ }
107
+
108
+ /* Dans votre fichier CSS global ou spécifique aux composants */
109
+
110
+ .kpi-value-with-total .kpi-main-value {
111
+ /* Style pour la valeur principale si nécessaire */
112
+ font-weight: bold; /* Exemple */
113
+ }
114
+
115
+ .kpi-value-with-total .kpi-separator {
116
+ opacity: 0.8;
117
+ margin: 0 0.2em; /* Un peu d'espace autour du slash */
118
+ }
119
+
120
+ .kpi-value-with-total .kpi-total-value {
121
+ font-size: 0.85em; /* Rur le total */
122
+ opacity: 0.9;
123
+ }
124
+
125
+ .kpi-percentage {
126
+ font-weight: bold; /* Mettre le pourcentage en gras par exemple */
127
+ }
128
+
129
+ .kpi-value-simple .kpi-value-number {
130
+ /* Style pour le nombre seul si nécessaire */
131
+ }
132
+
133
+ .kpi-unit-suffix {
134
+ margin-left: 0.2em; /* Espace avant l'unité */
135
+ font-size: 1.4em;
136
+ opacity: 0.8;
137
+ }
138
+
139
+ .kpi-error {
140
+ color: red; /* Ou une couleur adaptée à votre thème */
141
+ font-weight: bold;
142
+ }
143
+
144
+ .kpi-loading {
145
+ opacity: 0.7;
146
+ }
147
+
148
+ .kpi-value-nodata {
149
+ opacity: 0.7;
150
+ }
151
+
152
+ .kpi-widget:hover .kpi-remove-button {
153
+ opacity: 1;
154
+ }
155
+
156
+ .kpi-remove-button:hover {
157
+ background: rgba(0, 0, 0, 0.3);
158
+ }
159
+
160
+ }
161
+
162
+ .dashboard-sections {
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 12px;
166
+ .dashboard-section {
167
+ border: 1px solid #e0e0e0;
168
+ padding: 1rem;
169
+ border-radius: 4px;
170
+ position: relative; // Pour positionner le bouton de suppression
171
+
172
+ .section-header {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 0.5rem;
176
+ margin-bottom: 1rem;
177
+ border-bottom: 1px solid #eee;
178
+ padding-bottom: 0.5rem;
179
+
180
+ .section-title {
181
+ flex-grow: 1;
182
+ margin: 0;
183
+ padding: 0.25rem 0; // Un peu d'espace pour cliquer/éditer
184
+ border-radius: 3px;
185
+ cursor: text; // Indique qu'on peut cliquer
186
+
187
+ &:focus, &.editing {
188
+ outline: 2px solid dodgerblue; // Mise en évidence lors de l'édition
189
+ background-color: #f0f8ff;
190
+ cursor: initial;
191
+ }
192
+ }
193
+
194
+ .edit-section-name-button {
195
+ background: none;
196
+ border: none;
197
+ color: #666;
198
+ cursor: pointer;
199
+ padding: 0.25rem;
200
+ &:hover {
201
+ color: #333;
202
+ }
203
+ }
204
+ }
205
+
206
+ .kpi-grid {
207
+ // ... (styles existants pour kpi-grid)
208
+ }
209
+
210
+ .remove-section-button {
211
+ position: absolute;
212
+ top: 0.5rem;
213
+ right: 0.5rem;
214
+ background: none;
215
+ border: none;
216
+ color: #aaa;
217
+ cursor: pointer;
218
+ font-size: 0.9em;
219
+ padding: 0.25rem;
220
+
221
+ &:hover {
222
+ color: red;
223
+ }
224
+ }
225
+
226
+ .empty-section-message {
227
+ color: #888;
228
+ font-style: italic;
229
+ text-align: center;
230
+ padding: 1rem 0;
231
+ }
232
+ }
233
+ }
234
+ // C:/Dev/hackersonline-engine/client/src/Dashboard.scss
235
+ // ... (autres styles existants) ...
236
+
237
+ // Styles pour les badges de données dans FlexView
238
+ .flex-view-content-wrapper {
239
+ // Style pour le conteneur de chaque "carte" répétée
240
+ // Chaque DisplayFlexNodeRenderer généré par le .map() sera un enfant direct ici.
241
+ // Vous pouvez ajouter des styles pour espacer les cartes, leur donner des bordures, etc.
242
+ // Par exemple, pour que chaque carte (chaque instance de flexStructure) soit distincte :
243
+ & > .flex-node.preview-container { // Cible la racine de chaque structure Flex répétée
244
+ border: 1px solid #e0e0e0;
245
+ padding: 15px;
246
+ border-radius: 4px;
247
+ background-color: #fff;
248
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
249
+ }
250
+ }
251
+
252
+ .preview-item.is-data-badge-item {
253
+ // Styles spécifiques pour les cases qui contiennent un data-badge
254
+ // Par défaut, preview-item a déjà display: flex, align-items: center, justify-content: center
255
+ // On peut vouloir ajuster ça pour les badges
256
+ align-items: flex-start; // Aligner les badges en haut de la case
257
+ justify-content: flex-start; // Aligner les badges à gauche de la case
258
+ padding: 5px; // Réduire un peu le padding interne de la case si besoin
259
+ }
260
+
261
+ .data-badge {
262
+ display: inline-flex; // Pour que le label et la valeur soient sur la même ligne par défaut
263
+ align-items: baseline; // Aligner le texte du label et de la valeur
264
+ padding: 4px 8px;
265
+ border: 1px solid #d1d5db; // Gris clair
266
+ border-radius: 4px;
267
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
268
+ background-color: #f9fafb; // Fond très clair
269
+ font-size: 0.9em;
270
+ margin: 2px; // Petit espacement si plusieurs badges sont dans la même case (si la case elle-même est un flex container)
271
+ width: 100%; // Pour que le badge prenne la largeur de la case
272
+ box-sizing: border-box;
273
+
274
+ .data-badge-label {
275
+ font-weight: 600; // Semi-bold pour le libellé
276
+ color: #374151; // Gris foncé
277
+ margin-right: 6px; // Espace entre le libellé et la valeur
278
+ white-space: nowrap; // Empêcher le libellé de passer à la ligne trop vite
279
+ }
280
+
281
+ .data-badge-value {
282
+ color: #1f2937; // Gris un peu plus foncé pour la valeur
283
+ word-break: break-word; // Permettre à la valeur de passer à la ligne si elle est longue
284
+
285
+ .data-badge-novalue,
286
+ .data-badge-nodata {
287
+ color: #9ca3af; // Gris plus clair pour les messages d'absence de données
288
+ font-style: italic;
289
+ }
290
+ }
291
+ }
292
+
293
+ // Si vous voulez que les badges s'empilent verticalement dans une case,
294
+ // et que chaque badge prenne toute la largeur :
295
+ .preview-item.is-data-badge-item {
296
+ // Si la case (preview-item) est un conteneur flex pour ses enfants (les data-badges)
297
+ // et que vous voulez que les badges s'empilent verticalement :
298
+ // display: flex; // Déjà défini sur preview-item
299
+ // flex-direction: column; // Empiler verticalement
300
+ // align-items: stretch; // Étirer les badges sur la largeur
301
+
302
+ // Si la case n'est pas un conteneur flex pour ses enfants directs (les data-badges)
303
+ // ou si vous voulez simplement que chaque badge soit sur sa propre ligne :
304
+ .data-badge {
305
+ display: flex; // Pour un meilleur contrôle interne du badge
306
+ // width: 100%; // Déjà défini ci-dessus, assure que le badge prend la largeur
307
+ margin-bottom: 5px; // Espace entre les badges s'ils s'empilent
308
+ &:last-child {
309
+ margin-bottom: 0;
310
+ }
311
+ }
312
+ }
313
+
314
+
315
+ .empty-data-message {
316
+ padding: 20px;
317
+ text-align: center;
318
+ color: #6b7280; // Gris moyen
319
+ font-style: italic;
320
+ border: 1px dashed #d1d5db;
321
+ border-radius: 4px;
322
+ margin-top: 10px;
323
+ }
324
+
325
+ .loading-overlay-flexview {
326
+ position: absolute;
327
+ top: 0;
328
+ left: 0;
329
+ right: 0;
330
+ bottom: 0;
331
+ background-color: rgba(255, 255, 255, 0.7);
332
+ display: flex;
333
+ align-items: center;
334
+ justify-content: center;
335
+ z-index: 10;
336
+ font-size: 1.2em;
337
+ color: #333;
338
+ .spin {
339
+ margin-right: 10px;
340
+ }
341
+ }
342
+
343
+ .flex-view-placeholder {
344
+ padding: 20px;
345
+ text-align: center;
346
+ color: #6b7280;
347
+ border: 1px dashed #d1d5db;
348
+ border-radius: 4px;
349
+ min-height: 100px;
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ &.error {
354
+ color: #ef4444; // Rouge pour les erreurs
355
+ border-color: #ef4444;
356
+ }
357
+ .spin {
358
+ margin-right: 8px;
359
+ }
360
+ }
361
+
362
+ .flex-view-content-wrapper{
363
+ display: flex;
364
+ flex-wrap: wrap;
365
+ gap:16px;
366
+ align-items: center;
367
+ justify-content: center;
368
+ }
369
+
370
+ .flex-node{
371
+ margin: 0;
372
+ max-width: 480px;
373
+ .relation-value{
374
+ padding: 6px 10px;
375
+ border-radius: 20px;
376
+ }
377
+ }
378
+ // Styles pour les wrappers d'items dans le dashboard
379
+ .dashboard-item-wrapper {
380
+ position: relative; // Pour les boutons d'action
381
+ border: 1px solid #e5e7eb; // Bordure légère
382
+ border-radius: 6px;
383
+ // box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // Ombre subtile
384
+ display: flex; // Pour que le contenu flex s'ende bien
385
+ flex-direction: column; // Empiler le contenu et les actions
386
+ min-width: 280px; // Largeur minimale pour une carte
387
+
388
+ &.flex-view-wrapper {
389
+ // Styles spécifiques si un flex-view a besoin de plus d'espace ou d'un traitement différent
390
+ // Par exemple, si la structure flex interne doit gérer son propre défilement :
391
+ // overflow: auto; // Attention, cela peut cacher les ombres ou les bordures si mal géré
392
+ }
393
+
394
+ .flex-view-content-wrapper {
395
+ flex-grow: 1; // Permettre au contenu de la vue flex de prendre l'espace disponible
396
+ // padding: 10px; // Padding autour du contenu de la vue flex si nécessaire
397
+ // Si vous avez déjà un padding sur .flex-node.preview-container, ce n'est peut-as nécessaire ici.
398
+ }
399
+
400
+ .item-actions {
401
+ display: flex;
402
+ justify-content: flex-end;
403
+ padding: 4px 8px;
404
+
405
+ button {
406
+ background: none;
407
+ border: none;
408
+ color: #6b7280; // Gris
409
+ cursor: pointer;
410
+ padding: 6px;
411
+ margin-left: 4px;
412
+ border-radius: 4px;
413
+ display: flex;
414
+ align-items: center;
415
+ font-size: 0.9rem;
416
+
417
+ &:hover {
418
+ color: #1f2937; // Noir/Gris foncé
419
+ background-color: #e5e7eb; // Fond au survol
420
+ }
421
+ &:disabled {
422
+ color: #d1d5db;
423
+ cursor: not-allowed;
424
+ background-color: transparent;
425
+ }
426
+ }
427
+ }
428
+ }
429
+
430
+ // Assurez-vous que les items flex dans la grille du dashboard s'affichent correctement
431
+ .items-grid.flex {
432
+ display: grid; // Ou flex si vous préférez une disposition en ligne
433
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); // Exemple de grille responsive
434
+ gap: 1rem; // Espace entre les items
435
+ }
436
+
437
+ .preview-item {
438
+ text-align: center;
439
+ }
440
+
441
+ .dashboard-selector {
442
+ label {
443
+ text-align: right;
444
+ }
445
+ }