@inspirer-dev/crm-dashboard 1.0.80 → 1.0.82

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 (46) hide show
  1. package/admin/src/components/ButtonsBuilder/index.tsx +3 -3
  2. package/admin/src/components/CancelConditionsField/index.tsx +7 -6
  3. package/admin/src/components/RulesBuilder/index.tsx +9 -8
  4. package/admin/src/components/StepFlowBuilder/constants.ts +2 -0
  5. package/admin/src/components/StepFlowBuilder/index.tsx +1 -1
  6. package/admin/src/components/StepFlowBuilder/nodes/EventTriggerNode.tsx +29 -1
  7. package/admin/src/components/StepFlowBuilder/panels/EventTriggerConfig.tsx +97 -9
  8. package/admin/src/components/StepFlowBuilder/panels/NodeEditPanel.tsx +1 -1
  9. package/admin/src/components/StepFlowBuilder/panels/WaitConfig.tsx +1 -3
  10. package/admin/src/components/StepFlowBuilder/types.ts +2 -0
  11. package/admin/src/components/TriggerConfigField/index.tsx +11 -10
  12. package/admin/src/components/TriggerParamsField/constants.ts +109 -0
  13. package/admin/src/components/TriggerParamsField/index.tsx +292 -0
  14. package/admin/src/index.ts +26 -1
  15. package/admin/src/pages/HomePage/components/CampaignBuilder/index.tsx +4 -0
  16. package/admin/src/pages/HomePage/components/FlowBuilderTest.tsx +3 -3
  17. package/admin/src/pages/HomePage/components/SendTimeHeatMap.tsx +2 -2
  18. package/admin/src/pages/HomePage/components/StatsView.tsx +2 -2
  19. package/admin/src/translations/en.json +4 -1
  20. package/admin/src/translations/ru.json +4 -1
  21. package/admin/tsconfig.json +12 -1
  22. package/admin/tsconfig.tsbuildinfo +1 -1
  23. package/dist/_chunks/{en-DEUgX5uV.mjs → en-B5BgIROW.mjs} +3 -1
  24. package/dist/_chunks/{en-D2kTkBns.js → en-GKZo4lia.js} +3 -1
  25. package/dist/_chunks/{index-C-1xCfqJ.js → index-354YMebI.js} +152 -38
  26. package/dist/_chunks/{index-BydXrDhA.mjs → index-BKfFI_Jo.mjs} +2 -2
  27. package/dist/_chunks/{index-D1kMmlrA.js → index-BQcRoIJr.js} +5 -4
  28. package/dist/_chunks/{index-BJzk6xNb.mjs → index-BgwX1xYS.mjs} +7 -6
  29. package/dist/_chunks/{index-pJUyH-Qr.js → index-Cg0G_bTE.js} +2 -2
  30. package/dist/_chunks/index-CuMY0eo5.js +310 -0
  31. package/dist/_chunks/{index-unxa4Q_H.mjs → index-DKJtyGq7.mjs} +5 -4
  32. package/dist/_chunks/{index-BFRbyVHC.js → index-DRXMKPXI.js} +1 -1
  33. package/dist/_chunks/{index-Xa_4jez0.mjs → index-Dhj0KzCX.mjs} +5 -4
  34. package/dist/_chunks/{index-Dv1tGmDT.mjs → index-LXBoz7PC.mjs} +1 -1
  35. package/dist/_chunks/index-NvFKi6er.mjs +310 -0
  36. package/dist/_chunks/{index-BKrTVHBr.js → index-T7VXjklK.js} +5 -4
  37. package/dist/_chunks/{index-CWSibOAr.mjs → index-gR86Z3uh.mjs} +152 -38
  38. package/dist/_chunks/{index-CAwynvu7.js → index-xDSMnUMp.js} +7 -6
  39. package/dist/_chunks/{ru-BKzplvmu.js → ru-bj5iiIPr.js} +3 -1
  40. package/dist/_chunks/{ru-DOt1yfNm.mjs → ru-h22ZdPCS.mjs} +3 -1
  41. package/dist/admin/index.js +31 -7
  42. package/dist/admin/index.mjs +32 -8
  43. package/dist/server/index.js +5 -0
  44. package/dist/server/index.mjs +5 -0
  45. package/package.json +3 -3
  46. package/server/src/register.ts +6 -0
