@escalated-dev/escalated 0.8.0 → 0.11.1

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 (124) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +388 -361
  3. package/package.json +85 -84
  4. package/src/components/ActiveChatsPanel.vue +246 -246
  5. package/src/components/AdminDashboard.stories.js +1423 -1423
  6. package/src/components/AttachmentList.vue +65 -65
  7. package/src/components/ChatActionBar.vue +226 -226
  8. package/src/components/ChatBubble.vue +162 -162
  9. package/src/components/ChatComposer.vue +308 -308
  10. package/src/components/ChatQueue.vue +222 -222
  11. package/src/components/ChatThread.vue +197 -197
  12. package/src/components/CustomTicketActions.stories.js +71 -0
  13. package/src/components/CustomTicketActions.vue +101 -0
  14. package/src/components/EscalatedLayout.vue +628 -604
  15. package/src/components/FollowButton.vue +20 -1
  16. package/src/components/MentionDropdown.vue +154 -154
  17. package/src/components/MetricComparison.vue +89 -89
  18. package/src/components/PercentileChart.vue +67 -67
  19. package/src/components/PeriodSelector.vue +98 -98
  20. package/src/components/PresenceIndicator.vue +228 -213
  21. package/src/components/ReplyComposer.vue +336 -336
  22. package/src/components/ReplyThread.vue +153 -153
  23. package/src/components/ReportChart.vue +395 -395
  24. package/src/components/ReportExportButton.vue +108 -108
  25. package/src/components/SaveViewDialog.vue +172 -172
  26. package/src/components/SavedViewSidebar.vue +208 -208
  27. package/src/components/SkillTagManager.vue +3 -0
  28. package/src/components/SnoozeButton.vue +247 -247
  29. package/src/components/TicketFilters.vue +270 -270
  30. package/src/components/TicketList.vue +655 -655
  31. package/src/components/TicketSidebar.vue +4 -0
  32. package/src/components/TicketSplitDialog.vue +98 -98
  33. package/src/components/TicketSubjects.stories.js +63 -0
  34. package/src/components/TicketSubjects.vue +107 -0
  35. package/src/components/TwoFactorSetup.vue +1 -37
  36. package/src/components/VariableTokenInput.vue +111 -111
  37. package/src/components/WorkflowActionConfig.vue +287 -287
  38. package/src/components/WorkflowActionList.vue +350 -350
  39. package/src/components/WorkflowConditionBuilder.vue +265 -265
  40. package/src/components/WorkflowTriggerSelector.vue +125 -125
  41. package/src/components/admin/newsletters/AnalyticsTiles.stories.js +14 -0
  42. package/src/components/admin/newsletters/AnalyticsTiles.vue +55 -0
  43. package/src/components/admin/newsletters/DeliveriesTable.stories.js +28 -0
  44. package/src/components/admin/newsletters/DeliveriesTable.vue +104 -0
  45. package/src/components/admin/newsletters/DynamicFilterBuilder.stories.js +11 -0
  46. package/src/components/admin/newsletters/DynamicFilterBuilder.vue +53 -0
  47. package/src/components/admin/newsletters/ListMemberTable.stories.js +12 -0
  48. package/src/components/admin/newsletters/ListMemberTable.vue +53 -0
  49. package/src/components/admin/newsletters/MarkdownEditor.stories.js +6 -0
  50. package/src/components/admin/newsletters/MarkdownEditor.vue +53 -0
  51. package/src/components/admin/newsletters/MergeFieldDropdown.stories.js +6 -0
  52. package/src/components/admin/newsletters/MergeFieldDropdown.vue +84 -0
  53. package/src/components/admin/newsletters/PreviewIframe.stories.js +7 -0
  54. package/src/components/admin/newsletters/PreviewIframe.vue +40 -0
  55. package/src/components/admin/newsletters/SummaryTiles.stories.js +8 -0
  56. package/src/components/admin/newsletters/SummaryTiles.vue +63 -0
  57. package/src/composables/useChat.js +187 -187
  58. package/src/composables/useI18n.js +102 -102
  59. package/src/composables/useMentions.js +134 -134
  60. package/src/composables/usePermissions.js +31 -0
  61. package/src/composables/useRealtime.js +182 -182
  62. package/src/index.js +85 -84
  63. package/src/locales/README.md +13 -13
  64. package/src/locales/ar.json +727 -727
  65. package/src/locales/de.json +690 -690
  66. package/src/locales/en.json +889 -729
  67. package/src/locales/es.json +691 -691
  68. package/src/locales/fr.json +691 -691
  69. package/src/locales/index.js +16 -16
  70. package/src/locales/it.json +727 -727
  71. package/src/locales/ja.json +727 -727
  72. package/src/locales/ko.json +727 -727
  73. package/src/locales/nl.json +727 -727
  74. package/src/locales/pl.json +727 -727
  75. package/src/locales/pt-BR.json +727 -727
  76. package/src/locales/ru.json +727 -727
  77. package/src/locales/tr.json +727 -727
  78. package/src/locales/zh-CN.json +727 -727
  79. package/src/pages/Admin/Automations/Form.vue +280 -0
  80. package/src/pages/Admin/Automations/Index.vue +230 -0
  81. package/src/pages/Admin/Newsletters/Compose.vue +192 -0
  82. package/src/pages/Admin/Newsletters/Edit.vue +24 -0
  83. package/src/pages/Admin/Newsletters/Index.vue +141 -0
  84. package/src/pages/Admin/Newsletters/Lists/Create.vue +64 -0
  85. package/src/pages/Admin/Newsletters/Lists/Index.vue +59 -0
  86. package/src/pages/Admin/Newsletters/Lists/Show.vue +53 -0
  87. package/src/pages/Admin/Newsletters/Settings.vue +85 -0
  88. package/src/pages/Admin/Newsletters/Show.vue +140 -0
  89. package/src/pages/Admin/Newsletters/Templates/Create.vue +11 -0
  90. package/src/pages/Admin/Newsletters/Templates/Index.vue +54 -0
  91. package/src/pages/Admin/Newsletters/Templates/Show.vue +65 -0
  92. package/src/pages/Admin/Newsletters/usePreview.js +44 -0
  93. package/src/pages/Admin/Plugins/Index.vue +10 -2
  94. package/src/pages/Admin/Reports/AgentDetail.vue +132 -132
  95. package/src/pages/Admin/Reports/AgentRanking.vue +246 -246
  96. package/src/pages/Admin/Reports/Cohorts.vue +180 -180
  97. package/src/pages/Admin/Reports/Comparison.vue +141 -141
  98. package/src/pages/Admin/Reports/Dashboard.vue +226 -226
  99. package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -164
  100. package/src/pages/Admin/Reports/ResponseTimes.vue +164 -164
  101. package/src/pages/Admin/Reports/SlaTrends.vue +183 -183
  102. package/src/pages/Admin/Settings/DatabaseConnection.vue +228 -0
  103. package/src/pages/Admin/Settings/PublicTickets.vue +163 -0
  104. package/src/pages/Admin/Settings/SsoSettings.vue +157 -4
  105. package/src/pages/Admin/Settings/TwoFactor.vue +32 -6
  106. package/src/pages/Admin/Settings.vue +1008 -1008
  107. package/src/pages/Admin/Skills/Form.vue +30 -10
  108. package/src/pages/Admin/Tickets/Index.vue +126 -126
  109. package/src/pages/Admin/Tickets/Show.vue +254 -249
  110. package/src/pages/Admin/Users/Index.vue +145 -145
  111. package/src/pages/Admin/Webhooks/DeliveryLog.vue +1 -2
  112. package/src/pages/Admin/Workflows/Builder.vue +207 -207
  113. package/src/pages/Admin/Workflows/Form.vue +24 -0
  114. package/src/pages/Admin/Workflows/Index.vue +235 -235
  115. package/src/pages/Admin/Workflows/Logs.vue +286 -286
  116. package/src/pages/Agent/TicketIndex.vue +112 -112
  117. package/src/pages/Agent/TicketShow.vue +547 -494
  118. package/src/pages/Customer/Index.vue +56 -56
  119. package/src/pages/Customer/KnowledgeBase/Article.vue +129 -129
  120. package/src/pages/Plugin/Page.vue +61 -0
  121. package/src/plugin.js +175 -175
  122. package/src/utils/mentionHighlight.js +24 -24
  123. package/src/widget/EscalatedWidget.vue +804 -804
  124. package/src/widget/index.js +254 -254
