@inspirer-dev/crm-dashboard 1.0.11 → 1.0.12
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/admin/src/components/ButtonsBuilder/index.tsx +259 -0
- package/admin/src/index.ts +26 -1
- package/dist/_chunks/{index-CNvqpnNt.mjs → index-BSDO36Pf.mjs} +1 -468
- package/dist/_chunks/index-CiwOzO0B.js +377 -0
- package/dist/_chunks/index-XE6toVNT.mjs +170 -0
- package/dist/_chunks/index-d16UivTb.js +170 -0
- package/dist/_chunks/utils-C6_ndVAZ.mjs +491 -0
- package/dist/_chunks/utils-CmonL0io.js +490 -0
- package/dist/admin/index.js +25 -1
- package/dist/admin/index.mjs +26 -2
- package/dist/server/index.js +5 -0
- package/dist/server/index.mjs +5 -0
- package/package.json +1 -1
- package/server/src/register.ts +6 -0
- package/dist/_chunks/index-DIwnSzER.js +0 -844
|
@@ -1,844 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
-
const React = require("react");
|
|
5
|
-
const designSystem = require("@strapi/design-system");
|
|
6
|
-
const icons = require("@strapi/icons");
|
|
7
|
-
const styled = require("styled-components");
|
|
8
|
-
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
9
|
-
const React__default = /* @__PURE__ */ _interopDefault(React);
|
|
10
|
-
const OPERATORS = [
|
|
11
|
-
{ value: "$eq", label: "=", description: "Equals" },
|
|
12
|
-
{ value: "$ne", label: "≠", description: "Not equals" },
|
|
13
|
-
{ value: "$gt", label: ">", description: "Greater than" },
|
|
14
|
-
{ value: "$lt", label: "<", description: "Less than" },
|
|
15
|
-
{ value: "$gte", label: "≥", description: "Greater or equal" },
|
|
16
|
-
{ value: "$lte", label: "≤", description: "Less or equal" },
|
|
17
|
-
{ value: "$in", label: "in", description: "In list" },
|
|
18
|
-
{ value: "$nin", label: "not in", description: "Not in list" },
|
|
19
|
-
{ value: "$exists", label: "exists", description: "Field exists" },
|
|
20
|
-
{ value: "$regex", label: "matches", description: "Regex match" }
|
|
21
|
-
];
|
|
22
|
-
const numberOperators = ["$eq", "$ne", "$gt", "$lt", "$gte", "$lte"];
|
|
23
|
-
const timeAgoOperators = ["$gt", "$lt", "$gte", "$lte"];
|
|
24
|
-
const booleanOperators = ["$eq", "$ne"];
|
|
25
|
-
const stringOperators = ["$eq", "$ne", "$regex", "$in", "$nin"];
|
|
26
|
-
const existsOperators = ["$exists"];
|
|
27
|
-
const METRICS = [
|
|
28
|
-
{
|
|
29
|
-
key: "deposit_count",
|
|
30
|
-
label: "Deposit Count",
|
|
31
|
-
category: "counters",
|
|
32
|
-
valueType: "number",
|
|
33
|
-
description: "Total number of successful deposits",
|
|
34
|
-
operators: numberOperators
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
key: "case_open_count",
|
|
38
|
-
label: "Case Open Count",
|
|
39
|
-
category: "counters",
|
|
40
|
-
valueType: "number",
|
|
41
|
-
description: "Total cases opened",
|
|
42
|
-
operators: numberOperators
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
key: "upgrade_count",
|
|
46
|
-
label: "Upgrade Count",
|
|
47
|
-
category: "counters",
|
|
48
|
-
valueType: "number",
|
|
49
|
-
description: "Total upgrades performed",
|
|
50
|
-
operators: numberOperators
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
key: "battle_count",
|
|
54
|
-
label: "Battle Count",
|
|
55
|
-
category: "counters",
|
|
56
|
-
valueType: "number",
|
|
57
|
-
description: "Total battles created/participated",
|
|
58
|
-
operators: numberOperators
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
key: "contract_count",
|
|
62
|
-
label: "Contract Count",
|
|
63
|
-
category: "counters",
|
|
64
|
-
valueType: "number",
|
|
65
|
-
description: "Total contracts executed",
|
|
66
|
-
operators: numberOperators
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
key: "withdraw_count",
|
|
70
|
-
label: "Withdraw Count",
|
|
71
|
-
category: "counters",
|
|
72
|
-
valueType: "number",
|
|
73
|
-
description: "Total withdrawals",
|
|
74
|
-
operators: numberOperators
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
key: "session_count",
|
|
78
|
-
label: "Session Count",
|
|
79
|
-
category: "counters",
|
|
80
|
-
valueType: "number",
|
|
81
|
-
description: "Total sessions",
|
|
82
|
-
operators: numberOperators
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
key: "consecutive_active_days",
|
|
86
|
-
label: "Consecutive Active Days",
|
|
87
|
-
category: "counters",
|
|
88
|
-
valueType: "number",
|
|
89
|
-
description: "Days active in a row",
|
|
90
|
-
operators: numberOperators
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
key: "deposit_sum_total",
|
|
94
|
-
label: "Total Deposits Sum",
|
|
95
|
-
category: "money",
|
|
96
|
-
valueType: "number",
|
|
97
|
-
description: "Sum of all deposits in RUB",
|
|
98
|
-
operators: numberOperators
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
key: "withdraw_sum_total",
|
|
102
|
-
label: "Total Withdrawals Sum",
|
|
103
|
-
category: "money",
|
|
104
|
-
valueType: "number",
|
|
105
|
-
description: "Sum of all withdrawals",
|
|
106
|
-
operators: numberOperators
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
key: "win_sum_total",
|
|
110
|
-
label: "Total Wins Sum",
|
|
111
|
-
category: "money",
|
|
112
|
-
valueType: "number",
|
|
113
|
-
description: "Sum of all winnings",
|
|
114
|
-
operators: numberOperators
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
key: "avg_deposit",
|
|
118
|
-
label: "Average Deposit",
|
|
119
|
-
category: "money",
|
|
120
|
-
valueType: "number",
|
|
121
|
-
description: "Average deposit amount",
|
|
122
|
-
operators: numberOperators
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
key: "balance",
|
|
126
|
-
label: "Current Balance",
|
|
127
|
-
category: "money",
|
|
128
|
-
valueType: "number",
|
|
129
|
-
description: "Current account balance",
|
|
130
|
-
operators: numberOperators
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
key: "net_deposit",
|
|
134
|
-
label: "Net Deposit",
|
|
135
|
-
category: "money",
|
|
136
|
-
valueType: "number",
|
|
137
|
-
description: "Deposits minus withdrawals",
|
|
138
|
-
operators: numberOperators
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
key: "last_deposit_at",
|
|
142
|
-
label: "Last Deposit",
|
|
143
|
-
category: "time",
|
|
144
|
-
valueType: "time_ago",
|
|
145
|
-
description: "Time since last deposit",
|
|
146
|
-
operators: timeAgoOperators
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
key: "last_case_open_at",
|
|
150
|
-
label: "Last Case Open",
|
|
151
|
-
category: "time",
|
|
152
|
-
valueType: "time_ago",
|
|
153
|
-
description: "Time since last case opened",
|
|
154
|
-
operators: timeAgoOperators
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
key: "last_upgrade_at",
|
|
158
|
-
label: "Last Upgrade",
|
|
159
|
-
category: "time",
|
|
160
|
-
valueType: "time_ago",
|
|
161
|
-
description: "Time since last upgrade",
|
|
162
|
-
operators: timeAgoOperators
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
key: "last_battle_at",
|
|
166
|
-
label: "Last Battle",
|
|
167
|
-
category: "time",
|
|
168
|
-
valueType: "time_ago",
|
|
169
|
-
description: "Time since last battle",
|
|
170
|
-
operators: timeAgoOperators
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
key: "last_contract_at",
|
|
174
|
-
label: "Last Contract",
|
|
175
|
-
category: "time",
|
|
176
|
-
valueType: "time_ago",
|
|
177
|
-
description: "Time since last contract",
|
|
178
|
-
operators: timeAgoOperators
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
key: "last_withdraw_at",
|
|
182
|
-
label: "Last Withdrawal",
|
|
183
|
-
category: "time",
|
|
184
|
-
valueType: "time_ago",
|
|
185
|
-
description: "Time since last withdrawal",
|
|
186
|
-
operators: timeAgoOperators
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
key: "last_session_at",
|
|
190
|
-
label: "Last Activity",
|
|
191
|
-
category: "time",
|
|
192
|
-
valueType: "time_ago",
|
|
193
|
-
description: "Time since last session/activity",
|
|
194
|
-
operators: timeAgoOperators
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
key: "registered_at",
|
|
198
|
-
label: "Registration Date",
|
|
199
|
-
category: "time",
|
|
200
|
-
valueType: "time_ago",
|
|
201
|
-
description: "Time since registration",
|
|
202
|
-
operators: timeAgoOperators
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
key: "first_deposit_at",
|
|
206
|
-
label: "First Deposit Date",
|
|
207
|
-
category: "time",
|
|
208
|
-
valueType: "time_ago",
|
|
209
|
-
description: "Time since first deposit",
|
|
210
|
-
operators: [...timeAgoOperators, ...existsOperators]
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
key: "is_vip",
|
|
214
|
-
label: "Is VIP",
|
|
215
|
-
category: "status",
|
|
216
|
-
valueType: "boolean",
|
|
217
|
-
description: "VIP status",
|
|
218
|
-
operators: booleanOperators
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
key: "is_ftd",
|
|
222
|
-
label: "Is FTD (First-Time Depositor)",
|
|
223
|
-
category: "status",
|
|
224
|
-
valueType: "boolean",
|
|
225
|
-
description: "Has exactly 1 deposit",
|
|
226
|
-
operators: booleanOperators
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
key: "has_telegram",
|
|
230
|
-
label: "Has Telegram",
|
|
231
|
-
category: "status",
|
|
232
|
-
valueType: "boolean",
|
|
233
|
-
description: "Telegram connected",
|
|
234
|
-
operators: booleanOperators
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
key: "telegram_opt_out",
|
|
238
|
-
label: "Telegram Opt-Out",
|
|
239
|
-
category: "status",
|
|
240
|
-
valueType: "boolean",
|
|
241
|
-
description: "User opted out of TG messages",
|
|
242
|
-
operators: booleanOperators
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
key: "has_free_case",
|
|
246
|
-
label: "Has Free Case",
|
|
247
|
-
category: "status",
|
|
248
|
-
valueType: "boolean",
|
|
249
|
-
description: "Has available free case",
|
|
250
|
-
operators: booleanOperators
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
key: "lifecycle_stage",
|
|
254
|
-
label: "Lifecycle Stage",
|
|
255
|
-
category: "activity",
|
|
256
|
-
valueType: "string",
|
|
257
|
-
description: "New, Active, Sleep, Churn, Dead",
|
|
258
|
-
operators: stringOperators
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
key: "referral_source",
|
|
262
|
-
label: "Referral Source",
|
|
263
|
-
category: "activity",
|
|
264
|
-
valueType: "string",
|
|
265
|
-
description: "How user was acquired",
|
|
266
|
-
operators: stringOperators
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
key: "country",
|
|
270
|
-
label: "Country",
|
|
271
|
-
category: "activity",
|
|
272
|
-
valueType: "string",
|
|
273
|
-
description: "User country code",
|
|
274
|
-
operators: stringOperators
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
key: "timezone",
|
|
278
|
-
label: "Timezone",
|
|
279
|
-
category: "activity",
|
|
280
|
-
valueType: "string",
|
|
281
|
-
description: "User timezone",
|
|
282
|
-
operators: stringOperators
|
|
283
|
-
}
|
|
284
|
-
];
|
|
285
|
-
const METRICS_BY_CATEGORY = METRICS.reduce(
|
|
286
|
-
(acc, metric) => {
|
|
287
|
-
if (!acc[metric.category]) {
|
|
288
|
-
acc[metric.category] = [];
|
|
289
|
-
}
|
|
290
|
-
acc[metric.category].push(metric);
|
|
291
|
-
return acc;
|
|
292
|
-
},
|
|
293
|
-
{}
|
|
294
|
-
);
|
|
295
|
-
const CATEGORY_LABELS = {
|
|
296
|
-
counters: "📊 Counters",
|
|
297
|
-
money: "💰 Money",
|
|
298
|
-
time: "⏰ Time-based",
|
|
299
|
-
status: "🏷️ Status",
|
|
300
|
-
activity: "📱 Activity & Profile"
|
|
301
|
-
};
|
|
302
|
-
const LIFECYCLE_STAGES = ["new", "active", "sleep", "churn", "dead"];
|
|
303
|
-
const TIME_UNITS = [
|
|
304
|
-
{ value: "minutes_ago", label: "minutes ago" },
|
|
305
|
-
{ value: "hours_ago", label: "hours ago" },
|
|
306
|
-
{ value: "days_ago", label: "days ago" }
|
|
307
|
-
];
|
|
308
|
-
const DEFAULT_RULE = {
|
|
309
|
-
field: "deposit_count",
|
|
310
|
-
operator: "$eq",
|
|
311
|
-
value: 0
|
|
312
|
-
};
|
|
313
|
-
let idCounter = 0;
|
|
314
|
-
const generateId = () => {
|
|
315
|
-
idCounter += 1;
|
|
316
|
-
return `rule_${Date.now()}_${idCounter}`;
|
|
317
|
-
};
|
|
318
|
-
const createEmptyRule = () => ({
|
|
319
|
-
id: generateId(),
|
|
320
|
-
...DEFAULT_RULE
|
|
321
|
-
});
|
|
322
|
-
const createEmptyGroup = (logic = "$and") => ({
|
|
323
|
-
id: generateId(),
|
|
324
|
-
logic,
|
|
325
|
-
rules: [createEmptyRule()]
|
|
326
|
-
});
|
|
327
|
-
const createInitialConfig = () => ({
|
|
328
|
-
id: generateId(),
|
|
329
|
-
logic: "$and",
|
|
330
|
-
rules: []
|
|
331
|
-
});
|
|
332
|
-
const isRuleGroup = (item) => {
|
|
333
|
-
return "logic" in item && "rules" in item;
|
|
334
|
-
};
|
|
335
|
-
const getMetricDefinition = (key) => {
|
|
336
|
-
return METRICS.find((m) => m.key === key);
|
|
337
|
-
};
|
|
338
|
-
const isTimeAgoValue = (value) => {
|
|
339
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
return "hours_ago" in value || "days_ago" in value || "minutes_ago" in value;
|
|
343
|
-
};
|
|
344
|
-
const parseTimeAgoValue = (value) => {
|
|
345
|
-
if (!isTimeAgoValue(value)) {
|
|
346
|
-
return { amount: 24, unit: "hours_ago" };
|
|
347
|
-
}
|
|
348
|
-
if (value.minutes_ago !== void 0) {
|
|
349
|
-
return { amount: value.minutes_ago, unit: "minutes_ago" };
|
|
350
|
-
}
|
|
351
|
-
if (value.hours_ago !== void 0) {
|
|
352
|
-
return { amount: value.hours_ago, unit: "hours_ago" };
|
|
353
|
-
}
|
|
354
|
-
if (value.days_ago !== void 0) {
|
|
355
|
-
return { amount: value.days_ago, unit: "days_ago" };
|
|
356
|
-
}
|
|
357
|
-
return { amount: 24, unit: "hours_ago" };
|
|
358
|
-
};
|
|
359
|
-
const createTimeAgoValue = (amount, unit) => {
|
|
360
|
-
return { [unit]: amount };
|
|
361
|
-
};
|
|
362
|
-
const serializeConfig = (config) => {
|
|
363
|
-
return JSON.stringify(config, null, 2);
|
|
364
|
-
};
|
|
365
|
-
const deserializeConfig = (json) => {
|
|
366
|
-
if (!json) {
|
|
367
|
-
return createInitialConfig();
|
|
368
|
-
}
|
|
369
|
-
try {
|
|
370
|
-
const parsed = typeof json === "string" ? JSON.parse(json) : json;
|
|
371
|
-
if (parsed && typeof parsed === "object" && "logic" in parsed && "rules" in parsed) {
|
|
372
|
-
return parsed;
|
|
373
|
-
}
|
|
374
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
375
|
-
const rules = Object.entries(parsed).map(([field, condition]) => {
|
|
376
|
-
const condObj = condition;
|
|
377
|
-
const operator = Object.keys(condObj)[0];
|
|
378
|
-
const value = condObj[operator];
|
|
379
|
-
return {
|
|
380
|
-
id: generateId(),
|
|
381
|
-
field,
|
|
382
|
-
operator,
|
|
383
|
-
value
|
|
384
|
-
};
|
|
385
|
-
});
|
|
386
|
-
return {
|
|
387
|
-
id: generateId(),
|
|
388
|
-
logic: "$and",
|
|
389
|
-
rules
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
return createInitialConfig();
|
|
393
|
-
} catch {
|
|
394
|
-
return createInitialConfig();
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
const updateRuleInGroup = (group, ruleId, updater) => {
|
|
398
|
-
return {
|
|
399
|
-
...group,
|
|
400
|
-
rules: group.rules.map((item) => {
|
|
401
|
-
if (isRuleGroup(item)) {
|
|
402
|
-
return updateRuleInGroup(item, ruleId, updater);
|
|
403
|
-
}
|
|
404
|
-
if (item.id === ruleId) {
|
|
405
|
-
return updater(item);
|
|
406
|
-
}
|
|
407
|
-
return item;
|
|
408
|
-
})
|
|
409
|
-
};
|
|
410
|
-
};
|
|
411
|
-
const deleteRuleFromGroup = (group, ruleId) => {
|
|
412
|
-
const filtered = group.rules.map((item) => {
|
|
413
|
-
if (isRuleGroup(item)) {
|
|
414
|
-
const updated = deleteRuleFromGroup(item, ruleId);
|
|
415
|
-
if (updated.rules.length === 0) {
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
return updated;
|
|
419
|
-
}
|
|
420
|
-
if (item.id === ruleId) {
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
return item;
|
|
424
|
-
}).filter(Boolean);
|
|
425
|
-
return { ...group, rules: filtered };
|
|
426
|
-
};
|
|
427
|
-
const addRuleToGroup = (group, groupId, rule) => {
|
|
428
|
-
if (group.id === groupId) {
|
|
429
|
-
return {
|
|
430
|
-
...group,
|
|
431
|
-
rules: [...group.rules, rule]
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
...group,
|
|
436
|
-
rules: group.rules.map((item) => {
|
|
437
|
-
if (isRuleGroup(item)) {
|
|
438
|
-
return addRuleToGroup(item, groupId, rule);
|
|
439
|
-
}
|
|
440
|
-
return item;
|
|
441
|
-
})
|
|
442
|
-
};
|
|
443
|
-
};
|
|
444
|
-
const updateGroupLogic = (group, groupId, logic) => {
|
|
445
|
-
if (group.id === groupId) {
|
|
446
|
-
return { ...group, logic };
|
|
447
|
-
}
|
|
448
|
-
return {
|
|
449
|
-
...group,
|
|
450
|
-
rules: group.rules.map((item) => {
|
|
451
|
-
if (isRuleGroup(item)) {
|
|
452
|
-
return updateGroupLogic(item, groupId, logic);
|
|
453
|
-
}
|
|
454
|
-
return item;
|
|
455
|
-
})
|
|
456
|
-
};
|
|
457
|
-
};
|
|
458
|
-
const deleteGroupFromParent = (group, groupId) => {
|
|
459
|
-
const filtered = group.rules.map((item) => {
|
|
460
|
-
if (isRuleGroup(item)) {
|
|
461
|
-
if (item.id === groupId) {
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
return deleteGroupFromParent(item, groupId);
|
|
465
|
-
}
|
|
466
|
-
return item;
|
|
467
|
-
}).filter(Boolean);
|
|
468
|
-
return { ...group, rules: filtered };
|
|
469
|
-
};
|
|
470
|
-
const countRules = (group) => {
|
|
471
|
-
return group.rules.reduce((count, item) => {
|
|
472
|
-
if (isRuleGroup(item)) {
|
|
473
|
-
return count + countRules(item);
|
|
474
|
-
}
|
|
475
|
-
return count + 1;
|
|
476
|
-
}, 0);
|
|
477
|
-
};
|
|
478
|
-
const ValueInput = ({ metric, value, operator, onChange, disabled }) => {
|
|
479
|
-
if (operator === "$exists") {
|
|
480
|
-
const boolVal = value === true || value === "true";
|
|
481
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
482
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
483
|
-
designSystem.Switch,
|
|
484
|
-
{
|
|
485
|
-
label: "",
|
|
486
|
-
selected: boolVal,
|
|
487
|
-
onChange: () => onChange(!boolVal),
|
|
488
|
-
disabled
|
|
489
|
-
}
|
|
490
|
-
),
|
|
491
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: boolVal ? "exists" : "not exists" })
|
|
492
|
-
] });
|
|
493
|
-
}
|
|
494
|
-
if (metric.valueType === "boolean") {
|
|
495
|
-
const boolVal = value === true || value === "true";
|
|
496
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
497
|
-
designSystem.SingleSelect,
|
|
498
|
-
{
|
|
499
|
-
value: boolVal ? "true" : "false",
|
|
500
|
-
onChange: (val) => onChange(val === "true"),
|
|
501
|
-
disabled,
|
|
502
|
-
size: "S",
|
|
503
|
-
children: [
|
|
504
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "true", children: "Yes" }),
|
|
505
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "false", children: "No" })
|
|
506
|
-
]
|
|
507
|
-
}
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
if (metric.valueType === "time_ago") {
|
|
511
|
-
const { amount, unit } = parseTimeAgoValue(value);
|
|
512
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
513
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "80px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
514
|
-
designSystem.TextInput,
|
|
515
|
-
{
|
|
516
|
-
type: "number",
|
|
517
|
-
value: String(amount),
|
|
518
|
-
onChange: (e) => {
|
|
519
|
-
const num = parseInt(e.target.value, 10) || 0;
|
|
520
|
-
onChange(createTimeAgoValue(num, unit));
|
|
521
|
-
},
|
|
522
|
-
disabled,
|
|
523
|
-
size: "S",
|
|
524
|
-
"aria-label": "Time amount"
|
|
525
|
-
}
|
|
526
|
-
) }),
|
|
527
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "120px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
528
|
-
designSystem.SingleSelect,
|
|
529
|
-
{
|
|
530
|
-
value: unit,
|
|
531
|
-
onChange: (val) => onChange(createTimeAgoValue(amount, val)),
|
|
532
|
-
disabled,
|
|
533
|
-
size: "S",
|
|
534
|
-
children: TIME_UNITS.map((u) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: u.value, children: u.label }, u.value))
|
|
535
|
-
}
|
|
536
|
-
) })
|
|
537
|
-
] });
|
|
538
|
-
}
|
|
539
|
-
if (metric.key === "lifecycle_stage") {
|
|
540
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
541
|
-
designSystem.SingleSelect,
|
|
542
|
-
{
|
|
543
|
-
value: String(value || ""),
|
|
544
|
-
onChange: (val) => onChange(val),
|
|
545
|
-
disabled,
|
|
546
|
-
size: "S",
|
|
547
|
-
children: LIFECYCLE_STAGES.map((stage) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: stage, children: stage.charAt(0).toUpperCase() + stage.slice(1) }, stage))
|
|
548
|
-
}
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
if (metric.valueType === "number") {
|
|
552
|
-
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "120px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
553
|
-
designSystem.TextInput,
|
|
554
|
-
{
|
|
555
|
-
type: "number",
|
|
556
|
-
value: String(value ?? ""),
|
|
557
|
-
onChange: (e) => {
|
|
558
|
-
const num = parseFloat(e.target.value);
|
|
559
|
-
onChange(isNaN(num) ? 0 : num);
|
|
560
|
-
},
|
|
561
|
-
disabled,
|
|
562
|
-
size: "S",
|
|
563
|
-
"aria-label": "Value"
|
|
564
|
-
}
|
|
565
|
-
) });
|
|
566
|
-
}
|
|
567
|
-
if (operator === "$in" || operator === "$nin") {
|
|
568
|
-
const arrVal = Array.isArray(value) ? value.join(", ") : String(value || "");
|
|
569
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
570
|
-
designSystem.TextInput,
|
|
571
|
-
{
|
|
572
|
-
value: arrVal,
|
|
573
|
-
onChange: (e) => {
|
|
574
|
-
const arr = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
575
|
-
onChange(arr);
|
|
576
|
-
},
|
|
577
|
-
placeholder: "value1, value2, ...",
|
|
578
|
-
disabled,
|
|
579
|
-
size: "S",
|
|
580
|
-
"aria-label": "Values"
|
|
581
|
-
}
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
585
|
-
designSystem.TextInput,
|
|
586
|
-
{
|
|
587
|
-
value: String(value ?? ""),
|
|
588
|
-
onChange: (e) => onChange(e.target.value),
|
|
589
|
-
disabled,
|
|
590
|
-
size: "S",
|
|
591
|
-
"aria-label": "Value"
|
|
592
|
-
}
|
|
593
|
-
);
|
|
594
|
-
};
|
|
595
|
-
const RuleRow = ({ rule, onUpdate, onDelete, disabled }) => {
|
|
596
|
-
const theme = styled.useTheme();
|
|
597
|
-
const colors = theme?.colors;
|
|
598
|
-
const metric = getMetricDefinition(rule.field);
|
|
599
|
-
const availableOperators = metric?.operators || OPERATORS.map((o) => o.value);
|
|
600
|
-
const handleFieldChange = (fieldKey) => {
|
|
601
|
-
const newMetric = getMetricDefinition(fieldKey);
|
|
602
|
-
let newValue = 0;
|
|
603
|
-
if (newMetric?.valueType === "boolean") {
|
|
604
|
-
newValue = false;
|
|
605
|
-
} else if (newMetric?.valueType === "time_ago") {
|
|
606
|
-
newValue = { hours_ago: 24 };
|
|
607
|
-
} else if (newMetric?.valueType === "string") {
|
|
608
|
-
newValue = "";
|
|
609
|
-
}
|
|
610
|
-
const newOp = newMetric?.operators[0] || "$eq";
|
|
611
|
-
onUpdate({ ...rule, field: fieldKey, operator: newOp, value: newValue });
|
|
612
|
-
};
|
|
613
|
-
const handleOperatorChange = (op) => {
|
|
614
|
-
let newValue = rule.value;
|
|
615
|
-
if (op === "$exists") {
|
|
616
|
-
newValue = true;
|
|
617
|
-
} else if ((op === "$in" || op === "$nin") && !Array.isArray(rule.value)) {
|
|
618
|
-
newValue = [];
|
|
619
|
-
}
|
|
620
|
-
onUpdate({ ...rule, operator: op, value: newValue });
|
|
621
|
-
};
|
|
622
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
623
|
-
designSystem.Flex,
|
|
624
|
-
{
|
|
625
|
-
gap: 2,
|
|
626
|
-
alignItems: "center",
|
|
627
|
-
padding: 2,
|
|
628
|
-
background: "neutral0",
|
|
629
|
-
hasRadius: true,
|
|
630
|
-
style: { border: `1px solid ${colors?.neutral200 || "#dcdce4"}` },
|
|
631
|
-
children: [
|
|
632
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: "1 1 200px", minWidth: "180px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
633
|
-
designSystem.SingleSelect,
|
|
634
|
-
{
|
|
635
|
-
value: rule.field,
|
|
636
|
-
onChange: (val) => handleFieldChange(val),
|
|
637
|
-
disabled,
|
|
638
|
-
size: "S",
|
|
639
|
-
children: Object.entries(METRICS_BY_CATEGORY).map(([category, metrics]) => /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
|
|
640
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: `__header_${category}`, disabled: true, children: CATEGORY_LABELS[category] || category }),
|
|
641
|
-
metrics.map((m) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: m.key, children: m.label }, m.key))
|
|
642
|
-
] }, category))
|
|
643
|
-
}
|
|
644
|
-
) }),
|
|
645
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "100px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
646
|
-
designSystem.SingleSelect,
|
|
647
|
-
{
|
|
648
|
-
value: rule.operator,
|
|
649
|
-
onChange: (val) => handleOperatorChange(val),
|
|
650
|
-
disabled,
|
|
651
|
-
size: "S",
|
|
652
|
-
children: OPERATORS.filter((op) => availableOperators.includes(op.value)).map((op) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: op.value, children: op.label }, op.value))
|
|
653
|
-
}
|
|
654
|
-
) }),
|
|
655
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: "1 1 150px", minWidth: "120px" }, children: metric && /* @__PURE__ */ jsxRuntime.jsx(
|
|
656
|
-
ValueInput,
|
|
657
|
-
{
|
|
658
|
-
metric,
|
|
659
|
-
value: rule.value,
|
|
660
|
-
operator: rule.operator,
|
|
661
|
-
onChange: (val) => onUpdate({ ...rule, value: val }),
|
|
662
|
-
disabled
|
|
663
|
-
}
|
|
664
|
-
) }),
|
|
665
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: "Delete rule", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { onClick: onDelete, label: "Delete rule", variant: "ghost", disabled, children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) }) })
|
|
666
|
-
]
|
|
667
|
-
}
|
|
668
|
-
);
|
|
669
|
-
};
|
|
670
|
-
const RuleGroupComponent = ({
|
|
671
|
-
group,
|
|
672
|
-
onUpdate,
|
|
673
|
-
onDelete,
|
|
674
|
-
depth = 0,
|
|
675
|
-
disabled
|
|
676
|
-
}) => {
|
|
677
|
-
const theme = styled.useTheme();
|
|
678
|
-
const colors = theme?.colors;
|
|
679
|
-
const borderColors = {
|
|
680
|
-
0: colors?.primary200 || "#d9d8ff",
|
|
681
|
-
1: colors?.success200 || "#c6f0c2",
|
|
682
|
-
2: colors?.warning200 || "#fae7b9",
|
|
683
|
-
3: colors?.danger200 || "#f5c0b8"
|
|
684
|
-
};
|
|
685
|
-
const borderColor = borderColors[depth % 4] || borderColors[0];
|
|
686
|
-
const handleAddRule = () => {
|
|
687
|
-
onUpdate(addRuleToGroup(group, group.id, createEmptyRule()));
|
|
688
|
-
};
|
|
689
|
-
const handleAddGroup = () => {
|
|
690
|
-
const newLogic = group.logic === "$and" ? "$or" : "$and";
|
|
691
|
-
onUpdate(addRuleToGroup(group, group.id, createEmptyGroup(newLogic)));
|
|
692
|
-
};
|
|
693
|
-
const handleLogicToggle = () => {
|
|
694
|
-
onUpdate(updateGroupLogic(group, group.id, group.logic === "$and" ? "$or" : "$and"));
|
|
695
|
-
};
|
|
696
|
-
const handleRuleUpdate = (ruleId, updatedRule) => {
|
|
697
|
-
onUpdate(updateRuleInGroup(group, ruleId, () => updatedRule));
|
|
698
|
-
};
|
|
699
|
-
const handleRuleDelete = (ruleId) => {
|
|
700
|
-
onUpdate(deleteRuleFromGroup(group, ruleId));
|
|
701
|
-
};
|
|
702
|
-
const handleNestedGroupUpdate = (nestedGroup) => {
|
|
703
|
-
onUpdate({
|
|
704
|
-
...group,
|
|
705
|
-
rules: group.rules.map(
|
|
706
|
-
(item) => isRuleGroup(item) && item.id === nestedGroup.id ? nestedGroup : item
|
|
707
|
-
)
|
|
708
|
-
});
|
|
709
|
-
};
|
|
710
|
-
const handleNestedGroupDelete = (groupId) => {
|
|
711
|
-
onUpdate(deleteGroupFromParent(group, groupId));
|
|
712
|
-
};
|
|
713
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
714
|
-
designSystem.Box,
|
|
715
|
-
{
|
|
716
|
-
padding: 3,
|
|
717
|
-
background: depth === 0 ? "neutral100" : "neutral0",
|
|
718
|
-
hasRadius: true,
|
|
719
|
-
style: {
|
|
720
|
-
borderLeft: `3px solid ${borderColor}`,
|
|
721
|
-
marginLeft: depth > 0 ? "12px" : 0
|
|
722
|
-
},
|
|
723
|
-
children: [
|
|
724
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
|
|
725
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
726
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
727
|
-
designSystem.Button,
|
|
728
|
-
{
|
|
729
|
-
variant: group.logic === "$and" ? "default" : "secondary",
|
|
730
|
-
size: "S",
|
|
731
|
-
onClick: handleLogicToggle,
|
|
732
|
-
disabled,
|
|
733
|
-
children: group.logic === "$and" ? "AND" : "OR"
|
|
734
|
-
}
|
|
735
|
-
),
|
|
736
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: group.logic === "$and" ? "All conditions must match" : "Any condition can match" })
|
|
737
|
-
] }),
|
|
738
|
-
depth > 0 && onDelete && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: "Delete group", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { onClick: onDelete, label: "Delete group", variant: "ghost", disabled, children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) }) })
|
|
739
|
-
] }),
|
|
740
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: group.rules.map((item, index) => {
|
|
741
|
-
if (isRuleGroup(item)) {
|
|
742
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
743
|
-
RuleGroupComponent,
|
|
744
|
-
{
|
|
745
|
-
group: item,
|
|
746
|
-
onUpdate: handleNestedGroupUpdate,
|
|
747
|
-
onDelete: () => handleNestedGroupDelete(item.id),
|
|
748
|
-
depth: depth + 1,
|
|
749
|
-
disabled
|
|
750
|
-
},
|
|
751
|
-
item.id
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
755
|
-
RuleRow,
|
|
756
|
-
{
|
|
757
|
-
rule: item,
|
|
758
|
-
onUpdate: (updated) => handleRuleUpdate(item.id, updated),
|
|
759
|
-
onDelete: () => handleRuleDelete(item.id),
|
|
760
|
-
disabled
|
|
761
|
-
},
|
|
762
|
-
item.id
|
|
763
|
-
);
|
|
764
|
-
}) }),
|
|
765
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginTop: 3, children: [
|
|
766
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
767
|
-
designSystem.Button,
|
|
768
|
-
{
|
|
769
|
-
variant: "secondary",
|
|
770
|
-
size: "S",
|
|
771
|
-
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
|
|
772
|
-
onClick: handleAddRule,
|
|
773
|
-
disabled,
|
|
774
|
-
children: "Add condition"
|
|
775
|
-
}
|
|
776
|
-
),
|
|
777
|
-
depth < 2 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
778
|
-
designSystem.Button,
|
|
779
|
-
{
|
|
780
|
-
variant: "tertiary",
|
|
781
|
-
size: "S",
|
|
782
|
-
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Layout, {}),
|
|
783
|
-
onClick: handleAddGroup,
|
|
784
|
-
disabled,
|
|
785
|
-
children: [
|
|
786
|
-
"Add ",
|
|
787
|
-
group.logic === "$and" ? "OR" : "AND",
|
|
788
|
-
" group"
|
|
789
|
-
]
|
|
790
|
-
}
|
|
791
|
-
)
|
|
792
|
-
] })
|
|
793
|
-
]
|
|
794
|
-
}
|
|
795
|
-
);
|
|
796
|
-
};
|
|
797
|
-
const RulesBuilder = React.forwardRef(
|
|
798
|
-
({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
|
|
799
|
-
const [config, setConfig] = React.useState(() => deserializeConfig(value));
|
|
800
|
-
const displayLabel = React.useMemo(() => {
|
|
801
|
-
if (intlLabel?.defaultMessage && !intlLabel.defaultMessage.includes(".")) {
|
|
802
|
-
return intlLabel.defaultMessage;
|
|
803
|
-
}
|
|
804
|
-
return "Segment Rules";
|
|
805
|
-
}, [intlLabel]);
|
|
806
|
-
const rulesCount = React.useMemo(() => countRules(config), [config]);
|
|
807
|
-
const handleConfigUpdate = React.useCallback(
|
|
808
|
-
(newConfig) => {
|
|
809
|
-
setConfig(newConfig);
|
|
810
|
-
onChange({ target: { name, value: serializeConfig(newConfig) } });
|
|
811
|
-
},
|
|
812
|
-
[name, onChange]
|
|
813
|
-
);
|
|
814
|
-
const handleClear = () => {
|
|
815
|
-
handleConfigUpdate(createInitialConfig());
|
|
816
|
-
};
|
|
817
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name, error, hint, required, children: [
|
|
818
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
819
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: displayLabel }),
|
|
820
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
821
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { children: [
|
|
822
|
-
rulesCount,
|
|
823
|
-
" rule",
|
|
824
|
-
rulesCount !== 1 ? "s" : ""
|
|
825
|
-
] }),
|
|
826
|
-
rulesCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: handleClear, disabled, children: "Clear all" })
|
|
827
|
-
] })
|
|
828
|
-
] }),
|
|
829
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, ref, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
830
|
-
RuleGroupComponent,
|
|
831
|
-
{
|
|
832
|
-
group: config,
|
|
833
|
-
onUpdate: handleConfigUpdate,
|
|
834
|
-
depth: 0,
|
|
835
|
-
disabled
|
|
836
|
-
}
|
|
837
|
-
) }),
|
|
838
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
839
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
|
|
840
|
-
] });
|
|
841
|
-
}
|
|
842
|
-
);
|
|
843
|
-
RulesBuilder.displayName = "RulesBuilder";
|
|
844
|
-
exports.default = RulesBuilder;
|