@@ -0,0 +1,310 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useState, useEffect, useMemo } from "react";
3
+ import { Field, Flex, Box, Typography, Card, CardContent, NumberInput, Tooltip, IconButton, SingleSelect, SingleSelectOption, Button } from "@strapi/design-system";
4
+ import { Trash, Plus } 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 === "object" && !Array.isArray(value)) return value;
107
+ if (typeof value === "string") {
108
+ try {
109
+ const parsed = JSON.parse(value);
110
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
111
+ } catch {
112
+ }
113
+ }
114
+ return {};
115
+ };
116
+ const serialize = (params) => {
117
+ const clean = {};
118
+ for (const [key, val] of Object.entries(params)) {
119
+ if (val !== void 0 && val !== null) {
120
+ clean[key] = val;
121
+ }
122
+ }
123
+ return JSON.stringify(clean);
124
+ };
125
+ const useThemeColors = () => {
126
+ const theme = useTheme();
127
+ const isDark = theme?.colors?.neutral0 === "#212134";
128
+ return useMemo(
129
+ () => ({
130
+ isDark,
131
+ emptyBorder: isDark ? "#32324d" : "#dcdce4",
132
+ cardBorder: isDark ? "#32324d" : "#eaeaef",
133
+ tagBg: isDark ? "#2d2d4a" : "#f0f0ff"
134
+ }),
135
+ [isDark]
136
+ );
137
+ };
138
+ const TriggerParamsField = forwardRef(
139
+ ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
140
+ const [params, setParams] = useState(() => parseValue(value));
141
+ const colors = useThemeColors();
142
+ useEffect(() => {
143
+ setParams(parseValue(value));
144
+ }, [value]);
145
+ const update = (next) => {
146
+ setParams(next);
147
+ onChange({ target: { name, value: serialize(next) } });
148
+ };
149
+ const activeKeys = Object.keys(params).filter(
150
+ (k) => params[k] !== void 0 && params[k] !== null
151
+ );
152
+ const availableParams = TRIGGER_PARAMS.filter((p) => !activeKeys.includes(p.key));
153
+ const addParam = (key) => {
154
+ const def = TRIGGER_PARAMS.find((p) => p.key === key);
155
+ if (!def) return;
156
+ update({ ...params, [key]: void 0 });
157
+ };
158
+ const removeParam = (key) => {
159
+ const next = { ...params };
160
+ delete next[key];
161
+ update(next);
162
+ };
163
+ const setParamValue = (key, val) => {
164
+ update({ ...params, [key]: val });
165
+ };
166
+ const getParamDef = (key) => TRIGGER_PARAMS.find((p) => p.key === key);
167
+ return /* @__PURE__ */ jsx(Field.Root, { name, error, required, hint, ref, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
168
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { children: [
169
+ /* @__PURE__ */ jsx(Field.Label, { children: intlLabel?.defaultMessage || "Trigger Params" }),
170
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Настройте параметры срабатывания для этого этапа" })
171
+ ] }) }),
172
+ activeKeys.length === 0 ? /* @__PURE__ */ jsxs(
173
+ Box,
174
+ {
175
+ padding: 5,
176
+ background: "neutral100",
177
+ hasRadius: true,
178
+ style: {
179
+ border: `2px dashed ${colors.emptyBorder}`,
180
+ textAlign: "center"
181
+ },
182
+ children: [
183
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: "Параметры не настроены" }),
184
+ /* @__PURE__ */ jsx(
185
+ Typography,
186
+ {
187
+ variant: "pi",
188
+ textColor: "neutral400",
189
+ style: { display: "block", marginTop: 4 },
190
+ children: "Добавьте параметры триггера, чтобы управлять условиями показа этапа"
191
+ }
192
+ )
193
+ ]
194
+ }
195
+ ) : /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, children: activeKeys.map((key) => {
196
+ const def = getParamDef(key);
197
+ if (!def) return null;
198
+ return /* @__PURE__ */ jsx(
199
+ Card,
200
+ {
201
+ background: "neutral0",
202
+ style: {
203
+ border: `1px solid ${colors.cardBorder}`
204
+ },
205
+ children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", padding: 3, children: [
206
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1, minWidth: 0 }, children: [
207
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", marginBottom: 1, children: [
208
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "semiBold", children: def.label }),
209
+ def.unit && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
210
+ "(",
211
+ def.unit,
212
+ ")"
213
+ ] })
214
+ ] }),
215
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: def.description }),
216
+ /* @__PURE__ */ jsx(Flex, { gap: 1, marginTop: 1, wrap: "wrap", children: def.stageTypes.map((st) => /* @__PURE__ */ jsx(
217
+ Box,
218
+ {
219
+ style: {
220
+ padding: "1px 6px",
221
+ borderRadius: "4px",
222
+ backgroundColor: colors.tagBg,
223
+ fontSize: "11px"
224
+ },
225
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "primary600", children: STAGE_TYPE_LABELS[st] || st })
226
+ },
227
+ st
228
+ )) })
229
+ ] }),
230
+ /* @__PURE__ */ jsx(Box, { style: { width: 120, flexShrink: 0 }, children: /* @__PURE__ */ jsx(
231
+ NumberInput,
232
+ {
233
+ placeholder: def.placeholder,
234
+ value: params[key] ?? "",
235
+ onValueChange: (val) => setParamValue(key, val),
236
+ disabled,
237
+ size: "S",
238
+ step: key === "balanceThresholdMultiplier" ? 0.1 : 1
239
+ }
240
+ ) }),
241
+ /* @__PURE__ */ jsx(Tooltip, { label: "Удалить параметр", children: /* @__PURE__ */ jsx(
242
+ IconButton,
243
+ {
244
+ onClick: () => removeParam(key),
245
+ label: "Delete",
246
+ variant: "ghost",
247
+ size: "S",
248
+ disabled,
249
+ style: { color: "#d02b20", flexShrink: 0 },
250
+ children: /* @__PURE__ */ jsx(Trash, { width: 16, height: 16 })
251
+ }
252
+ ) })
253
+ ] }) })
254
+ },
255
+ key
256
+ );
257
+ }) }),
258
+ availableParams.length > 0 && /* @__PURE__ */ jsx(
259
+ AddParamSelect,
260
+ {
261
+ available: availableParams,
262
+ onAdd: addParam,
263
+ disabled
264
+ }
265
+ ),
266
+ error && /* @__PURE__ */ jsx(Field.Error, {}),
267
+ hint && /* @__PURE__ */ jsx(Field.Hint, {})
268
+ ] }) });
269
+ }
270
+ );
271
+ TriggerParamsField.displayName = "TriggerParamsField";
272
+ const AddParamSelect = ({ available, onAdd, disabled }) => {
273
+ const [selected, setSelected] = useState(null);
274
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-end", children: [
275
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
276
+ SingleSelect,
277
+ {
278
+ placeholder: "Выберите параметр...",
279
+ value: selected,
280
+ onChange: (val) => setSelected(val),
281
+ disabled,
282
+ size: "S",
283
+ children: available.map((p) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: p.key, children: [
284
+ p.label,
285
+ " — ",
286
+ p.description
287
+ ] }, p.key))
288
+ }
289
+ ) }),
290
+ /* @__PURE__ */ jsx(
291
+ Button,
292
+ {
293
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
294
+ onClick: () => {
295
+ if (selected) {
296
+ onAdd(selected);
297
+ setSelected(null);
298
+ }
299
+ },
300
+ disabled: disabled || !selected,
301
+ variant: "secondary",
302
+ size: "S",
303
+ children: "Добавить"
304
+ }
305
+ )
306
+ ] });
307
+ };
308
+ export {
309
+ TriggerParamsField as default
310
+ };
@@ -57,8 +57,9 @@ const ValueInput = ({
57
57
  /* @__PURE__ */ jsxRuntime.jsx(
58
58
  designSystem.Switch,
59
59
  {
60
- label: "",
61
- selected: boolVal,
60
+ onLabel: "",
61
+ offLabel: "",
62
+ checked: boolVal,
62
63
  onChange: () => onChange(!boolVal),
63
64
  disabled
64
65
  }
@@ -108,7 +109,7 @@ const ValueInput = ({
108
109
  designSystem.SingleSelect,
109
110
  {
110
111
  value: unit,
111
- onChange: (val) => onChange(utils.createTimeAgoValue(amount, val)),
112
+ onChange: (val) => onChange(utils.createTimeAgoValue(amount, String(val))),
112
113
  disabled,
113
114
  size: "S",
114
115
  children: utils.TIME_UNITS.map((u) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: u.value, children: u.label }, u.value))
@@ -235,7 +236,7 @@ const RuleRow = ({ rule, onUpdate, onDelete, disabled }) => {
235
236
  designSystem.SingleSelect,
236
237
  {
237
238
  value: rule.field,
238
- onChange: (val) => handleFieldChange(val),
239
+ onChange: (val) => handleFieldChange(String(val)),
239
240
  disabled,
240
241
  size: "S",
241
242
  children: Object.entries(utils.METRICS_BY_CATEGORY).map(([category, metrics]) => /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
@@ -139,7 +139,9 @@ const DEFAULT_NODE_DATA = {
139
139
  config: {
140
140
  eventTrigger: {
141
141
  events: [],
142
- logic: "or"
142
+ logic: "or",
143
+ timeout: 0,
144
+ timeoutUnit: "minutes"
143
145
  }
144
146
  }
145
147
  }
@@ -1087,12 +1089,24 @@ const EventTriggerNode = ({ id, data, selected }) => {
1087
1089
  const eventTrigger = data.config.eventTrigger;
1088
1090
  const events = eventTrigger?.events || [];
1089
1091
  const logic = eventTrigger?.logic || "or";
1092
+ const timeout = eventTrigger?.timeout || 0;
1093
+ const timeoutUnit = eventTrigger?.timeoutUnit || "minutes";
1090
1094
  const { hasError, hasWarning } = useNodeValidation(id);
1091
1095
  const getEventLabel2 = () => {
1092
1096
  if (events.length === 0) return "No events";
1093
1097
  if (events.length === 1) return events[0];
1094
1098
  return `${events.length} events (${logic.toUpperCase()})`;
1095
1099
  };
1100
+ const getTimeoutLabel = () => {
1101
+ if (timeout <= 0) return null;
1102
+ const unitShort = {
1103
+ seconds: "s",
1104
+ minutes: "m",
1105
+ hours: "h",
1106
+ days: "d"
1107
+ };
1108
+ return `${timeout}${unitShort[timeoutUnit] || timeoutUnit}`;
1109
+ };
1096
1110
  return /* @__PURE__ */ jsxs(
1097
1111
  "div",
1098
1112
  {
@@ -1174,7 +1188,7 @@ const EventTriggerNode = ({ id, data, selected }) => {
1174
1188
  children: data.name
1175
1189
  }
1176
1190
  ),
1177
- /* @__PURE__ */ jsx(
1191
+ /* @__PURE__ */ jsxs(
1178
1192
  "div",
1179
1193
  {
1180
1194
  style: {
@@ -1182,24 +1196,44 @@ const EventTriggerNode = ({ id, data, selected }) => {
1182
1196
  gap: 6,
1183
1197
  marginTop: 6
1184
1198
  },
1185
- children: /* @__PURE__ */ jsx(
1186
- "span",
1187
- {
1188
- style: {
1189
- fontSize: 10,
1190
- fontWeight: 600,
1191
- color: colors.border,
1192
- background: "rgba(255, 255, 255, 0.9)",
1193
- padding: "2px 10px",
1194
- borderRadius: 4,
1195
- maxWidth: 160,
1196
- overflow: "hidden",
1197
- textOverflow: "ellipsis",
1198
- whiteSpace: "nowrap"
1199
- },
1200
- children: getEventLabel2()
1201
- }
1202
- )
1199
+ children: [
1200
+ /* @__PURE__ */ jsx(
1201
+ "span",
1202
+ {
1203
+ style: {
1204
+ fontSize: 10,
1205
+ fontWeight: 600,
1206
+ color: colors.border,
1207
+ background: "rgba(255, 255, 255, 0.9)",
1208
+ padding: "2px 10px",
1209
+ borderRadius: 4,
1210
+ maxWidth: getTimeoutLabel() ? 120 : 160,
1211
+ overflow: "hidden",
1212
+ textOverflow: "ellipsis",
1213
+ whiteSpace: "nowrap"
1214
+ },
1215
+ children: getEventLabel2()
1216
+ }
1217
+ ),
1218
+ getTimeoutLabel() && /* @__PURE__ */ jsxs(
1219
+ "span",
1220
+ {
1221
+ style: {
1222
+ fontSize: 10,
1223
+ fontWeight: 600,
1224
+ color: "#d97706",
1225
+ background: "rgba(255, 255, 255, 0.9)",
1226
+ padding: "2px 8px",
1227
+ borderRadius: 4,
1228
+ whiteSpace: "nowrap"
1229
+ },
1230
+ children: [
1231
+ "⏱ ",
1232
+ getTimeoutLabel()
1233
+ ]
1234
+ }
1235
+ )
1236
+ ]
1203
1237
  }
1204
1238
  )
1205
1239
  ] }),
@@ -1711,8 +1745,7 @@ const WaitConfig = ({ data, onUpdate, disabled }) => {
1711
1745
  value: duration,
1712
1746
  onValueChange: handleDurationChange,
1713
1747
  disabled,
1714
- step: 1,
1715
- label: ""
1748
+ step: 1
1716
1749
  }
1717
1750
  ) })
1718
1751
  ] }),
@@ -1735,9 +1768,8 @@ const WaitConfig = ({ data, onUpdate, disabled }) => {
1735
1768
  SingleSelect,
1736
1769
  {
1737
1770
  value: durationUnit,
1738
- onChange: handleUnitChange,
1771
+ onChange: (val) => handleUnitChange(String(val)),
1739
1772
  disabled,
1740
- label: "",
1741
1773
  children: DURATION_UNIT_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SingleSelectOption, { value: opt.value, children: opt.label }, opt.value))
1742
1774
  }
1743
1775
  ) })
