@inspirer-dev/crm-dashboard 1.0.85 → 1.0.86

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.
@@ -0,0 +1,476 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React, { forwardRef, useRef, useState, useMemo } from "react";
3
+ import { Field, Flex, Box, Typography, Button, Tooltip, IconButton, SingleSelect, SingleSelectOption, Card, CardContent, NumberInput } from "@strapi/design-system";
4
+ import { Plus, Trash } from "@strapi/icons";
5
+ import { useTheme } from "styled-components";
6
+ const TRIGGER_PARAMS = [
7
+ {
8
+ key: "delaySeconds",
9
+ label: "Задержка",
10
+ description: "Задержка перед показом после срабатывания триггера",
11
+ unit: "сек",
12
+ min: 0,
13
+ placeholder: "7",
14
+ stageTypes: ["side_hint", "modal", "retry"]
15
+ },
16
+ {
17
+ key: "timeOnSiteSeconds",
18
+ label: "Время на сайте",
19
+ description: "Мин. время на сайте для срабатывания",
20
+ unit: "сек",
21
+ min: 0,
22
+ placeholder: "120",
23
+ stageTypes: ["exit_intent", "side_hint", "modal"]
24
+ },
25
+ {
26
+ key: "scrollThresholdPx",
27
+ label: "Порог скролла",
28
+ description: "Скролл в пикселях для срабатывания",
29
+ unit: "px",
30
+ min: 0,
31
+ placeholder: "200",
32
+ stageTypes: ["side_hint"]
33
+ },
34
+ {
35
+ key: "idleSeconds",
36
+ label: "Бездействие",
37
+ description: "Время без кликов/скролла",
38
+ unit: "сек",
39
+ min: 0,
40
+ placeholder: "15",
41
+ stageTypes: ["side_hint"]
42
+ },
43
+ {
44
+ key: "caseViewSeconds",
45
+ label: "Просмотр кейса",
46
+ description: "Время на странице кейса",
47
+ unit: "сек",
48
+ min: 0,
49
+ placeholder: "12",
50
+ stageTypes: ["modal", "side_hint"]
51
+ },
52
+ {
53
+ key: "activePlayMinutes",
54
+ label: "Активная игра",
55
+ description: "Время активной игры",
56
+ unit: "мин",
57
+ min: 0,
58
+ placeholder: "10",
59
+ stageTypes: ["modal"]
60
+ },
61
+ {
62
+ key: "actionCountThreshold",
63
+ label: "Порог действий",
64
+ description: "Кол-во значимых действий + низкий баланс",
65
+ unit: "",
66
+ min: 0,
67
+ placeholder: "2",
68
+ stageTypes: ["modal"]
69
+ },
70
+ {
71
+ key: "balanceThresholdMultiplier",
72
+ label: "Множитель баланса",
73
+ description: 'Множитель мин. цены кейса для "низкого баланса"',
74
+ unit: "×",
75
+ min: 0,
76
+ placeholder: "1.5",
77
+ stageTypes: ["modal", "side_hint"]
78
+ },
79
+ {
80
+ key: "abandonedTimeoutMinutes",
81
+ label: "Таймаут заброшенного депозита",
82
+ description: "Через сколько минут депозит считается заброшенным",
83
+ unit: "мин",
84
+ min: 1,
85
+ placeholder: "10",
86
+ stageTypes: ["retry"]
87
+ },
88
+ {
89
+ key: "minDepositAmount",
90
+ label: "Мин. сумма депозита",
91
+ description: "Подстановка в {{min_deposit}} в текстах",
92
+ unit: "₽",
93
+ min: 0,
94
+ placeholder: "250",
95
+ stageTypes: ["modal", "side_hint", "retry"]
96
+ }
97
+ ];
98
+ const STAGE_TYPE_LABELS = {
99
+ side_hint: "Side-hint",
100
+ modal: "Modal",
101
+ exit_intent: "Exit intent",
102
+ retry: "Retry"
103
+ };
104
+ const parseValue = (value) => {
105
+ if (!value) return [{}];
106
+ if (typeof value === "string") {
107
+ try {
108
+ const parsed = JSON.parse(value);
109
+ if (Array.isArray(parsed)) return parsed.length > 0 ? parsed : [{}];
110
+ if (typeof parsed === "object" && parsed !== null) return [parsed];
111
+ } catch {
112
+ }
113
+ return [{}];
114
+ }
115
+ if (Array.isArray(value)) return value.length > 0 ? value : [{}];
116
+ if (typeof value === "object") return [value];
117
+ return [{}];
118
+ };
119
+ const serialize = (groups) => {
120
+ const cleaned = groups.map((group) => {
121
+ const out = {};
122
+ for (const [key, val] of Object.entries(group)) {
123
+ if (typeof val === "number") out[key] = val;
124
+ }
125
+ return out;
126
+ }).filter((group) => Object.keys(group).length > 0);
127
+ return JSON.stringify(cleaned.length > 0 ? cleaned : []);
128
+ };
129
+ const DEPTH_BORDERS = ["#7b79ff", "#ee5e52", "#0c75af", "#328048"];
130
+ const useThemeColors = () => {
131
+ const theme = useTheme();
132
+ const isDark = theme?.colors?.neutral0 === "#212134";
133
+ return useMemo(
134
+ () => ({
135
+ isDark,
136
+ emptyBorder: isDark ? "#32324d" : "#dcdce4",
137
+ cardBorder: isDark ? "#32324d" : "#eaeaef",
138
+ tagBg: isDark ? "#2d2d4a" : "#f0f0ff",
139
+ groupBg: isDark ? "#1a1a2e" : "#f6f6f9",
140
+ orDividerLine: isDark ? "#32324d" : "#dcdce4",
141
+ andLabelColor: isDark ? "#a5a5ba" : "#666687",
142
+ depthBorders: DEPTH_BORDERS
143
+ }),
144
+ [isDark]
145
+ );
146
+ };
147
+ const getParamDef = (key) => TRIGGER_PARAMS.find((p) => p.key === key);
148
+ const ParamCard = ({
149
+ paramKey,
150
+ value,
151
+ onValueChange,
152
+ onRemove,
153
+ disabled,
154
+ colors
155
+ }) => {
156
+ const def = getParamDef(paramKey);
157
+ if (!def) return null;
158
+ return /* @__PURE__ */ jsx(Card, { background: "neutral0", style: { border: `1px solid ${colors.cardBorder}` }, children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", padding: 3, children: [
159
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1, minWidth: 0 }, children: [
160
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", marginBottom: 1, children: [
161
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "semiBold", children: def.label }),
162
+ def.unit && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
163
+ "(",
164
+ def.unit,
165
+ ")"
166
+ ] })
167
+ ] }),
168
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: def.description }),
169
+ /* @__PURE__ */ jsx(Flex, { gap: 1, marginTop: 1, wrap: "wrap", children: def.stageTypes.map((st) => /* @__PURE__ */ jsx(
170
+ Box,
171
+ {
172
+ style: {
173
+ padding: "1px 6px",
174
+ borderRadius: "4px",
175
+ backgroundColor: colors.tagBg,
176
+ fontSize: "11px"
177
+ },
178
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "primary600", children: STAGE_TYPE_LABELS[st] || st })
179
+ },
180
+ st
181
+ )) })
182
+ ] }),
183
+ /* @__PURE__ */ jsx(Box, { style: { width: 120, flexShrink: 0 }, children: /* @__PURE__ */ jsx(
184
+ NumberInput,
185
+ {
186
+ placeholder: def.placeholder,
187
+ value: value ?? "",
188
+ onValueChange,
189
+ disabled,
190
+ size: "S",
191
+ step: paramKey === "balanceThresholdMultiplier" ? 0.1 : 1
192
+ }
193
+ ) }),
194
+ /* @__PURE__ */ jsx(Tooltip, { label: "Удалить параметр", children: /* @__PURE__ */ jsx(
195
+ IconButton,
196
+ {
197
+ onClick: onRemove,
198
+ label: "Delete",
199
+ variant: "ghost",
200
+ size: "S",
201
+ disabled,
202
+ style: { color: "#d02b20", flexShrink: 0 },
203
+ children: /* @__PURE__ */ jsx(Trash, { width: 16, height: 16 })
204
+ }
205
+ ) })
206
+ ] }) }) });
207
+ };
208
+ const AndLabel = ({ colors }) => /* @__PURE__ */ jsx(Flex, { justifyContent: "center", paddingTop: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx(
209
+ Typography,
210
+ {
211
+ variant: "sigma",
212
+ textColor: "neutral600",
213
+ style: {
214
+ fontSize: "11px",
215
+ fontWeight: 700,
216
+ letterSpacing: "0.5px",
217
+ color: colors.andLabelColor,
218
+ textTransform: "uppercase"
219
+ },
220
+ children: "И"
221
+ }
222
+ ) });
223
+ const OrDivider = ({ colors }) => /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 3, paddingTop: 2, paddingBottom: 2, children: [
224
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, height: "1px", backgroundColor: colors.orDividerLine } }),
225
+ /* @__PURE__ */ jsx(
226
+ Box,
227
+ {
228
+ style: {
229
+ padding: "2px 12px",
230
+ borderRadius: "12px",
231
+ border: `1px solid ${colors.orDividerLine}`,
232
+ backgroundColor: colors.isDark ? "#212134" : "#ffffff"
233
+ },
234
+ children: /* @__PURE__ */ jsx(
235
+ Typography,
236
+ {
237
+ variant: "sigma",
238
+ style: {
239
+ fontSize: "11px",
240
+ fontWeight: 700,
241
+ letterSpacing: "0.5px",
242
+ color: colors.andLabelColor,
243
+ textTransform: "uppercase"
244
+ },
245
+ children: "ИЛИ"
246
+ }
247
+ )
248
+ }
249
+ ),
250
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, height: "1px", backgroundColor: colors.orDividerLine } })
251
+ ] });
252
+ const ParamGroupCard = ({
253
+ group,
254
+ groupIndex,
255
+ totalGroups,
256
+ onAddParam,
257
+ onRemoveParam,
258
+ onSetValue,
259
+ onRemoveGroup,
260
+ disabled,
261
+ colors
262
+ }) => {
263
+ const activeKeys = Object.keys(group);
264
+ const availableParams = TRIGGER_PARAMS.filter((p) => !activeKeys.includes(p.key));
265
+ const borderColor = colors.depthBorders[groupIndex % colors.depthBorders.length];
266
+ return /* @__PURE__ */ jsx(
267
+ Box,
268
+ {
269
+ padding: 4,
270
+ hasRadius: true,
271
+ style: {
272
+ backgroundColor: colors.groupBg,
273
+ borderLeft: `3px solid ${borderColor}`,
274
+ border: `1px solid ${colors.cardBorder}`,
275
+ borderLeftWidth: "3px",
276
+ borderLeftColor: borderColor
277
+ },
278
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
279
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
280
+ /* @__PURE__ */ jsxs(
281
+ Typography,
282
+ {
283
+ variant: "sigma",
284
+ textColor: "neutral700",
285
+ style: { textTransform: "uppercase", fontSize: "11px", letterSpacing: "0.5px" },
286
+ children: [
287
+ "Группа ",
288
+ groupIndex + 1
289
+ ]
290
+ }
291
+ ),
292
+ totalGroups > 1 && /* @__PURE__ */ jsx(Tooltip, { label: "Удалить группу", children: /* @__PURE__ */ jsx(
293
+ IconButton,
294
+ {
295
+ onClick: onRemoveGroup,
296
+ label: "Удалить группу",
297
+ variant: "ghost",
298
+ size: "S",
299
+ disabled,
300
+ style: { color: "#d02b20" },
301
+ children: /* @__PURE__ */ jsx(Trash, { width: 14, height: 14 })
302
+ }
303
+ ) })
304
+ ] }),
305
+ activeKeys.length === 0 ? /* @__PURE__ */ jsx(
306
+ Box,
307
+ {
308
+ padding: 3,
309
+ hasRadius: true,
310
+ style: {
311
+ border: `1px dashed ${colors.emptyBorder}`,
312
+ textAlign: "center"
313
+ },
314
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Добавьте параметры в группу" })
315
+ }
316
+ ) : activeKeys.map((key, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
317
+ i > 0 && /* @__PURE__ */ jsx(AndLabel, { colors }),
318
+ /* @__PURE__ */ jsx(
319
+ ParamCard,
320
+ {
321
+ paramKey: key,
322
+ value: group[key],
323
+ onValueChange: (val) => onSetValue(key, val),
324
+ onRemove: () => onRemoveParam(key),
325
+ disabled,
326
+ colors
327
+ }
328
+ )
329
+ ] }, key)),
330
+ availableParams.length > 0 && /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(AddParamSelect, { available: availableParams, onAdd: onAddParam, disabled }) })
331
+ ] })
332
+ }
333
+ );
334
+ };
335
+ const TriggerParamsField = forwardRef(
336
+ ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
337
+ const initialRef = useRef(value);
338
+ const [groups, setGroups] = useState(() => parseValue(initialRef.current));
339
+ const colors = useThemeColors();
340
+ const update = (next) => {
341
+ setGroups(next);
342
+ onChange({ target: { name, value: serialize(next) } });
343
+ };
344
+ const addGroup = () => {
345
+ update([...groups, {}]);
346
+ };
347
+ const removeGroup = (idx) => {
348
+ const next = groups.filter((_, i) => i !== idx);
349
+ update(next.length > 0 ? next : [{}]);
350
+ };
351
+ const addParamToGroup = (idx, key) => {
352
+ const next = groups.map((g, i) => i === idx ? { ...g, [key]: null } : g);
353
+ update(next);
354
+ };
355
+ const removeParamFromGroup = (idx, key) => {
356
+ const next = groups.map((g, i) => {
357
+ if (i !== idx) return g;
358
+ const copy = { ...g };
359
+ delete copy[key];
360
+ return copy;
361
+ });
362
+ update(next);
363
+ };
364
+ const setParamValue = (idx, key, val) => {
365
+ const next = groups.map((g, i) => i === idx ? { ...g, [key]: val ?? null } : g);
366
+ update(next);
367
+ };
368
+ const allEmpty = groups.every((g) => Object.keys(g).length === 0);
369
+ return /* @__PURE__ */ jsx(Field.Root, { name, error, required, hint, ref, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
370
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { children: [
371
+ /* @__PURE__ */ jsx(Field.Label, { children: intlLabel?.defaultMessage || "Trigger Params" }),
372
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Настройте параметры срабатывания для этого этапа" })
373
+ ] }) }),
374
+ groups.length === 1 && allEmpty ? /* @__PURE__ */ jsxs(
375
+ Box,
376
+ {
377
+ padding: 5,
378
+ background: "neutral100",
379
+ hasRadius: true,
380
+ style: {
381
+ border: `2px dashed ${colors.emptyBorder}`,
382
+ textAlign: "center"
383
+ },
384
+ children: [
385
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: "Параметры не настроены" }),
386
+ /* @__PURE__ */ jsx(
387
+ Typography,
388
+ {
389
+ variant: "pi",
390
+ textColor: "neutral400",
391
+ style: { display: "block", marginTop: 4 },
392
+ children: "Добавьте параметры триггера, чтобы управлять условиями показа этапа"
393
+ }
394
+ )
395
+ ]
396
+ }
397
+ ) : /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 0, children: groups.map((group, idx) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
398
+ idx > 0 && /* @__PURE__ */ jsx(OrDivider, { colors }),
399
+ /* @__PURE__ */ jsx(
400
+ ParamGroupCard,
401
+ {
402
+ group,
403
+ groupIndex: idx,
404
+ totalGroups: groups.length,
405
+ onAddParam: (key) => addParamToGroup(idx, key),
406
+ onRemoveParam: (key) => removeParamFromGroup(idx, key),
407
+ onSetValue: (key, val) => setParamValue(idx, key, val),
408
+ onRemoveGroup: () => removeGroup(idx),
409
+ disabled,
410
+ colors
411
+ }
412
+ )
413
+ ] }, idx)) }),
414
+ groups.length === 1 && allEmpty ? /* @__PURE__ */ jsx(
415
+ AddParamSelect,
416
+ {
417
+ available: TRIGGER_PARAMS,
418
+ onAdd: (key) => addParamToGroup(0, key),
419
+ disabled
420
+ }
421
+ ) : /* @__PURE__ */ jsx(
422
+ Button,
423
+ {
424
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
425
+ onClick: addGroup,
426
+ disabled,
427
+ variant: "tertiary",
428
+ size: "S",
429
+ children: "Добавить OR группу"
430
+ }
431
+ ),
432
+ error && /* @__PURE__ */ jsx(Field.Error, {}),
433
+ hint && /* @__PURE__ */ jsx(Field.Hint, {})
434
+ ] }) });
435
+ }
436
+ );
437
+ TriggerParamsField.displayName = "TriggerParamsField";
438
+ const AddParamSelect = ({ available, onAdd, disabled }) => {
439
+ const [selected, setSelected] = useState(null);
440
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-end", children: [
441
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
442
+ SingleSelect,
443
+ {
444
+ placeholder: "Выберите параметр...",
445
+ value: selected,
446
+ onChange: (val) => setSelected(val),
447
+ disabled,
448
+ size: "S",
449
+ children: available.map((p) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: p.key, children: [
450
+ p.label,
451
+ " — ",
452
+ p.description
453
+ ] }, p.key))
454
+ }
455
+ ) }),
456
+ /* @__PURE__ */ jsx(
457
+ Button,
458
+ {
459
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
460
+ onClick: () => {
461
+ if (selected) {
462
+ onAdd(selected);
463
+ setSelected(null);
464
+ }
465
+ },
466
+ disabled: disabled || !selected,
467
+ variant: "secondary",
468
+ size: "S",
469
+ children: "Добавить"
470
+ }
471
+ )
472
+ ] });
473
+ };
474
+ export {
475
+ TriggerParamsField as default
476
+ };
@@ -195,7 +195,10 @@ const ButtonsBuilder = React.forwardRef(
195
195
  designSystem.SingleSelect,
196
196
  {
197
197
  value: btn.type || "url",
198
- onChange: (val) => updateButton(btn.id, { type: val }),
198
+ onChange: (val) => updateButton(btn.id, {
199
+ type: val,
200
+ ...val === "url" ? { screenSlug: "" } : { url: "" }
201
+ }),
199
202
  disabled,
200
203
  size: "S",
201
204
  children: [
@@ -193,7 +193,10 @@ const ButtonsBuilder = forwardRef(
193
193
  SingleSelect,
194
194
  {
195
195
  value: btn.type || "url",
196
- onChange: (val) => updateButton(btn.id, { type: val }),
196
+ onChange: (val) => updateButton(btn.id, {
197
+ type: val,
198
+ ...val === "url" ? { screenSlug: "" } : { url: "" }
199
+ }),
197
200
  disabled,
198
201
  size: "S",
199
202
  children: [
@@ -107,7 +107,7 @@ const index = {
107
107
  components: {
108
108
  Input: async () => Promise.resolve().then(() => require(
109
109
  /* webpackChunkName: "crm-telegram-buttons" */
110
- "../_chunks/index-u-jR-zfs.js"
110
+ "../_chunks/index-CapXG1AZ.js"
111
111
  ))
112
112
  },
113
113
  options: {
@@ -155,7 +155,7 @@ const index = {
155
155
  components: {
156
156
  Input: async () => Promise.resolve().then(() => require(
157
157
  /* webpackChunkName: "crm-trigger-params" */
158
- "../_chunks/index-CqRPcaXV.js"
158
+ "../_chunks/index-BqcFfrh8.js"
159
159
  ))
160
160
  },
161
161
  options: {
@@ -106,7 +106,7 @@ const index = {
106
106
  components: {
107
107
  Input: async () => import(
108
108
  /* webpackChunkName: "crm-telegram-buttons" */
109
- "../_chunks/index-DRhcN7TG.mjs"
109
+ "../_chunks/index-ClbsgJmF.mjs"
110
110
  )
111
111
  },
112
112
  options: {
@@ -154,7 +154,7 @@ const index = {
154
154
  components: {
155
155
  Input: async () => import(
156
156
  /* webpackChunkName: "crm-trigger-params" */
157
- "../_chunks/index-DnyoVNLo.mjs"
157
+ "../_chunks/index-CKr1VvX0.mjs"
158
158
  )
159
159
  },
160
160
  options: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspirer-dev/crm-dashboard",
3
- "version": "1.0.85",
3
+ "version": "1.0.86",
4
4
  "description": "CRM Dashboard and Tools",
5
5
  "strapi": {
6
6
  "name": "crm-dashboard",