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.
- package/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +5390 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- package/vitest.config.js +2 -2
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
|
|
2
|
+
export const MONGO_CALC_OPERATORS = {
|
|
3
|
+
$find: {
|
|
4
|
+
label: 'Find in relation',
|
|
5
|
+
description: 'Recherche dans une relation/tableau',
|
|
6
|
+
supportsNesting: true,
|
|
7
|
+
multi: false,
|
|
8
|
+
isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
|
|
9
|
+
},
|
|
10
|
+
$and: { label: 'Et (and)', description: 'Renvoie vrai si toutes les conditions sont vérifiées', args: 1, multi: true },
|
|
11
|
+
$or: { label: 'Ou (or)', description: 'Renvoie vrai si au moins une des conditions est vérifiée.', args: 1, multi: true },
|
|
12
|
+
$not: { label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true },
|
|
13
|
+
$nor: { label: 'Ou Non (nor)', description: 'Renvoie vrai si aucune des conditions n\'est vérifiée', args: 1, multi: true },
|
|
14
|
+
$eq: { label: '=', multi: false, args: 2 },
|
|
15
|
+
$ne: { label: '!=', multi: false, args: 2 },
|
|
16
|
+
$gt: { label: '>', multi: false, args: 2 },
|
|
17
|
+
$gte: { label: '>=', multi: false, args: 2 },
|
|
18
|
+
$lt: { label: '<', multi: false, args: 2 },
|
|
19
|
+
$lte: { label: '<=', multi: false, args: 2 },
|
|
20
|
+
$in: { label: 'in', multi: true, args: 2 },
|
|
21
|
+
$nin: { label: 'nin', multi: true, args: 2 },
|
|
22
|
+
$all: { label: 'afll', multi: true },
|
|
23
|
+
$size: {
|
|
24
|
+
label: 'size',
|
|
25
|
+
multi: false,
|
|
26
|
+
description: 'Renvoie le nombre d\'éléments dans un tableau',
|
|
27
|
+
disableAdvancedValue: true
|
|
28
|
+
},
|
|
29
|
+
$elemMatch: { label: 'elemMatch', multi: true },
|
|
30
|
+
$type: {
|
|
31
|
+
label: 'type',
|
|
32
|
+
description: 'Renvoie le type BSON de l\'élément',
|
|
33
|
+
multi: false,
|
|
34
|
+
disableAdvancedValue: true
|
|
35
|
+
},
|
|
36
|
+
$add: { label: '+', multi: true },
|
|
37
|
+
$subtract: { label: '-', multi: false, args: 2 },
|
|
38
|
+
$multiply: { label: '*', multi: true },
|
|
39
|
+
$divide: { label: '/', multi: false, args: 2 },
|
|
40
|
+
$mod: { label: '%', multi: false, args: 2 },
|
|
41
|
+
$pow: { label: 'pow', multi: false },
|
|
42
|
+
$sqrt: { label: 'sqrt', multi: false },
|
|
43
|
+
$exp: { label: 'exp', multi: false },
|
|
44
|
+
$abs: { label: 'abs', multi: false },
|
|
45
|
+
$ceil: { label: 'ceil', multi: false },
|
|
46
|
+
$floor: { label: 'floor', multi: false },
|
|
47
|
+
$ln: { label: 'ln', multi: false },
|
|
48
|
+
$log10: { label: 'log', multi: false },
|
|
49
|
+
$concat: { label: 'concat', multi: true },
|
|
50
|
+
$year: { label: 'year', multi: false, isDate: true },
|
|
51
|
+
$month: { label: 'month', multi: false, isDate: true },
|
|
52
|
+
$dayOfMonth: { label: 'dayOfMonth', multi: false, isDate: true },
|
|
53
|
+
$hour: { label: 'hour', multi: false, isDate: true },
|
|
54
|
+
$minute: { label: 'minute', multi: false, isDate: true },
|
|
55
|
+
$second: { label: 'second', multi: false, isDate: true },
|
|
56
|
+
$millisecond: { label: 'ms', multi: false, isDate: true },
|
|
57
|
+
$toBool: { label: 'toBool', multi: false, converter: true },
|
|
58
|
+
$toString: { label: 'toString', multi: false, converter: true },
|
|
59
|
+
$toInt: { label: 'toInt', multi: false, converter: true },
|
|
60
|
+
$toDouble: { label: 'toDouble', multi: false, converter: true },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const convertInputValue = (value) => {
|
|
64
|
+
if (typeof value !== 'string') return value;
|
|
65
|
+
|
|
66
|
+
if (value.startsWith('$')) {
|
|
67
|
+
// C'est une référence de champ
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Si la valeur est une chaîne entourée de guillemets, on la conserve telle quelle
|
|
72
|
+
if (/^['"].*['"]$/.test(value)) {
|
|
73
|
+
return value.slice(1, -1); // Retire les guillemets
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Conversion en nombre si possible
|
|
77
|
+
if (!isNaN(value) && value.trim() !== '') {
|
|
78
|
+
return Number(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (value.toLowerCase() === 'null') return null;
|
|
82
|
+
|
|
83
|
+
// Conversion en booléen pour 'true' et 'false'
|
|
84
|
+
if (value.toLowerCase() === 'true') return true;
|
|
85
|
+
if (value.toLowerCase() === 'false') return false;
|
|
86
|
+
|
|
87
|
+
// Par défaut, retourne la chaîne telle quelle
|
|
88
|
+
return value;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const parseNode = (node, pathPrefix = '$') => {
|
|
92
|
+
if (node && node.path && Array.isArray(node.path)) {
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const keys = Object.keys(node);
|
|
101
|
+
if (keys.length === 0) return null;
|
|
102
|
+
|
|
103
|
+
// Gestion des $find
|
|
104
|
+
if (keys.length === 1 && keys[0] === '$find') {
|
|
105
|
+
const findCondition = node.$find;
|
|
106
|
+
const parsedCondition = parseNode(findCondition, pathPrefix === '$' ? '$$this.' : pathPrefix);
|
|
107
|
+
|
|
108
|
+
if (parsedCondition) {
|
|
109
|
+
return {
|
|
110
|
+
...parsedCondition,
|
|
111
|
+
path: parsedCondition.path || [],
|
|
112
|
+
op: '$find',
|
|
113
|
+
value: parsedCondition
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Gestion des opérateurs logiques
|
|
119
|
+
if (keys.length === 1 && (keys[0] === '$and' || keys[0] === '$or')) {
|
|
120
|
+
const children = node[keys[0]]
|
|
121
|
+
.map(childNode => parseNode(childNode, pathPrefix))
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
return { [keys[0]]: children };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Gestion des opérateurs de comparaison
|
|
127
|
+
if (keys[0].startsWith('$')) {
|
|
128
|
+
const op = keys[0];
|
|
129
|
+
const args = node[op];
|
|
130
|
+
|
|
131
|
+
// Cas spécial pour regexMatch
|
|
132
|
+
if (op === '$regexMatch' && typeof args === 'object') {
|
|
133
|
+
const transformExpr = reverseTransformExpr(args.input, pathPrefix === '$$this.');
|
|
134
|
+
return {
|
|
135
|
+
path: transformExpr.path || [args.input.substring(pathPrefix.length).split('.')],
|
|
136
|
+
op: '$regex',
|
|
137
|
+
value: args.regex,
|
|
138
|
+
transform: transformExpr.transform
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Cas spécial pour exists
|
|
143
|
+
if ((op === '$ne' || op === '$eq') && Array.isArray(args) && args.length === 2 &&
|
|
144
|
+
typeof args[0] === 'object' && args[0].$type && args[1] === 'missing') {
|
|
145
|
+
const transformExpr = reverseTransformExpr(args[0].$type, pathPrefix === '$$this.');
|
|
146
|
+
return {
|
|
147
|
+
path: transformExpr.path || [args[0].$type.substring(pathPrefix.length).split('.')],
|
|
148
|
+
op: '$exists',
|
|
149
|
+
value: op === '$ne',
|
|
150
|
+
transform: transformExpr.transform
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Cas normal avec transformation
|
|
155
|
+
if (Array.isArray(args) && args.length === 2) {
|
|
156
|
+
const transformExpr = reverseTransformExpr(args[0], pathPrefix === '$$this.');
|
|
157
|
+
return {
|
|
158
|
+
path: transformExpr.path || [args[0].substring(pathPrefix.length).split('.')],
|
|
159
|
+
op,
|
|
160
|
+
value: args[1],
|
|
161
|
+
transform: transformExpr.transform
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Cas des transformations pures
|
|
166
|
+
const transformExpr = reverseTransformExpr(node, pathPrefix === '$$this.');
|
|
167
|
+
if (transformExpr && transformExpr.transform) {
|
|
168
|
+
return {
|
|
169
|
+
path: transformExpr.path || [keys[0].substring(1)],
|
|
170
|
+
transform: transformExpr.transform
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Gestion des champs simples
|
|
176
|
+
const path = keys[0];
|
|
177
|
+
if (typeof path === 'string' && !path.startsWith('$')) {
|
|
178
|
+
const condition = node[path];
|
|
179
|
+
if (typeof condition === 'object' && condition !== null && !Array.isArray(condition)) {
|
|
180
|
+
if (condition.$find) {
|
|
181
|
+
const parsedInnerNode = parseNode(condition.$find, '$$this.');
|
|
182
|
+
if (parsedInnerNode) {
|
|
183
|
+
return {
|
|
184
|
+
...parsedInnerNode,
|
|
185
|
+
path: [path, ...(parsedInnerNode.path || [])]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
const parsedCondition = parseNode(condition, pathPrefix);
|
|
190
|
+
if (parsedCondition) {
|
|
191
|
+
return {
|
|
192
|
+
...parsedCondition,
|
|
193
|
+
path: [path, ...(parsedCondition.path || [])]
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
return {
|
|
199
|
+
path: [path],
|
|
200
|
+
op: '$eq',
|
|
201
|
+
value: condition
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
export const buildRootFromExpr = (query) => {
|
|
211
|
+
if (!query || typeof query !== 'object' || Array.isArray(query)) {
|
|
212
|
+
return { $and: [] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const exprToParse = query.$expr || query;
|
|
216
|
+
const parsedRoot = parseNode(exprToParse);
|
|
217
|
+
|
|
218
|
+
if (!parsedRoot) return { $and: [] };
|
|
219
|
+
if (parsedRoot.$and || parsedRoot.$or) return parsedRoot;
|
|
220
|
+
|
|
221
|
+
return { $and: [parsedRoot] };
|
|
222
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {useQuery} from "react-query";
|
|
2
|
+
import {getObjectHash} from "data-primals-engine/core";
|
|
3
|
+
import {useAuthContext} from "../contexts/AuthContext.jsx";
|
|
4
|
+
import {getUserHash, getUserId} from "data-primals-engine/data";
|
|
5
|
+
import {useTranslation} from "react-i18next";
|
|
6
|
+
import {useEffect, useState} from "react";
|
|
7
|
+
import {useUI} from "../contexts/UIContext.jsx";
|
|
8
|
+
import {useNotificationContext} from "../NotificationProvider.jsx";
|
|
9
|
+
|
|
10
|
+
export const useData = (model, filter, options) => {
|
|
11
|
+
const { me } = useAuthContext();
|
|
12
|
+
const { i18n, t } = useTranslation();
|
|
13
|
+
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
14
|
+
return useQuery([options.queryKey || model, filter, me], () => {
|
|
15
|
+
return fetch('/api/data/search?limit='+(options.limit||1)+'&lang='+(options.lang||lang)+'&_user=' + encodeURIComponent(getUserId(me)) + '&model='+model, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: JSON.stringify({filter}),
|
|
18
|
+
headers: { "Content-Type": "application/json"}}
|
|
19
|
+
)
|
|
20
|
+
.then(e => e.json())
|
|
21
|
+
.then(e => e.data);
|
|
22
|
+
}, {...options, enabled: typeof(options.enabled) === "undefined" ? !!getUserId(me) : options.enabled });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export const useAlerts = () => {
|
|
27
|
+
// 2. Supprimer l'état local 'alerts', il sera géré par le UIContext
|
|
28
|
+
// const [alerts, setAlerts] = useState([]);
|
|
29
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
30
|
+
const { me } = useAuthContext();
|
|
31
|
+
const { addNotification } = useNotificationContext(); // 3. Récupérer la fonction addNotification du contexte
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!me?.username || !addNotification) { // S'assurer que addNotification est disponible
|
|
35
|
+
if (isConnected) setIsConnected(false);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log("Attempting to connect to SSE for alerts...");
|
|
40
|
+
const eventSource = new EventSource('/api/alerts/subscribe');
|
|
41
|
+
|
|
42
|
+
eventSource.onopen = () => {
|
|
43
|
+
console.log('SSE connection established for alerts.');
|
|
44
|
+
setIsConnected(true);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
eventSource.onmessage = (event) => {
|
|
48
|
+
try {
|
|
49
|
+
const newAlert = JSON.parse(event.data);
|
|
50
|
+
|
|
51
|
+
if (newAlert.type === 'connection_established') {
|
|
52
|
+
console.log(`SSE Status: ${newAlert.message}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 4. Remplacer setAlerts par addNotification
|
|
57
|
+
// On crée un objet notification standard que votre système peut afficher.
|
|
58
|
+
if (newAlert.type === 'cron_alert') {
|
|
59
|
+
addNotification({
|
|
60
|
+
type: 'info', // ou 'success', 'warning' selon ce que votre système supporte
|
|
61
|
+
title: 'Alerte planifiée',
|
|
62
|
+
message: newAlert.message,
|
|
63
|
+
duration: 10000 // Durée d'affichage en ms (10 secondes)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// La notification native du navigateur peut rester si vous le souhaitez
|
|
68
|
+
/*if (Notification.permission === "granted") {
|
|
69
|
+
new Notification("Nouvelle Alerte", {
|
|
70
|
+
body: newAlert.message,
|
|
71
|
+
icon: "/logo.png"
|
|
72
|
+
});
|
|
73
|
+
}*/
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Failed to parse SSE data:", error);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
eventSource.onerror = (error) => {
|
|
81
|
+
console.error('SSE Error:', error);
|
|
82
|
+
setIsConnected(false);
|
|
83
|
+
eventSource.close();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return () => {
|
|
87
|
+
console.log("Closing SSE connection.");
|
|
88
|
+
eventSource.close();
|
|
89
|
+
setIsConnected(false);
|
|
90
|
+
};
|
|
91
|
+
}, [me?.username, addNotification]); // Ajouter addNotification aux dépendances
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
/*if ("Notification" in window && Notification.permission !== "denied") {
|
|
95
|
+
Notification.requestPermission();
|
|
96
|
+
}*/
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
// 5. Mettre à jour la valeur de retour. Le hook n'a plus besoin de retourner la liste des alertes.
|
|
100
|
+
return { isConnected };
|
|
101
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/hooks/useDebounce.js
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useDebounce(value, delay) {
|
|
5
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const handler = setTimeout(() => {
|
|
9
|
+
setDebouncedValue(value);
|
|
10
|
+
}, delay);
|
|
11
|
+
|
|
12
|
+
return () => {
|
|
13
|
+
clearTimeout(handler);
|
|
14
|
+
};
|
|
15
|
+
}, [value, delay]);
|
|
16
|
+
|
|
17
|
+
return debouncedValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default useDebounce;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// C:/Dev/hackersonline-engine/client/src/hooks/useDragAndDrop.js
|
|
2
|
+
import { useState, useRef, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
const useDragAndDrop = () => {
|
|
5
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
6
|
+
const draggedItemRef = useRef(null); // Changed name for clarity
|
|
7
|
+
const [dragOverContainerId, setDragOverContainerId] = useState(null);
|
|
8
|
+
// dropPosition might not be needed if we simplify
|
|
9
|
+
|
|
10
|
+
const handleNodeDragStart = useCallback((nodeId) => {
|
|
11
|
+
setIsDragging(true);
|
|
12
|
+
draggedItemRef.current = nodeId;
|
|
13
|
+
console.log("useDragAndDrop: Dragging node with ID:", nodeId);
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
const handleContainerDragEnter = useCallback((containerId) => {
|
|
17
|
+
setDragOverContainerId(containerId);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const handleContainerDragLeave = useCallback(() => {
|
|
21
|
+
setDragOverContainerId(null);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
// This is for the draggable item's onDragEnd
|
|
25
|
+
const handleItemDragEnd = useCallback(() => {
|
|
26
|
+
setIsDragging(false);
|
|
27
|
+
// Do NOT reset draggedItemRef.current here.
|
|
28
|
+
// It's needed by the onDrop handler of the container.
|
|
29
|
+
console.log("useDragAndDrop: Item drag ended. Current draggedItemRef:", draggedItemRef.current);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
// This is for the droppable container's onDrop
|
|
33
|
+
const finalizeDropAndGetData = useCallback(() => {
|
|
34
|
+
const droppedItemId = draggedItemRef.current;
|
|
35
|
+
|
|
36
|
+
// Reset state after retrieving the ID
|
|
37
|
+
setIsDragging(false);
|
|
38
|
+
draggedItemRef.current = null;
|
|
39
|
+
setDragOverContainerId(null);
|
|
40
|
+
// setDropPosition(null); // if you were using it
|
|
41
|
+
|
|
42
|
+
console.log("useDragAndDrop: Finalized drop. Dropped ID:", droppedItemId);
|
|
43
|
+
return droppedItemId; // Return the ID of the item that was dropped
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
isDragging,
|
|
48
|
+
draggedItemRef, // Expose the ref object
|
|
49
|
+
dragOverContainerId,
|
|
50
|
+
// dropPosition,
|
|
51
|
+
handleNodeDragStart,
|
|
52
|
+
handleContainerDragEnter,
|
|
53
|
+
handleContainerDragLeave,
|
|
54
|
+
handleItemDragEnd, // For draggable's onDragEnd
|
|
55
|
+
finalizeDropAndGetData // For droppable's onDrop
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default useDragAndDrop;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {useEffect, useMemo, useRef, useState} from "react";
|
|
2
|
+
|
|
3
|
+
function useLocalStorage(
|
|
4
|
+
key,
|
|
5
|
+
defaultValue,
|
|
6
|
+
options
|
|
7
|
+
) {
|
|
8
|
+
const opts = useMemo(() => {
|
|
9
|
+
return {
|
|
10
|
+
serializer: JSON.stringify,
|
|
11
|
+
parser: JSON.parse,
|
|
12
|
+
logger: console.log,
|
|
13
|
+
syncData: true,
|
|
14
|
+
...options,
|
|
15
|
+
};
|
|
16
|
+
}, [options]);
|
|
17
|
+
|
|
18
|
+
const { serializer, parser, logger, syncData } = opts;
|
|
19
|
+
|
|
20
|
+
const rawValueRef = useRef(null);
|
|
21
|
+
|
|
22
|
+
const [value, setValue] = useState(() => {
|
|
23
|
+
if (typeof window === "undefined") return defaultValue;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
rawValueRef.current = window.localStorage.getItem(key);
|
|
27
|
+
return rawValueRef.current
|
|
28
|
+
? parser(rawValueRef.current)
|
|
29
|
+
: defaultValue;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
logger(e);
|
|
32
|
+
return defaultValue;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (typeof window === "undefined") return;
|
|
38
|
+
|
|
39
|
+
const updateLocalStorage = () => {
|
|
40
|
+
// Browser ONLY dispatch storage events to other tabs, NOT current tab.
|
|
41
|
+
// We need to manually dispatch storage event for current tab
|
|
42
|
+
if (value !== undefined) {
|
|
43
|
+
const newValue = serializer(value);
|
|
44
|
+
const oldValue = rawValueRef.current;
|
|
45
|
+
rawValueRef.current = newValue;
|
|
46
|
+
window.localStorage.setItem(key, newValue);
|
|
47
|
+
window.dispatchEvent(
|
|
48
|
+
new StorageEvent("storage", {
|
|
49
|
+
storageArea: window.localStorage,
|
|
50
|
+
url: window.location.href,
|
|
51
|
+
key,
|
|
52
|
+
newValue,
|
|
53
|
+
oldValue,
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
window.localStorage.removeItem(key);
|
|
58
|
+
window.dispatchEvent(
|
|
59
|
+
new StorageEvent("storage", {
|
|
60
|
+
storageArea: window.localStorage,
|
|
61
|
+
url: window.location.href,
|
|
62
|
+
key,
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
updateLocalStorage();
|
|
70
|
+
} catch (e) {
|
|
71
|
+
logger(e);
|
|
72
|
+
}
|
|
73
|
+
}, [value]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!syncData) return;
|
|
77
|
+
|
|
78
|
+
const handleStorageChange = (e) => {
|
|
79
|
+
if (e.key !== key || e.storageArea !== window.localStorage) return;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (e.newValue !== rawValueRef.current) {
|
|
83
|
+
rawValueRef.current = e.newValue;
|
|
84
|
+
setValue(e.newValue ? parser(e.newValue) : undefined);
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
logger(e);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (typeof window === "undefined") return;
|
|
92
|
+
|
|
93
|
+
window.addEventListener("storage", handleStorageChange);
|
|
94
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
95
|
+
}, [key, syncData]);
|
|
96
|
+
|
|
97
|
+
return [value, setValue];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default useLocalStorage;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {useEffect, useRef} from "react";
|
|
2
|
+
import {useMutation} from "react-query";
|
|
3
|
+
|
|
4
|
+
export const useMount = (effect) => {
|
|
5
|
+
const mounted = useRef(false);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (mounted.current) {
|
|
9
|
+
return effect();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
mounted.current = true;
|
|
13
|
+
|
|
14
|
+
return () => {};
|
|
15
|
+
}, [mounted, effect]);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useStrictModeMutation(options){
|
|
19
|
+
|
|
20
|
+
// `hasRun` tracks if the mutation has already been executed
|
|
21
|
+
const hasRun = useRef(false);
|
|
22
|
+
|
|
23
|
+
// Setting up the mutation with React Query
|
|
24
|
+
const mutation = useMutation(options);
|
|
25
|
+
|
|
26
|
+
// Conditional mutation function to prevent double execution
|
|
27
|
+
const strictModeMutate = (variables) => {
|
|
28
|
+
if (!hasRun.current) {
|
|
29
|
+
hasRun.current = true;
|
|
30
|
+
mutation.mutate(variables);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Custom reset function to allow the mutation to be re-triggered
|
|
35
|
+
const reset = () => {
|
|
36
|
+
hasRun.current = false;
|
|
37
|
+
mutation.reset();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Return the mutation with `strictModeMutate` instead of `mutate`
|
|
41
|
+
return { ...mutation, mutate: strictModeMutate, reset };
|
|
42
|
+
}
|