@@ -2377,6 +2409,8 @@ const EventTriggerConfig = ({ data, onUpdate, disabled }) => {
2377
2409
  const eventTrigger = data.config.eventTrigger || { events: [], logic: "or" };
2378
2410
  const events = eventTrigger.events || [];
2379
2411
  const logic = eventTrigger.logic || "or";
2412
+ const timeout = eventTrigger.timeout || 0;
2413
+ const timeoutUnit = eventTrigger.timeoutUnit || "minutes";
2380
2414
  const [inputText, setInputText] = useState("");
2381
2415
  const justSelectedRef = useRef(false);
2382
2416
  useEffect(() => {
@@ -2423,6 +2457,28 @@ const EventTriggerConfig = ({ data, onUpdate, disabled }) => {
2423
2457
  }
2424
2458
  });
2425
2459
  };
2460
+ const handleTimeoutChange = (value) => {
2461
+ onUpdate({
2462
+ config: {
2463
+ ...data.config,
2464
+ eventTrigger: {
2465
+ ...eventTrigger,
2466
+ timeout: value || 0
2467
+ }
2468
+ }
2469
+ });
2470
+ };
2471
+ const handleTimeoutUnitChange = (value) => {
2472
+ onUpdate({
2473
+ config: {
2474
+ ...data.config,
2475
+ eventTrigger: {
2476
+ ...eventTrigger,
2477
+ timeoutUnit: value
2478
+ }
2479
+ }
2480
+ });
2481
+ };
2426
2482
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
2427
2483
  /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
