@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.
- package/admin/src/components/ButtonsBuilder/index.tsx +3 -3
- package/admin/src/components/CancelConditionsField/index.tsx +7 -6
- package/admin/src/components/RulesBuilder/index.tsx +9 -8
- package/admin/src/components/StepFlowBuilder/constants.ts +2 -0
- package/admin/src/components/StepFlowBuilder/index.tsx +1 -1
- package/admin/src/components/StepFlowBuilder/nodes/EventTriggerNode.tsx +29 -1
- package/admin/src/components/StepFlowBuilder/panels/EventTriggerConfig.tsx +97 -9
- package/admin/src/components/StepFlowBuilder/panels/NodeEditPanel.tsx +1 -1
- package/admin/src/components/StepFlowBuilder/panels/WaitConfig.tsx +1 -3
- package/admin/src/components/StepFlowBuilder/types.ts +2 -0
- package/admin/src/components/TriggerConfigField/index.tsx +11 -10
- package/admin/src/components/TriggerParamsField/constants.ts +109 -0
- package/admin/src/components/TriggerParamsField/index.tsx +292 -0
- package/admin/src/index.ts +26 -1
- package/admin/src/pages/HomePage/components/CampaignBuilder/index.tsx +4 -0
- package/admin/src/pages/HomePage/components/FlowBuilderTest.tsx +3 -3
- package/admin/src/pages/HomePage/components/SendTimeHeatMap.tsx +2 -2
- package/admin/src/pages/HomePage/components/StatsView.tsx +2 -2
- package/admin/src/translations/en.json +4 -1
- package/admin/src/translations/ru.json +4 -1
- package/admin/tsconfig.json +12 -1
- package/admin/tsconfig.tsbuildinfo +1 -1
- package/dist/_chunks/{en-DEUgX5uV.mjs → en-B5BgIROW.mjs} +3 -1
- package/dist/_chunks/{en-D2kTkBns.js → en-GKZo4lia.js} +3 -1
- package/dist/_chunks/{index-C-1xCfqJ.js → index-354YMebI.js} +152 -38
- package/dist/_chunks/{index-BydXrDhA.mjs → index-BKfFI_Jo.mjs} +2 -2
- package/dist/_chunks/{index-D1kMmlrA.js → index-BQcRoIJr.js} +5 -4
- package/dist/_chunks/{index-BJzk6xNb.mjs → index-BgwX1xYS.mjs} +7 -6
- package/dist/_chunks/{index-pJUyH-Qr.js → index-Cg0G_bTE.js} +2 -2
- package/dist/_chunks/index-CuMY0eo5.js +310 -0
- package/dist/_chunks/{index-unxa4Q_H.mjs → index-DKJtyGq7.mjs} +5 -4
- package/dist/_chunks/{index-BFRbyVHC.js → index-DRXMKPXI.js} +1 -1
- package/dist/_chunks/{index-Xa_4jez0.mjs → index-Dhj0KzCX.mjs} +5 -4
- package/dist/_chunks/{index-Dv1tGmDT.mjs → index-LXBoz7PC.mjs} +1 -1
- package/dist/_chunks/index-NvFKi6er.mjs +310 -0
- package/dist/_chunks/{index-BKrTVHBr.js → index-T7VXjklK.js} +5 -4
- package/dist/_chunks/{index-CWSibOAr.mjs → index-gR86Z3uh.mjs} +152 -38
- package/dist/_chunks/{index-CAwynvu7.js → index-xDSMnUMp.js} +7 -6
- package/dist/_chunks/{ru-BKzplvmu.js → ru-bj5iiIPr.js} +3 -1
- package/dist/_chunks/{ru-DOt1yfNm.mjs → ru-h22ZdPCS.mjs} +3 -1
- package/dist/admin/index.js +31 -7
- package/dist/admin/index.mjs +32 -8
- package/dist/server/index.js +5 -0
- package/dist/server/index.mjs +5 -0
- package/package.json +3 -3
- 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
|
-
|
|
61
|
-
|
|
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__ */
|
|
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:
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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 ? "Выберите события, которые должен совершить пользователь для продолжения кампании." :
|
|
2623
|
-
"
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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, {
|
|
354
|
-
hint && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.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;
|