@@ -1,350 +1,350 @@
1
- <script setup>
2
- import { ref, computed } from 'vue';
3
- import WorkflowActionConfig from './WorkflowActionConfig.vue';
4
-
5
- const props = defineProps({
6
- modelValue: { type: Array, default: () => [] },
7
- });
8
-
9
- const emit = defineEmits(['update:modelValue']);
10
-
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',
21
- icon: 'M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
22
- color: 'text-emerald-400 bg-emerald-500/10',
23
- },
24
- {
25
- value: 'change_priority',
26
- label: 'Change Priority',
27
- icon: 'M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12',
28
- color: 'text-red-400 bg-red-500/10',
29
- },
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',
63
- 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
- color: 'text-indigo-400 bg-indigo-500/10',
65
- },
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',
81
- 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
- color: 'text-gray-400 bg-gray-500/10',
83
- },
84
- {
85
- value: 'snooze',
86
- label: 'Snooze',
87
- 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
- color: 'text-teal-400 bg-teal-500/10',
89
- },
90
- ];
91
-
92
- const showTypeSelector = ref(false);
93
- const expandedIndex = ref(null);
94
- const dragIndex = ref(null);
95
-
96
- const actions = computed(() => props.modelValue);
97
-
98
- function getActionType(value) {
99
- return actionTypes.find((t) => t.value === value) || actionTypes[0];
100
- }
101
-
102
- function addAction(typeValue) {
103
- const newActions = [...actions.value, { type: typeValue, config: {} }];
104
- emit('update:modelValue', newActions);
105
- showTypeSelector.value = false;
106
- expandedIndex.value = newActions.length - 1;
107
- }
108
-
109
- function removeAction(index) {
110
- const newActions = actions.value.filter((_, i) => i !== index);
111
- emit('update:modelValue', newActions);
112
- if (expandedIndex.value === index) expandedIndex.value = null;
113
- }
114
-
115
- function updateConfig(index, config) {
116
- const newActions = actions.value.map((a, i) => (i === index ? { ...a, config } : a));
117
- emit('update:modelValue', newActions);
118
- }
119
-
120
- function toggleExpand(index) {
121
- expandedIndex.value = expandedIndex.value === index ? null : index;
122
- }
123
-
124
- 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
- }
159
- }
160
-
161
- // Drag-and-drop reordering
162
- function onDragStart(index) {
163
- dragIndex.value = index;
164
- }
165
-
166
- function onDragOver(event, index) {
167
- event.preventDefault();
168
- if (dragIndex.value === null || dragIndex.value === index) return;
169
- const newActions = [...actions.value];
170
- const [moved] = newActions.splice(dragIndex.value, 1);
171
- newActions.splice(index, 0, moved);
172
- emit('update:modelValue', newActions);
173
- dragIndex.value = index;
174
- }
175
-
176
- function onDragEnd() {
177
- dragIndex.value = null;
178
- }
179
- </script>
180
-
181
- <template>
182
- <div class="space-y-0">
183
- <div v-for="(action, i) in actions" :key="i" class="relative">
184
- <!-- Connector line -->
185
- <div v-if="i > 0" class="flex justify-center">
186
- <div class="h-6 w-px bg-[var(--esc-panel-border)]" />
187
- </div>
188
-
189
- <!-- Delay divider -->
190
- <div
191
- v-if="action.type === 'delay'"
192
- class="my-1 flex items-center gap-3"
193
- draggable="true"
194
- @dragstart="onDragStart(i)"
195
- @dragover="onDragOver($event, i)"
196
- @dragend="onDragEnd"
197
- >
198
- <div class="h-px flex-1 border-t border-dashed border-orange-500/40" />
199
- <div
200
- class="flex items-center gap-2 rounded-full border border-orange-500/30 bg-orange-500/10 px-4 py-1.5"
201
- >
202
- <svg
203
- class="h-4 w-4 text-orange-400"
204
- fill="none"
205
- stroke="currentColor"
206
- stroke-width="1.5"
207
- viewBox="0 0 24 24"
208
- >
209
- <path
210
- stroke-linecap="round"
211
- stroke-linejoin="round"
212
- d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
213
- />
214
- </svg>
215
- <span class="text-xs font-medium text-orange-400">{{ getActionSummary(action) }}</span>
216
- <button type="button" class="ml-1 text-orange-400/60 hover:text-rose-400" @click="removeAction(i)">
217
- <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
218
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
219
- </svg>
220
- </button>
221
- </div>
222
- <div class="h-px flex-1 border-t border-dashed border-orange-500/40" />
223
- </div>
224
-
225
- <!-- Regular action card -->
226
- <div
227
- v-else
228
- class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] transition-all"
229
- :class="expandedIndex === i ? 'shadow-lg' : ''"
230
- draggable="true"
231
- @dragstart="onDragStart(i)"
232
- @dragover="onDragOver($event, i)"
233
- @dragend="onDragEnd"
234
- >
235
- <!-- Action header -->
236
- <div class="flex cursor-pointer items-center gap-3 px-4 py-3" @click="toggleExpand(i)">
237
- <!-- Drag handle -->
238
- <div class="cursor-grab text-[var(--esc-panel-text-muted)]">
239
- <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
240
- <circle cx="9" cy="6" r="1.5" />
241
- <circle cx="15" cy="6" r="1.5" />
242
- <circle cx="9" cy="12" r="1.5" />
243
- <circle cx="15" cy="12" r="1.5" />
244
- <circle cx="9" cy="18" r="1.5" />
245
- <circle cx="15" cy="18" r="1.5" />
246
- </svg>
247
- </div>
248
-
249
- <!-- Icon -->
250
- <div
251
- class="flex h-8 w-8 items-center justify-center rounded-lg"
252
- :class="getActionType(action.type).color"
253
- >
254
- <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
255
- <path stroke-linecap="round" stroke-linejoin="round" :d="getActionType(action.type).icon" />
256
- </svg>
257
- </div>
258
-
259
- <!-- Label and summary -->
260
- <div class="min-w-0 flex-1">
261
- <div class="text-sm font-medium text-[var(--esc-panel-text-secondary)]">
262
- {{ getActionType(action.type).label }}
263
- </div>
264
- <div class="truncate text-xs text-[var(--esc-panel-text-muted)]">
265
- {{ getActionSummary(action) }}
266
- </div>
267
- </div>
268
-
269
- <!-- Expand/collapse chevron -->
270
- <svg
271
- class="h-4 w-4 text-[var(--esc-panel-text-muted)] transition-transform"
272
- :class="expandedIndex === i ? 'rotate-180' : ''"
273
- fill="none"
274
- stroke="currentColor"
275
- stroke-width="2"
276
- viewBox="0 0 24 24"
277
- >
278
- <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
279
- </svg>
280
-
281
- <!-- Remove -->
282
- <button
283
- type="button"
284
- class="flex h-7 w-7 items-center justify-center rounded-lg text-[var(--esc-panel-text-muted)] transition-colors hover:bg-rose-500/10 hover:text-rose-400"
285
- @click.stop="removeAction(i)"
286
- >
287
- <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
288
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
289
- </svg>
290
- </button>
291
- </div>
292
-
293
- <!-- Expanded config -->
294
- <div v-if="expandedIndex === i" class="border-t border-[var(--esc-panel-border)] px-4 pb-4">
295
- <WorkflowActionConfig
296
- :action-type="action.type"
297
- :model-value="action.config"
298
- @update:model-value="updateConfig(i, $event)"
299
- />
300
- </div>
301
- </div>
302
- </div>
303
-
304
- <!-- Connector to add button -->
305
- <div v-if="actions.length" class="flex justify-center">
306
- <div class="h-6 w-px bg-[var(--esc-panel-border)]" />
307
- </div>
308
-
309
- <!-- Add action -->
310
- <div class="relative">
311
- <button
312
- type="button"
313
- class="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-[var(--esc-panel-border)] px-4 py-3 text-sm text-[var(--esc-panel-text-muted)] transition-all hover:border-emerald-500/50 hover:text-emerald-400"
314
- @click="showTypeSelector = !showTypeSelector"
315
- >
316
- <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
317
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
318
- </svg>
319
- Add Action
320
- </button>
321
-
322
- <!-- Action type selector dropdown -->
323
- <div
324
- v-if="showTypeSelector"
325
- 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
- >
327
- <button
328
- v-for="at in actionTypes"
329
- :key="at.value"
330
- type="button"
331
- class="flex w-full items-center gap-3 px-4 py-2 text-left transition-colors hover:bg-[var(--esc-panel-hover)]"
332
- @click="addAction(at.value)"
333
- >
334
- <div class="flex h-7 w-7 items-center justify-center rounded-lg" :class="at.color">
335
- <svg
336
- class="h-3.5 w-3.5"
337
- fill="none"
338
- stroke="currentColor"
339
- stroke-width="1.5"
340
- viewBox="0 0 24 24"
341
- >
342
- <path stroke-linecap="round" stroke-linejoin="round" :d="at.icon" />
343
- </svg>
344
- </div>
345
- <span class="text-sm text-[var(--esc-panel-text-secondary)]">{{ at.label }}</span>
346
- </button>
347
- </div>
348
- </div>
349
- </div>
350
- </template>
1
+ <script setup>
2
+ import { ref, computed } from 'vue';
3
+ import WorkflowActionConfig from './WorkflowActionConfig.vue';
4
+
5
+ const props = defineProps({
6
+ modelValue: { type: Array, default: () => [] },
7
+ });
8
+
9
+ const emit = defineEmits(['update:modelValue']);
10
+
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',
21
+ icon: 'M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
22
+ color: 'text-emerald-400 bg-emerald-500/10',
23
+ },
24
+ {
25
+ value: 'change_priority',
26
+ label: 'Change Priority',
27
+ icon: 'M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12',
28
+ color: 'text-red-400 bg-red-500/10',
29
+ },
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',
63
+ 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
+ color: 'text-indigo-400 bg-indigo-500/10',
65
+ },
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',
81
+ 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
+ color: 'text-gray-400 bg-gray-500/10',
83
+ },
84
+ {
85
+ value: 'snooze',
86
+ label: 'Snooze',
87
+ 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
+ color: 'text-teal-400 bg-teal-500/10',
89
+ },
90
+ ];
91
+
92
+ const showTypeSelector = ref(false);
93
+ const expandedIndex = ref(null);
94
+ const dragIndex = ref(null);
95
+
96
+ const actions = computed(() => props.modelValue);
97
+
98
+ function getActionType(value) {
99
+ return actionTypes.find((t) => t.value === value) || actionTypes[0];
100
+ }
101
+
102
+ function addAction(typeValue) {
103
+ const newActions = [...actions.value, { type: typeValue, config: {} }];
104
+ emit('update:modelValue', newActions);
105
+ showTypeSelector.value = false;
106
+ expandedIndex.value = newActions.length - 1;
107
+ }
108
+
109
+ function removeAction(index) {
110
+ const newActions = actions.value.filter((_, i) => i !== index);
111
+ emit('update:modelValue', newActions);
112
+ if (expandedIndex.value === index) expandedIndex.value = null;
113
+ }
114
+
115
+ function updateConfig(index, config) {
116
+ const newActions = actions.value.map((a, i) => (i === index ? { ...a, config } : a));
117
+ emit('update:modelValue', newActions);
118
+ }
119
+
120
+ function toggleExpand(index) {
121
+ expandedIndex.value = expandedIndex.value === index ? null : index;
122
+ }
123
+
124
+ 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
+ }
159
+ }
160
+
161
+ // Drag-and-drop reordering
162
+ function onDragStart(index) {
163
+ dragIndex.value = index;
164
+ }
165
+
166
+ function onDragOver(event, index) {
167
+ event.preventDefault();
168
+ if (dragIndex.value === null || dragIndex.value === index) return;
169
+ const newActions = [...actions.value];
170
+ const [moved] = newActions.splice(dragIndex.value, 1);
171
+ newActions.splice(index, 0, moved);
172
+ emit('update:modelValue', newActions);
173
+ dragIndex.value = index;
174
+ }
175
+
176
+ function onDragEnd() {
177
+ dragIndex.value = null;
178
+ }
179
+ </script>
180
+
181
+ <template>
182
+ <div class="space-y-0">
183
+ <div v-for="(action, i) in actions" :key="i" class="relative">
184
+ <!-- Connector line -->
185
+ <div v-if="i > 0" class="flex justify-center">
186
+ <div class="h-6 w-px bg-[var(--esc-panel-border)]" />
187
+ </div>
188
+
189
+ <!-- Delay divider -->
190
+ <div
191
+ v-if="action.type === 'delay'"
192
+ class="my-1 flex items-center gap-3"
193
+ draggable="true"
194
+ @dragstart="onDragStart(i)"
195
+ @dragover="onDragOver($event, i)"
196
+ @dragend="onDragEnd"
197
+ >
198
+ <div class="h-px flex-1 border-t border-dashed border-orange-500/40" />
199
+ <div
200
+ class="flex items-center gap-2 rounded-full border border-orange-500/30 bg-orange-500/10 px-4 py-1.5"
201
+ >
202
+ <svg
203
+ class="h-4 w-4 text-orange-400"
204
+ fill="none"
205
+ stroke="currentColor"
206
+ stroke-width="1.5"
207
+ viewBox="0 0 24 24"
208
+ >
209
+ <path
210
+ stroke-linecap="round"
211
+ stroke-linejoin="round"
212
+ d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
213
+ />
214
+ </svg>
215
+ <span class="text-xs font-medium text-orange-400">{{ getActionSummary(action) }}</span>
216
+ <button type="button" class="ml-1 text-orange-400/60 hover:text-rose-400" @click="removeAction(i)">
217
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
218
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
219
+ </svg>
220
+ </button>
221
+ </div>
222
+ <div class="h-px flex-1 border-t border-dashed border-orange-500/40" />
223
+ </div>
224
+
225
+ <!-- Regular action card -->
226
+ <div
227
+ v-else
228
+ class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] transition-all"
229
+ :class="expandedIndex === i ? 'shadow-lg' : ''"
230
+ draggable="true"
231
+ @dragstart="onDragStart(i)"
232
+ @dragover="onDragOver($event, i)"
233
+ @dragend="onDragEnd"
234
+ >
235
+ <!-- Action header -->
236
+ <div class="flex cursor-pointer items-center gap-3 px-4 py-3" @click="toggleExpand(i)">
237
+ <!-- Drag handle -->
238
+ <div class="cursor-grab text-[var(--esc-panel-text-muted)]">
239
+ <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
240
+ <circle cx="9" cy="6" r="1.5" />
241
+ <circle cx="15" cy="6" r="1.5" />
242
+ <circle cx="9" cy="12" r="1.5" />
243
+ <circle cx="15" cy="12" r="1.5" />
244
+ <circle cx="9" cy="18" r="1.5" />
245
+ <circle cx="15" cy="18" r="1.5" />
246
+ </svg>
247
+ </div>
248
+
249
+ <!-- Icon -->
250
+ <div
251
+ class="flex h-8 w-8 items-center justify-center rounded-lg"
252
+ :class="getActionType(action.type).color"
253
+ >
254
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
255
+ <path stroke-linecap="round" stroke-linejoin="round" :d="getActionType(action.type).icon" />
256
+ </svg>
257
+ </div>
258
+
259
+ <!-- Label and summary -->
260
+ <div class="min-w-0 flex-1">
261
+ <div class="text-sm font-medium text-[var(--esc-panel-text-secondary)]">
262
+ {{ getActionType(action.type).label }}
263
+ </div>
264
+ <div class="truncate text-xs text-[var(--esc-panel-text-muted)]">
265
+ {{ getActionSummary(action) }}
266
+ </div>
267
+ </div>
268
+
269
+ <!-- Expand/collapse chevron -->
270
+ <svg
271
+ class="h-4 w-4 text-[var(--esc-panel-text-muted)] transition-transform"
272
+ :class="expandedIndex === i ? 'rotate-180' : ''"
273
+ fill="none"
274
+ stroke="currentColor"
275
+ stroke-width="2"
276
+ viewBox="0 0 24 24"
277
+ >
278
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
279
+ </svg>
280
+
281
+ <!-- Remove -->
282
+ <button
283
+ type="button"
284
+ class="flex h-7 w-7 items-center justify-center rounded-lg text-[var(--esc-panel-text-muted)] transition-colors hover:bg-rose-500/10 hover:text-rose-400"
285
+ @click.stop="removeAction(i)"
286
+ >
287
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
288
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
289
+ </svg>
290
+ </button>
291
+ </div>
292
+
293
+ <!-- Expanded config -->
294
+ <div v-if="expandedIndex === i" class="border-t border-[var(--esc-panel-border)] px-4 pb-4">
295
+ <WorkflowActionConfig
296
+ :action-type="action.type"
297
+ :model-value="action.config"
298
+ @update:model-value="updateConfig(i, $event)"
299
+ />
300
+ </div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- Connector to add button -->
305
+ <div v-if="actions.length" class="flex justify-center">
306
+ <div class="h-6 w-px bg-[var(--esc-panel-border)]" />
307
+ </div>
308
+
309
+ <!-- Add action -->
310
+ <div class="relative">
311
+ <button
312
+ type="button"
313
+ class="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-[var(--esc-panel-border)] px-4 py-3 text-sm text-[var(--esc-panel-text-muted)] transition-all hover:border-emerald-500/50 hover:text-emerald-400"
314
+ @click="showTypeSelector = !showTypeSelector"
315
+ >
316
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
317
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
318
+ </svg>
319
+ Add Action
320
+ </button>
321
+
322
+ <!-- Action type selector dropdown -->
323
+ <div
324
+ v-if="showTypeSelector"
325
+ 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
+ >
327
+ <button
328
+ v-for="at in actionTypes"
329
+ :key="at.value"
330
+ type="button"
331
+ class="flex w-full items-center gap-3 px-4 py-2 text-left transition-colors hover:bg-[var(--esc-panel-hover)]"
332
+ @click="addAction(at.value)"
333
+ >
334
+ <div class="flex h-7 w-7 items-center justify-center rounded-lg" :class="at.color">
335
+ <svg
336
+ class="h-3.5 w-3.5"
337
+ fill="none"
338
+ stroke="currentColor"
339
+ stroke-width="1.5"
340
+ viewBox="0 0 24 24"
341
+ >
342
+ <path stroke-linecap="round" stroke-linejoin="round" :d="at.icon" />
343
+ </svg>
344
+ </div>
345
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">{{ at.label }}</span>
346
+ </button>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ </template>