2428
2484
  /* @__PURE__ */ jsx(
@@ -2460,6 +2516,7 @@ const EventTriggerConfig = ({ data, onUpdate, disabled }) => {
2460
2516
  creatable: true,
2461
2517
  createMessage: (v) => `Использовать: "${v}"`,
2462
2518
  onCreateOption: (v) => {
2519
+ if (!v) return;
2463
2520
  handleAddEvent(v);
2464
2521
  },
2465
2522
  children: Object.entries(ANALYTICS_EVENTS).flatMap(
@@ -2601,6 +2658,51 @@ const EventTriggerConfig = ({ data, onUpdate, disabled }) => {
2601
2658
  }
2602
2659
  )
2603
2660
  ] }),
2661
+ /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
2662
+ /* @__PURE__ */ jsx(
2663
+ Typography,
2664
+ {
2665
+ variant: "sigma",
2666
+ style: {
2667
+ marginBottom: 10,
2668
+ display: "block",
2669
+ fontSize: 11,
2670
+ letterSpacing: "0.05em",
2671
+ color: theme.textMuted
2672
+ },
2673
+ children: "ТАЙМАУТ"
2674
+ }
2675
+ ),
2676
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
2677
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
2678
+ NumberInput,
2679
+ {
2680
+ value: timeout,
2681
+ onValueChange: handleTimeoutChange,
2682
+ disabled,
2683
+ step: 1
2684
+ }
2685
+ ) }),
2686
+ /* @__PURE__ */ jsx(Box, { style: { width: 140 }, children: /* @__PURE__ */ jsx(
2687
+ SingleSelect,
2688
+ {
2689
+ value: timeoutUnit,
2690
+ onChange: (val) => handleTimeoutUnitChange(String(val)),
2691
+ disabled,
2692
+ children: DURATION_UNIT_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SingleSelectOption, { value: opt.value, children: opt.label }, opt.value))
2693
+ }
2694
+ ) })
2695
+ ] }),
2696
+ /* @__PURE__ */ jsx(
2697
+ Typography,
2698
+ {
2699
+ variant: "pi",
2700
+ textColor: "neutral500",
2701
+ style: { marginTop: 8, display: "block" },
2702
+ children: timeout > 0 ? `Если событие не произойдёт за ${timeout} ${timeoutUnit}, кампания продолжится автоматически` : "Установите 0 для бесконечного ожидания события"
2703
+ }
2704
+ )
2705
+ ] }),
2604
2706
  /* @__PURE__ */ jsx(
2605
2707
  "div",
2606
2708
  {
@@ -2619,19 +2721,31 @@ const EventTriggerConfig = ({ data, onUpdate, disabled }) => {
2619
2721
  fontSize: 12,
2620
2722
  lineHeight: 1.5
2621
2723
  },
2622
- children: events.length === 0 ? "Выберите события, которые должен совершить пользователь для продолжения кампании." : logic === "or" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2623
- "Кампания продолжится, когда пользователь выполнит",
2624
- " ",
2625
- /* @__PURE__ */ jsx("strong", { children: "любое" }),
2626
- " из выбранных событий."
2627
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2628
- "Кампания продолжится только когда пользователь выполнит",
2629
- " ",
2630
- /* @__PURE__ */ jsxs("strong", { children: [
2631
- "все ",
2632
- events.length
2724
+ children: events.length === 0 ? "Выберите события, которые должен совершить пользователь для продолжения кампании." : /* @__PURE__ */ jsxs(Fragment, { children: [
2725
+ logic === "or" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2726
+ "Кампания продолжится, когда пользователь выполнит",
2727
+ " ",
2728
+ /* @__PURE__ */ jsx("strong", { children: "любое" }),
2729
+ " из выбранных событий"
2730
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2731
+ "Кампания продолжится только когда пользователь выполнит",
2732
+ " ",
2733
+ /* @__PURE__ */ jsxs("strong", { children: [
2734
+ "все ",
2735
+ events.length
2736
+ ] }),
2737
+ " выбранных события"
2633
2738
  ] }),
2634
- " выбранных события."
2739
+ timeout > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2740
+ " ",
2741
+ "или через ",
2742
+ /* @__PURE__ */ jsxs("strong", { children: [
2743
+ timeout,
2744
+ " ",
2745
+ timeoutUnit
2746
+ ] })
2747
+ ] }),
2748
+ "."
2635
2749
  ] })
