@escalated-dev/escalated 0.11.5 → 0.11.7

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.
@@ -4,16 +4,19 @@ import VariableTokenInput from './VariableTokenInput.vue';
4
4
 
5
5
  const props = defineProps({
6
6
  actionType: { type: String, required: true },
7
- modelValue: { type: Object, default: () => ({}) },
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 headersPlaceholder = '{"Authorization": "Bearer ..."}';
13
- const config = computed(() => props.modelValue || {});
15
+ const isStructured = computed(() => props.modelValue !== null && typeof props.modelValue === 'object');
16
+ const value = computed(() => (isStructured.value ? '' : (props.modelValue ?? '')));
14
17
 
15
- function update(key, value) {
16
- emit('update:modelValue', { ...config.value, [key]: value });
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
- <!-- Assign Agent -->
51
- <template v-if="actionType === 'assign_agent'">
52
- <div>
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-else-if="actionType === 'change_status'">
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="config.tag || ''"
74
+ :value="value"
110
75
  placeholder="Enter tag name"
111
- @input="update('tag', $event.target.value)"
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
- <!-- Move Department -->
117
- <template v-else-if="actionType === 'move_department'">
94
+ <!-- Assign Agent -->
95
+ <template v-else-if="actionType === 'assign_agent'">
118
96
  <div>
119
- <label :class="labelClass">Department</label>
97
+ <label :class="labelClass">Agent</label>
120
98
  <input
121
99
  :class="inputClass"
122
- :value="config.department || ''"
123
- placeholder="Department name or ID"
124
- @input="update('department', $event.target.value)"
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="config.body || ''"
112
+ :model-value="value"
135
113
  :multiline="true"
136
114
  placeholder="Write the note..."
137
- @update:model-value="update('body', $event)"
115
+ @update:model-value="update($event)"
138
116
  />
139
117
  </div>
140
118
  </template>
141
119
 
142
- <!-- Send Email -->
143
- <template v-else-if="actionType === 'send_email'">
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">Body</label>
123
+ <label :class="labelClass">Reply</label>
162
124
  <VariableTokenInput
163
- :model-value="config.body || ''"
125
+ :model-value="value"
164
126
  :multiline="true"
165
- placeholder="Email body..."
166
- @update:model-value="update('body', $event)"
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
- <!-- Send Webhook -->
172
- <template v-else-if="actionType === 'send_webhook'">
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">Body template</label>
203
- <VariableTokenInput
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 class="flex items-center gap-3">
215
- <div class="flex-1">
216
- <label :class="labelClass">Duration</label>
217
- <input
218
- type="number"
219
- min="1"
220
- :class="inputClass"
221
- :value="config.duration || 1"
222
- @input="update('duration', parseInt($event.target.value) || 1)"
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 {{ config.duration || 1 }} {{ config.unit || 'hours' }}, then continue...
154
+ Wait {{ value || 1 }} minutes, then continue...
238
155
  </div>
239
156
  </template>
240
157
 
241
- <!-- Apply Macro -->
242
- <template v-else-if="actionType === 'apply_macro'">
158
+ <!-- Send Webhook -->
159
+ <template v-else-if="actionType === 'send_webhook'">
243
160
  <div>
244
- <label :class="labelClass">Macro</label>
161
+ <label :class="labelClass">URL</label>
245
162
  <input
246
163
  :class="inputClass"
247
- :value="config.macro || ''"
248
- placeholder="Macro name or ID"
249
- @input="update('macro', $event.target.value)"
164
+ :value="value"
165
+ placeholder="https://..."
166
+ @input="update($event.target.value)"
250
167
  />
251
168
  </div>
252
169
  </template>
253
170
 
254
- <!-- Snooze -->
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 actionTypes = [
12
- {
13
- value: 'assign_agent',
14
- label: 'Assign Agent',
15
- icon: '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',
16
- color: 'text-blue-400 bg-blue-500/10',
17
- },
18
- {
19
- value: 'change_status',
20
- label: 'Change Status',
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
- value: 'add_tag',
32
- label: 'Add Tag',
33
- icon: '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',
34
- color: 'text-violet-400 bg-violet-500/10',
35
- },
36
- {
37
- value: 'remove_tag',
38
- label: 'Remove Tag',
39
- icon: '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',
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
- value: 'delay',
68
- label: 'Delay',
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
- return actionTypes.find((t) => t.value === value) || actionTypes[0];
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, config: {} }];
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 updateConfig(index, config) {
116
- const newActions = actions.value.map((a, i) => (i === index ? { ...a, config } : 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 c = action.config || {};
126
- switch (action.type) {
127
- case 'assign_agent':
128
- return c.mode === 'round_robin'
129
- ? 'Round robin'
130
- : c.mode === 'least_busy'
131
- ? 'Least busy'
132
- : c.agent || 'Not configured';
133
- case 'change_status':
134
- return c.status || 'Not configured';
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.config"
298
- @update:model-value="updateConfig(i, $event)"
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 actionTypes"
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)]"