@escalated-dev/escalated 0.11.4 → 0.11.6
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/package.json +1 -1
- package/pages.json +2 -1
- package/src/components/WorkflowActionConfig.vue +73 -174
- package/src/components/WorkflowActionList.vue +65 -106
- package/src/components/WorkflowConditionBuilder.vue +56 -48
- package/src/components/WorkflowTriggerSelector.vue +34 -38
- package/src/pages/Admin/Workflows/Builder.vue +24 -10
- package/src/pages/Admin/Workflows/Form.vue +21 -9
- package/src/pages/Admin/Workflows/Index.vue +28 -22
- package/src/pages/Error.vue +69 -0
- package/src/utils/workflowContract.js +143 -0
package/package.json
CHANGED
package/pages.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Generated by scripts/generate-page-manifest.js from src/pages. Every name here resolves to a component; a name a backend renders that is not here is a blank screen.",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"pages": [
|
|
5
5
|
"Escalated/Admin/ApiTokens/Index",
|
|
6
6
|
"Escalated/Admin/AuditLog/Index",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"Escalated/Customer/KnowledgeBase/Article",
|
|
90
90
|
"Escalated/Customer/KnowledgeBase/Index",
|
|
91
91
|
"Escalated/Customer/Show",
|
|
92
|
+
"Escalated/Error",
|
|
92
93
|
"Escalated/Guest/Create",
|
|
93
94
|
"Escalated/Guest/Show",
|
|
94
95
|
"Escalated/Plugin/Page"
|
|
@@ -4,16 +4,19 @@ import VariableTokenInput from './VariableTokenInput.vue';
|
|
|
4
4
|
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
actionType: { type: String, required: true },
|
|
7
|
-
|
|
7
|
+
// The action's `value`, which the contract keeps scalar. A structured value
|
|
8
|
+
// a backend stored itself (Laravel's `{agent_id, strategy}`, say) is left
|
|
9
|
+
// untouched until someone edits the field.
|
|
10
|
+
modelValue: { type: [String, Number, Object, Array], default: '' },
|
|
8
11
|
});
|
|
9
12
|
|
|
10
13
|
const emit = defineEmits(['update:modelValue']);
|
|
11
14
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
15
|
+
const isStructured = computed(() => props.modelValue !== null && typeof props.modelValue === 'object');
|
|
16
|
+
const value = computed(() => (isStructured.value ? '' : (props.modelValue ?? '')));
|
|
14
17
|
|
|
15
|
-
function update(
|
|
16
|
-
emit('update:modelValue',
|
|
18
|
+
function update(next) {
|
|
19
|
+
emit('update:modelValue', next);
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const statusOptions = [
|
|
@@ -26,17 +29,6 @@ const statusOptions = [
|
|
|
26
29
|
'closed',
|
|
27
30
|
];
|
|
28
31
|
const priorityOptions = ['low', 'medium', 'high', 'urgent', 'critical'];
|
|
29
|
-
const assignModes = [
|
|
30
|
-
{ value: 'specific', label: 'Specific agent' },
|
|
31
|
-
{ value: 'least_busy', label: 'Least busy agent' },
|
|
32
|
-
{ value: 'round_robin', label: 'Round robin' },
|
|
33
|
-
];
|
|
34
|
-
const delayUnits = [
|
|
35
|
-
{ value: 'minutes', label: 'Minutes' },
|
|
36
|
-
{ value: 'hours', label: 'Hours' },
|
|
37
|
-
{ value: 'days', label: 'Days' },
|
|
38
|
-
];
|
|
39
|
-
const webhookMethods = ['POST', 'PUT', 'PATCH'];
|
|
40
32
|
|
|
41
33
|
const inputClass =
|
|
42
34
|
'w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]';
|
|
@@ -47,38 +39,15 @@ const labelClass = 'block text-xs font-medium text-[var(--esc-panel-text-muted)]
|
|
|
47
39
|
|
|
48
40
|
<template>
|
|
49
41
|
<div class="space-y-3 pt-2">
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<label :class="labelClass">Assignment mode</label>
|
|
54
|
-
<select
|
|
55
|
-
:class="selectClass"
|
|
56
|
-
:value="config.mode || 'specific'"
|
|
57
|
-
@change="update('mode', $event.target.value)"
|
|
58
|
-
>
|
|
59
|
-
<option v-for="m in assignModes" :key="m.value" :value="m.value">{{ m.label }}</option>
|
|
60
|
-
</select>
|
|
61
|
-
</div>
|
|
62
|
-
<div v-if="(config.mode || 'specific') === 'specific'">
|
|
63
|
-
<label :class="labelClass">Agent</label>
|
|
64
|
-
<input
|
|
65
|
-
:class="inputClass"
|
|
66
|
-
:value="config.agent || ''"
|
|
67
|
-
placeholder="Agent ID or email"
|
|
68
|
-
@input="update('agent', $event.target.value)"
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
</template>
|
|
42
|
+
<p v-if="isStructured" class="text-xs text-[var(--esc-panel-text-muted)]">
|
|
43
|
+
This action was configured outside the builder. Editing it here replaces that configuration.
|
|
44
|
+
</p>
|
|
72
45
|
|
|
73
46
|
<!-- Change Status -->
|
|
74
|
-
<template v-
|
|
47
|
+
<template v-if="actionType === 'change_status'">
|
|
75
48
|
<div>
|
|
76
49
|
<label :class="labelClass">New status</label>
|
|
77
|
-
<select
|
|
78
|
-
:class="selectClass"
|
|
79
|
-
:value="config.status || ''"
|
|
80
|
-
@change="update('status', $event.target.value)"
|
|
81
|
-
>
|
|
50
|
+
<select :class="selectClass" :value="value" @change="update($event.target.value)">
|
|
82
51
|
<option value="">Select status...</option>
|
|
83
52
|
<option v-for="s in statusOptions" :key="s" :value="s">{{ s }}</option>
|
|
84
53
|
</select>
|
|
@@ -89,11 +58,7 @@ const labelClass = 'block text-xs font-medium text-[var(--esc-panel-text-muted)]
|
|
|
89
58
|
<template v-else-if="actionType === 'change_priority'">
|
|
90
59
|
<div>
|
|
91
60
|
<label :class="labelClass">New priority</label>
|
|
92
|
-
<select
|
|
93
|
-
:class="selectClass"
|
|
94
|
-
:value="config.priority || ''"
|
|
95
|
-
@change="update('priority', $event.target.value)"
|
|
96
|
-
>
|
|
61
|
+
<select :class="selectClass" :value="value" @change="update($event.target.value)">
|
|
97
62
|
<option value="">Select priority...</option>
|
|
98
63
|
<option v-for="p in priorityOptions" :key="p" :value="p">{{ p }}</option>
|
|
99
64
|
</select>
|
|
@@ -106,182 +71,116 @@ const labelClass = 'block text-xs font-medium text-[var(--esc-panel-text-muted)]
|
|
|
106
71
|
<label :class="labelClass">Tag name</label>
|
|
107
72
|
<input
|
|
108
73
|
:class="inputClass"
|
|
109
|
-
:value="
|
|
74
|
+
:value="value"
|
|
110
75
|
placeholder="Enter tag name"
|
|
111
|
-
@input="update(
|
|
76
|
+
@input="update($event.target.value)"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<!-- Set Department -->
|
|
82
|
+
<template v-else-if="actionType === 'set_department' || actionType === 'move_department'">
|
|
83
|
+
<div>
|
|
84
|
+
<label :class="labelClass">Department ID</label>
|
|
85
|
+
<input
|
|
86
|
+
:class="inputClass"
|
|
87
|
+
:value="value"
|
|
88
|
+
placeholder="Department ID"
|
|
89
|
+
@input="update($event.target.value)"
|
|
112
90
|
/>
|
|
113
91
|
</div>
|
|
114
92
|
</template>
|
|
115
93
|
|
|
116
|
-
<!--
|
|
117
|
-
<template v-else-if="actionType === '
|
|
94
|
+
<!-- Assign Agent -->
|
|
95
|
+
<template v-else-if="actionType === 'assign_agent'">
|
|
118
96
|
<div>
|
|
119
|
-
<label :class="labelClass">
|
|
97
|
+
<label :class="labelClass">Agent</label>
|
|
120
98
|
<input
|
|
121
99
|
:class="inputClass"
|
|
122
|
-
:value="
|
|
123
|
-
placeholder="
|
|
124
|
-
@input="update(
|
|
100
|
+
:value="value"
|
|
101
|
+
placeholder="Agent user ID"
|
|
102
|
+
@input="update($event.target.value)"
|
|
125
103
|
/>
|
|
126
104
|
</div>
|
|
127
105
|
</template>
|
|
128
106
|
|
|
129
107
|
<!-- Add Internal Note -->
|
|
130
|
-
<template v-else-if="actionType === 'add_note'">
|
|
108
|
+
<template v-else-if="actionType === 'add_note' || actionType === 'add_internal_note'">
|
|
131
109
|
<div>
|
|
132
110
|
<label :class="labelClass">Note content</label>
|
|
133
111
|
<VariableTokenInput
|
|
134
|
-
:model-value="
|
|
112
|
+
:model-value="value"
|
|
135
113
|
:multiline="true"
|
|
136
114
|
placeholder="Write the note..."
|
|
137
|
-
@update:model-value="update(
|
|
115
|
+
@update:model-value="update($event)"
|
|
138
116
|
/>
|
|
139
117
|
</div>
|
|
140
118
|
</template>
|
|
141
119
|
|
|
142
|
-
<!--
|
|
143
|
-
<template v-else-if="actionType === '
|
|
144
|
-
<div>
|
|
145
|
-
<label :class="labelClass">To</label>
|
|
146
|
-
<VariableTokenInput
|
|
147
|
-
:model-value="config.to || ''"
|
|
148
|
-
placeholder="Recipient email or variable"
|
|
149
|
-
@update:model-value="update('to', $event)"
|
|
150
|
-
/>
|
|
151
|
-
</div>
|
|
152
|
-
<div>
|
|
153
|
-
<label :class="labelClass">Subject</label>
|
|
154
|
-
<VariableTokenInput
|
|
155
|
-
:model-value="config.subject || ''"
|
|
156
|
-
placeholder="Email subject"
|
|
157
|
-
@update:model-value="update('subject', $event)"
|
|
158
|
-
/>
|
|
159
|
-
</div>
|
|
120
|
+
<!-- Insert Canned Reply -->
|
|
121
|
+
<template v-else-if="actionType === 'insert_canned_reply'">
|
|
160
122
|
<div>
|
|
161
|
-
<label :class="labelClass">
|
|
123
|
+
<label :class="labelClass">Reply</label>
|
|
162
124
|
<VariableTokenInput
|
|
163
|
-
:model-value="
|
|
125
|
+
:model-value="value"
|
|
164
126
|
:multiline="true"
|
|
165
|
-
placeholder="
|
|
166
|
-
@update:model-value="update(
|
|
127
|
+
placeholder="The reply to post on the ticket..."
|
|
128
|
+
@update:model-value="update($event)"
|
|
167
129
|
/>
|
|
168
130
|
</div>
|
|
169
131
|
</template>
|
|
170
132
|
|
|
171
|
-
<!--
|
|
172
|
-
<template v-else-if="actionType === '
|
|
173
|
-
<div>
|
|
174
|
-
<label :class="labelClass">URL</label>
|
|
175
|
-
<input
|
|
176
|
-
:class="inputClass"
|
|
177
|
-
:value="config.url || ''"
|
|
178
|
-
placeholder="https://..."
|
|
179
|
-
@input="update('url', $event.target.value)"
|
|
180
|
-
/>
|
|
181
|
-
</div>
|
|
182
|
-
<div>
|
|
183
|
-
<label :class="labelClass">Method</label>
|
|
184
|
-
<select
|
|
185
|
-
:class="selectClass"
|
|
186
|
-
:value="config.method || 'POST'"
|
|
187
|
-
@change="update('method', $event.target.value)"
|
|
188
|
-
>
|
|
189
|
-
<option v-for="m in webhookMethods" :key="m" :value="m">{{ m }}</option>
|
|
190
|
-
</select>
|
|
191
|
-
</div>
|
|
192
|
-
<div>
|
|
193
|
-
<label :class="labelClass">Headers (JSON)</label>
|
|
194
|
-
<input
|
|
195
|
-
:class="inputClass"
|
|
196
|
-
:value="config.headers || ''"
|
|
197
|
-
:placeholder="headersPlaceholder"
|
|
198
|
-
@input="update('headers', $event.target.value)"
|
|
199
|
-
/>
|
|
200
|
-
</div>
|
|
133
|
+
<!-- Add Follower -->
|
|
134
|
+
<template v-else-if="actionType === 'add_follower'">
|
|
201
135
|
<div>
|
|
202
|
-
<label :class="labelClass">
|
|
203
|
-
<
|
|
204
|
-
:model-value="config.body || ''"
|
|
205
|
-
:multiline="true"
|
|
206
|
-
placeholder="JSON body with variables..."
|
|
207
|
-
@update:model-value="update('body', $event)"
|
|
208
|
-
/>
|
|
136
|
+
<label :class="labelClass">Follower</label>
|
|
137
|
+
<input :class="inputClass" :value="value" placeholder="User ID" @input="update($event.target.value)" />
|
|
209
138
|
</div>
|
|
210
139
|
</template>
|
|
211
140
|
|
|
212
141
|
<!-- Delay -->
|
|
213
142
|
<template v-else-if="actionType === 'delay'">
|
|
214
|
-
<div
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
<div class="flex-1">
|
|
226
|
-
<label :class="labelClass">Unit</label>
|
|
227
|
-
<select
|
|
228
|
-
:class="selectClass + ' w-full'"
|
|
229
|
-
:value="config.unit || 'hours'"
|
|
230
|
-
@change="update('unit', $event.target.value)"
|
|
231
|
-
>
|
|
232
|
-
<option v-for="u in delayUnits" :key="u.value" :value="u.value">{{ u.label }}</option>
|
|
233
|
-
</select>
|
|
234
|
-
</div>
|
|
143
|
+
<div>
|
|
144
|
+
<label :class="labelClass">Wait (minutes)</label>
|
|
145
|
+
<input
|
|
146
|
+
type="number"
|
|
147
|
+
min="1"
|
|
148
|
+
:class="inputClass"
|
|
149
|
+
:value="value || 1"
|
|
150
|
+
@input="update(parseInt($event.target.value) || 1)"
|
|
151
|
+
/>
|
|
235
152
|
</div>
|
|
236
153
|
<div class="rounded-lg bg-orange-500/10 px-3 py-2 text-xs text-orange-400">
|
|
237
|
-
Wait {{
|
|
154
|
+
Wait {{ value || 1 }} minutes, then continue...
|
|
238
155
|
</div>
|
|
239
156
|
</template>
|
|
240
157
|
|
|
241
|
-
<!--
|
|
242
|
-
<template v-else-if="actionType === '
|
|
158
|
+
<!-- Send Webhook -->
|
|
159
|
+
<template v-else-if="actionType === 'send_webhook'">
|
|
243
160
|
<div>
|
|
244
|
-
<label :class="labelClass">
|
|
161
|
+
<label :class="labelClass">URL</label>
|
|
245
162
|
<input
|
|
246
163
|
:class="inputClass"
|
|
247
|
-
:value="
|
|
248
|
-
placeholder="
|
|
249
|
-
@input="update(
|
|
164
|
+
:value="value"
|
|
165
|
+
placeholder="https://..."
|
|
166
|
+
@input="update($event.target.value)"
|
|
250
167
|
/>
|
|
251
168
|
</div>
|
|
252
169
|
</template>
|
|
253
170
|
|
|
254
|
-
<!--
|
|
255
|
-
<template v-else-if="actionType === 'snooze'">
|
|
256
|
-
<div class="flex items-center gap-3">
|
|
257
|
-
<div class="flex-1">
|
|
258
|
-
<label :class="labelClass">Duration</label>
|
|
259
|
-
<input
|
|
260
|
-
type="number"
|
|
261
|
-
min="1"
|
|
262
|
-
:class="inputClass"
|
|
263
|
-
:value="config.duration || 1"
|
|
264
|
-
@input="update('duration', parseInt($event.target.value) || 1)"
|
|
265
|
-
/>
|
|
266
|
-
</div>
|
|
267
|
-
<div class="flex-1">
|
|
268
|
-
<label :class="labelClass">Unit</label>
|
|
269
|
-
<select
|
|
270
|
-
:class="selectClass + ' w-full'"
|
|
271
|
-
:value="config.unit || 'hours'"
|
|
272
|
-
@change="update('unit', $event.target.value)"
|
|
273
|
-
>
|
|
274
|
-
<option v-for="u in delayUnits" :key="u.value" :value="u.value">{{ u.label }}</option>
|
|
275
|
-
</select>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
</template>
|
|
279
|
-
|
|
280
|
-
<!-- Close Ticket (no config needed) -->
|
|
171
|
+
<!-- Close Ticket (no value needed) -->
|
|
281
172
|
<template v-else-if="actionType === 'close_ticket'">
|
|
282
173
|
<p class="text-xs text-[var(--esc-panel-text-muted)]">
|
|
283
174
|
This action closes the ticket immediately. No configuration needed.
|
|
284
175
|
</p>
|
|
285
176
|
</template>
|
|
177
|
+
|
|
178
|
+
<!-- Anything else the backend lists -->
|
|
179
|
+
<template v-else>
|
|
180
|
+
<div>
|
|
181
|
+
<label :class="labelClass">Value</label>
|
|
182
|
+
<input :class="inputClass" :value="value" @input="update($event.target.value)" />
|
|
183
|
+
</div>
|
|
184
|
+
</template>
|
|
286
185
|
</div>
|
|
287
186
|
</template>
|
|
@@ -1,93 +1,72 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from 'vue';
|
|
3
3
|
import WorkflowActionConfig from './WorkflowActionConfig.vue';
|
|
4
|
+
import { CORE_ACTION_TYPES, normalizeOptions } from '../utils/workflowContract.js';
|
|
4
5
|
|
|
5
6
|
const props = defineProps({
|
|
7
|
+
// `[{type, value}]` (workflow-admin-contract.md).
|
|
6
8
|
modelValue: { type: Array, default: () => [] },
|
|
9
|
+
// What the backend executes. Absent means the core catalog every backend
|
|
10
|
+
// handles; anything else the backend lists is offered as well.
|
|
11
|
+
actionTypes: { type: [Array, Object], default: null },
|
|
7
12
|
});
|
|
8
13
|
|
|
9
14
|
const emit = defineEmits(['update:modelValue']);
|
|
10
15
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const PERSON_ICON =
|
|
17
|
+
'M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z';
|
|
18
|
+
const TAG_ICON =
|
|
19
|
+
'M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z';
|
|
20
|
+
const DEPARTMENT_ICON =
|
|
21
|
+
'M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75z';
|
|
22
|
+
const NOTE_ICON =
|
|
23
|
+
'M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z';
|
|
24
|
+
const MAIL_ICON =
|
|
25
|
+
'M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75';
|
|
26
|
+
const BOLT_ICON = 'M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z';
|
|
27
|
+
|
|
28
|
+
const ACTION_STYLES = {
|
|
29
|
+
assign_agent: { icon: PERSON_ICON, color: 'text-blue-400 bg-blue-500/10' },
|
|
30
|
+
change_status: {
|
|
21
31
|
icon: 'M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
22
32
|
color: 'text-emerald-400 bg-emerald-500/10',
|
|
23
33
|
},
|
|
24
|
-
{
|
|
25
|
-
value: 'change_priority',
|
|
26
|
-
label: 'Change Priority',
|
|
34
|
+
change_priority: {
|
|
27
35
|
icon: 'M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12',
|
|
28
36
|
color: 'text-red-400 bg-red-500/10',
|
|
29
37
|
},
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
color: 'text-pink-400 bg-pink-500/10',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
value: 'move_department',
|
|
44
|
-
label: 'Move Department',
|
|
45
|
-
icon: 'M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75z',
|
|
46
|
-
color: 'text-cyan-400 bg-cyan-500/10',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
value: 'add_note',
|
|
50
|
-
label: 'Add Internal Note',
|
|
51
|
-
icon: 'M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z',
|
|
52
|
-
color: 'text-amber-400 bg-amber-500/10',
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
value: 'send_email',
|
|
56
|
-
label: 'Send Email Notification',
|
|
57
|
-
icon: 'M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75',
|
|
58
|
-
color: 'text-sky-400 bg-sky-500/10',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
value: 'send_webhook',
|
|
62
|
-
label: 'Send Webhook',
|
|
38
|
+
add_tag: { icon: TAG_ICON, color: 'text-violet-400 bg-violet-500/10' },
|
|
39
|
+
remove_tag: { icon: TAG_ICON, color: 'text-pink-400 bg-pink-500/10' },
|
|
40
|
+
set_department: { icon: DEPARTMENT_ICON, color: 'text-cyan-400 bg-cyan-500/10' },
|
|
41
|
+
move_department: { icon: DEPARTMENT_ICON, color: 'text-cyan-400 bg-cyan-500/10' },
|
|
42
|
+
add_note: { icon: NOTE_ICON, color: 'text-amber-400 bg-amber-500/10' },
|
|
43
|
+
add_internal_note: { icon: NOTE_ICON, color: 'text-amber-400 bg-amber-500/10' },
|
|
44
|
+
insert_canned_reply: { icon: MAIL_ICON, color: 'text-sky-400 bg-sky-500/10' },
|
|
45
|
+
send_notification: { icon: MAIL_ICON, color: 'text-sky-400 bg-sky-500/10' },
|
|
46
|
+
add_follower: { icon: PERSON_ICON, color: 'text-indigo-400 bg-indigo-500/10' },
|
|
47
|
+
send_webhook: {
|
|
63
48
|
icon: 'M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244',
|
|
64
49
|
color: 'text-indigo-400 bg-indigo-500/10',
|
|
65
50
|
},
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
icon: 'M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
70
|
-
color: 'text-orange-400 bg-orange-500/10',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
value: 'apply_macro',
|
|
74
|
-
label: 'Apply Macro',
|
|
75
|
-
icon: 'M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z',
|
|
76
|
-
color: 'text-yellow-400 bg-yellow-500/10',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
value: 'close_ticket',
|
|
80
|
-
label: 'Close Ticket',
|
|
51
|
+
delay: { icon: 'M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z', color: 'text-orange-400 bg-orange-500/10' },
|
|
52
|
+
apply_macro: { icon: BOLT_ICON, color: 'text-yellow-400 bg-yellow-500/10' },
|
|
53
|
+
close_ticket: {
|
|
81
54
|
icon: 'M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
82
55
|
color: 'text-gray-400 bg-gray-500/10',
|
|
83
56
|
},
|
|
84
|
-
{
|
|
85
|
-
value: 'snooze',
|
|
86
|
-
label: 'Snooze',
|
|
57
|
+
snooze_ticket: {
|
|
87
58
|
icon: 'M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0',
|
|
88
59
|
color: 'text-teal-400 bg-teal-500/10',
|
|
89
60
|
},
|
|
90
|
-
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const DEFAULT_STYLE = { icon: BOLT_ICON, color: 'text-gray-400 bg-gray-500/10' };
|
|
64
|
+
|
|
65
|
+
function withStyle(option) {
|
|
66
|
+
return { ...option, ...(ACTION_STYLES[option.value] ?? DEFAULT_STYLE) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const availableTypes = computed(() => normalizeOptions(props.actionTypes, CORE_ACTION_TYPES).map(withStyle));
|
|
91
70
|
|
|
92
71
|
const showTypeSelector = ref(false);
|
|
93
72
|
const expandedIndex = ref(null);
|
|
@@ -95,12 +74,16 @@ const dragIndex = ref(null);
|
|
|
95
74
|
|
|
96
75
|
const actions = computed(() => props.modelValue);
|
|
97
76
|
|
|
77
|
+
// A stored action may have a type the backend no longer lists; it still gets a
|
|
78
|
+
// label, derived from its name.
|
|
98
79
|
function getActionType(value) {
|
|
99
|
-
|
|
80
|
+
const listed = availableTypes.value.find((t) => t.value === value);
|
|
81
|
+
|
|
82
|
+
return listed ?? withStyle(normalizeOptions([value], CORE_ACTION_TYPES)[0]);
|
|
100
83
|
}
|
|
101
84
|
|
|
102
85
|
function addAction(typeValue) {
|
|
103
|
-
const newActions = [...actions.value, { type: typeValue,
|
|
86
|
+
const newActions = [...actions.value, { type: typeValue, value: '' }];
|
|
104
87
|
emit('update:modelValue', newActions);
|
|
105
88
|
showTypeSelector.value = false;
|
|
106
89
|
expandedIndex.value = newActions.length - 1;
|
|
@@ -112,8 +95,8 @@ function removeAction(index) {
|
|
|
112
95
|
if (expandedIndex.value === index) expandedIndex.value = null;
|
|
113
96
|
}
|
|
114
97
|
|
|
115
|
-
function
|
|
116
|
-
const newActions = actions.value.map((a, i) => (i === index ? { ...a,
|
|
98
|
+
function updateValue(index, value) {
|
|
99
|
+
const newActions = actions.value.map((a, i) => (i === index ? { ...a, value } : a));
|
|
117
100
|
emit('update:modelValue', newActions);
|
|
118
101
|
}
|
|
119
102
|
|
|
@@ -122,40 +105,16 @@ function toggleExpand(index) {
|
|
|
122
105
|
}
|
|
123
106
|
|
|
124
107
|
function getActionSummary(action) {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
case 'change_priority':
|
|
136
|
-
return c.priority || 'Not configured';
|
|
137
|
-
case 'add_tag':
|
|
138
|
-
case 'remove_tag':
|
|
139
|
-
return c.tag || 'Not configured';
|
|
140
|
-
case 'move_department':
|
|
141
|
-
return c.department || 'Not configured';
|
|
142
|
-
case 'add_note':
|
|
143
|
-
return c.body ? c.body.substring(0, 40) + (c.body.length > 40 ? '...' : '') : 'Not configured';
|
|
144
|
-
case 'send_email':
|
|
145
|
-
return c.to || 'Not configured';
|
|
146
|
-
case 'send_webhook':
|
|
147
|
-
return c.url || 'Not configured';
|
|
148
|
-
case 'delay':
|
|
149
|
-
return `Wait ${c.duration || 1} ${c.unit || 'hours'}`;
|
|
150
|
-
case 'apply_macro':
|
|
151
|
-
return c.macro || 'Not configured';
|
|
152
|
-
case 'close_ticket':
|
|
153
|
-
return 'Close immediately';
|
|
154
|
-
case 'snooze':
|
|
155
|
-
return `${c.duration || 1} ${c.unit || 'hours'}`;
|
|
156
|
-
default:
|
|
157
|
-
return 'Not configured';
|
|
158
|
-
}
|
|
108
|
+
const value = action.value;
|
|
109
|
+
|
|
110
|
+
if (action.type === 'delay') return `Wait ${value || 1} minutes`;
|
|
111
|
+
if (action.type === 'close_ticket') return 'Close immediately';
|
|
112
|
+
if (value === null || value === undefined || value === '') return 'Not configured';
|
|
113
|
+
if (typeof value === 'object') return 'Configured outside the builder';
|
|
114
|
+
|
|
115
|
+
const text = String(value);
|
|
116
|
+
|
|
117
|
+
return text.length > 40 ? `${text.substring(0, 40)}...` : text;
|
|
159
118
|
}
|
|
160
119
|
|
|
161
120
|
// Drag-and-drop reordering
|
|
@@ -294,8 +253,8 @@ function onDragEnd() {
|
|
|
294
253
|
<div v-if="expandedIndex === i" class="border-t border-[var(--esc-panel-border)] px-4 pb-4">
|
|
295
254
|
<WorkflowActionConfig
|
|
296
255
|
:action-type="action.type"
|
|
297
|
-
:model-value="action.
|
|
298
|
-
@update:model-value="
|
|
256
|
+
:model-value="action.value"
|
|
257
|
+
@update:model-value="updateValue(i, $event)"
|
|
299
258
|
/>
|
|
300
259
|
</div>
|
|
301
260
|
</div>
|
|
@@ -325,7 +284,7 @@ function onDragEnd() {
|
|
|
325
284
|
class="absolute left-0 z-20 mt-2 w-full rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] py-2 shadow-2xl"
|
|
326
285
|
>
|
|
327
286
|
<button
|
|
328
|
-
v-for="at in
|
|
287
|
+
v-for="at in availableTypes"
|
|
329
288
|
:key="at.value"
|
|
330
289
|
type="button"
|
|
331
290
|
class="flex w-full items-center gap-3 px-4 py-2 text-left transition-colors hover:bg-[var(--esc-panel-hover)]"
|