2636
2750
  }
2637
2751
  )
@@ -2948,7 +3062,7 @@ const NodeEditPanel = ({
2948
3062
  {
2949
3063
  value: name,
2950
3064
  onChange: handleNameChange,
2951
- disabled: disabled || stepType === "entry",
3065
+ disabled,
2952
3066
  placeholder: "Enter step name..."
2953
3067
  }
2954
3068
  ) })
@@ -4668,7 +4782,7 @@ const StepFlowBuilderInner = forwardRef(
4668
4782
  }, [intlLabel]);
4669
4783
  const stepCount = steps.length;
4670
4784
  const validationError = useMemo(() => {
4671
- if (!validation || validation.isValid) return null;
4785
+ if (!validation || validation.isValid) return void 0;
4672
4786
  const errorCount = validation.errors.length;
4673
4787
  const errorWord = pluralize(errorCount, "ошибка", "ошибки", "ошибок");
4674
4788
  return `Поток содержит ${errorCount} ${errorWord} валидации. Исправьте все ошибки перед публикацией.`;
@@ -149,7 +149,8 @@ const ValueInput = ({ metric, value, operator, onChange, disabled }) => {
149
149
  /* @__PURE__ */ jsxRuntime.jsx(
150
150
  designSystem.Switch,
151
151
  {
152
- label: "",
152
+ onLabel: "",
153
+ offLabel: "",
153
154
  checked: boolVal,
154
155
  onChange: () => onChange(!boolVal),
155
156
  disabled
@@ -189,7 +190,7 @@ const ValueInput = ({ metric, value, operator, onChange, disabled }) => {
189
190
  designSystem.SingleSelect,
190
191
  {
191
192
  value: unit,
192
- onChange: (newUnit) => onChange({ [newUnit]: numValue }),
193
+ onChange: (newUnit) => onChange({ [String(newUnit)]: numValue }),
193
194
  disabled,
194
195
  children: CANCEL_TIME_UNITS.map((u) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: u.value, children: u.label }, u.value))
195
196
  }
@@ -228,7 +229,7 @@ const RuleRow = ({ rule, onUpdate, onDelete, disabled }) => {
228
229
  designSystem.SingleSelect,
229
230
  {
230
231
  value: rule.field,
231
- onChange: (val) => handleFieldChange(val),
232
+ onChange: (val) => handleFieldChange(String(val)),
232
233
  disabled,
233
234
  size: "S",
234
235
  children: CANCEL_METRICS.map((m) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: m.key, children: m.label }, m.key))
@@ -238,7 +239,7 @@ const RuleRow = ({ rule, onUpdate, onDelete, disabled }) => {
238
239
  designSystem.SingleSelect,
239
240
  {
240
241
  value: rule.operator,
241
- onChange: (val) => onUpdate({ ...rule, operator: val }),
242
+ onChange: (val) => onUpdate({ ...rule, operator: String(val) }),
242
243
  disabled,
243
244
  size: "S",
244
245
  children: OPERATORS.filter((op) => availableOperators.includes(op.value)).map((op) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: op.value, children: op.label }, op.value))
@@ -350,8 +351,8 @@ const CancelConditionsField = React.forwardRef(
350
351
  }
351
352
  ) })
352
353
  ] }) }) }),
353
- error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, { children: error }),
354
- hint && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: hint })
354
+ error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
355
+ hint && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
355
356
  ] }) });
356
357
  }
357
358
  );
@@ -81,6 +81,8 @@ const ru = {
81
81
  "crm-dashboard.template.title.label": "Заголовок",
82
82
  "crm-dashboard.template.title.description": "Для email: тема письма. Для Telegram: внутренний заголовок (не виден пользователю). Для push: заголовок уведомления.",
83
83
  "crm-dashboard.template.body.label": "Текст сообщения",
84
- "crm-dashboard.template.body.description": "Текст сообщения. Переменные: {{userName}}, {{balance}}, {{depositCount}}, {{depositTotal}}, {{amount}}. Markdown: *жирный*, _курсив_"
84
+ "crm-dashboard.template.body.description": "Текст сообщения. Переменные: {{userName}}, {{balance}}, {{depositCount}}, {{depositTotal}}, {{amount}}. Markdown: *жирный*, _курсив_",
85
+ "crm-dashboard.trigger-params.label": "Параметры триггера",
86
+ "crm-dashboard.trigger-params.description": "Настройте параметры срабатывания для этапов popup-флоу. Добавляйте только параметры, релевантные для данного типа этапа."
85
87
  };
86
88
  exports.default = ru;