@escalated-dev/escalated 0.6.0 → 0.7.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 (187) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +125 -2
  3. package/package.json +40 -5
  4. package/src/components/ActiveChatsPanel.vue +246 -0
  5. package/src/components/ActivityTimeline.stories.js +55 -0
  6. package/src/components/ActivityTimeline.vue +83 -66
  7. package/src/components/AdminDashboard.stories.js +1423 -0
  8. package/src/components/AgentLoadIndicator.stories.js +37 -0
  9. package/src/components/AgentLoadIndicator.vue +49 -0
  10. package/src/components/AgentPerformanceCard.vue +44 -0
  11. package/src/components/ArticleEditor.vue +82 -0
  12. package/src/components/AssigneeSelect.vue +37 -23
  13. package/src/components/AttachmentList.vue +65 -37
  14. package/src/components/AuditLogEntry.vue +95 -0
  15. package/src/components/BulkActionBar.vue +166 -97
  16. package/src/components/CategoryTree.vue +89 -0
  17. package/src/components/ChartWidget.vue +46 -0
  18. package/src/components/ChatActionBar.vue +226 -0
  19. package/src/components/ChatBubble.vue +162 -0
  20. package/src/components/ChatComposer.vue +308 -0
  21. package/src/components/ChatQueue.vue +222 -0
  22. package/src/components/ChatThread.vue +197 -0
  23. package/src/components/CollisionWarning.stories.js +30 -0
  24. package/src/components/CollisionWarning.vue +68 -0
  25. package/src/components/ConditionalFieldRules.vue +135 -0
  26. package/src/components/ContextPanel.vue +66 -0
  27. package/src/components/ContextPanelSection.vue +65 -0
  28. package/src/components/CustomFieldRenderer.vue +159 -0
  29. package/src/components/EscalatedLayout.vue +424 -70
  30. package/src/components/FileDropzone.vue +57 -41
  31. package/src/components/FollowButton.stories.js +30 -0
  32. package/src/components/FollowButton.vue +88 -63
  33. package/src/components/KeyboardShortcutHelp.vue +213 -123
  34. package/src/components/KnowledgePanel.vue +122 -0
  35. package/src/components/KpiCard.stories.js +32 -0
  36. package/src/components/KpiCard.vue +88 -0
  37. package/src/components/MacroDropdown.vue +150 -90
  38. package/src/components/MentionDropdown.vue +154 -0
  39. package/src/components/MetricComparison.vue +89 -0
  40. package/src/components/PercentileChart.vue +67 -0
  41. package/src/components/PeriodSelector.vue +98 -0
  42. package/src/components/PermissionMatrix.vue +107 -0
  43. package/src/components/PinnedNotes.vue +129 -90
  44. package/src/components/PluginSlot.vue +68 -62
  45. package/src/components/PresenceIndicator.vue +138 -28
  46. package/src/components/PriorityBadge.stories.js +33 -0
  47. package/src/components/PriorityBadge.vue +46 -39
  48. package/src/components/QuickFilters.vue +65 -49
  49. package/src/components/ReplyComposer.vue +207 -30
  50. package/src/components/ReplyThread.vue +111 -21
  51. package/src/components/ReportChart.vue +395 -0
  52. package/src/components/ReportExportButton.vue +108 -0
  53. package/src/components/SatisfactionRating.vue +180 -128
  54. package/src/components/SaveViewDialog.vue +172 -0
  55. package/src/components/SavedViewSidebar.vue +208 -0
  56. package/src/components/ScheduleEditor.vue +104 -0
  57. package/src/components/SideConversation.vue +165 -0
  58. package/src/components/SideConversationList.vue +147 -0
  59. package/src/components/SkillTagManager.vue +79 -0
  60. package/src/components/SlaComplianceChart.vue +46 -0
  61. package/src/components/SlaTimer.stories.js +35 -0
  62. package/src/components/SlaTimer.vue +62 -49
  63. package/src/components/SnoozeButton.vue +247 -0
  64. package/src/components/StatsCard.stories.js +24 -0
  65. package/src/components/StatsCard.vue +18 -6
  66. package/src/components/StatusBadge.stories.js +53 -0
  67. package/src/components/StatusBadge.vue +78 -46
  68. package/src/components/TagSelect.vue +84 -53
  69. package/src/components/TicketFilters.vue +247 -46
  70. package/src/components/TicketLinkPanel.vue +193 -0
  71. package/src/components/TicketList.vue +536 -74
  72. package/src/components/TicketMergeDialog.vue +157 -0
  73. package/src/components/TicketSidebar.vue +167 -99
  74. package/src/components/TicketSplitDialog.vue +98 -0
  75. package/src/components/TicketTypeSelector.vue +21 -0
  76. package/src/components/TotpInput.stories.js +9 -0
  77. package/src/components/TotpInput.vue +81 -0
  78. package/src/components/TwoFactorSetup.vue +113 -0
  79. package/src/components/VariableTokenInput.vue +111 -0
  80. package/src/components/WorkflowActionConfig.vue +287 -0
  81. package/src/components/WorkflowActionList.vue +350 -0
  82. package/src/components/WorkflowConditionBuilder.vue +265 -0
  83. package/src/components/WorkflowTriggerSelector.vue +125 -0
  84. package/src/composables/useChat.js +187 -0
  85. package/src/composables/useDebouncedSearch.js +38 -0
  86. package/src/composables/useI18n.js +80 -80
  87. package/src/composables/useKeyboardShortcuts.js +46 -46
  88. package/src/composables/useMentions.js +134 -0
  89. package/src/composables/usePluginExtensions.js +159 -67
  90. package/src/composables/usePluginHooks.js +65 -0
  91. package/src/composables/useRealtime.js +182 -0
  92. package/src/defineEscalatedPlugin.js +76 -50
  93. package/src/index.js +77 -28
  94. package/src/locales/ar.json +727 -0
  95. package/src/locales/de.json +678 -430
  96. package/src/locales/en.json +717 -430
  97. package/src/locales/es.json +679 -430
  98. package/src/locales/fr.json +680 -431
  99. package/src/locales/index.js +12 -2
  100. package/src/locales/it.json +727 -0
  101. package/src/locales/ja.json +727 -0
  102. package/src/locales/ko.json +727 -0
  103. package/src/locales/nl.json +727 -0
  104. package/src/locales/pl.json +727 -0
  105. package/src/locales/pt-BR.json +727 -0
  106. package/src/locales/ru.json +727 -0
  107. package/src/locales/tr.json +727 -0
  108. package/src/locales/zh-CN.json +727 -0
  109. package/src/pages/Admin/ApiTokens/Index.vue +381 -217
  110. package/src/pages/Admin/AuditLog/Index.vue +154 -0
  111. package/src/pages/Admin/BusinessHours/Form.vue +172 -0
  112. package/src/pages/Admin/BusinessHours/Index.vue +113 -0
  113. package/src/pages/Admin/CannedResponses/Index.vue +82 -25
  114. package/src/pages/Admin/Capacity/Index.vue +134 -0
  115. package/src/pages/Admin/CustomFields/Form.vue +195 -0
  116. package/src/pages/Admin/CustomFields/Index.vue +134 -0
  117. package/src/pages/Admin/CustomObjects/Form.vue +202 -0
  118. package/src/pages/Admin/CustomObjects/Index.vue +127 -0
  119. package/src/pages/Admin/CustomObjects/Records.vue +228 -0
  120. package/src/pages/Admin/Departments/Form.vue +35 -11
  121. package/src/pages/Admin/Departments/Index.vue +74 -21
  122. package/src/pages/Admin/EscalationRules/Form.vue +86 -25
  123. package/src/pages/Admin/EscalationRules/Index.vue +158 -37
  124. package/src/pages/Admin/Import/Connect.vue +95 -0
  125. package/src/pages/Admin/Import/Index.vue +102 -0
  126. package/src/pages/Admin/Import/Mapping.vue +110 -0
  127. package/src/pages/Admin/Import/Progress.vue +153 -0
  128. package/src/pages/Admin/Import/Review.vue +76 -0
  129. package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -0
  130. package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -0
  131. package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -0
  132. package/src/pages/Admin/Macros/Index.vue +448 -287
  133. package/src/pages/Admin/Plugins/Index.vue +327 -197
  134. package/src/pages/Admin/Reports/AgentDetail.vue +132 -0
  135. package/src/pages/Admin/Reports/AgentMetrics.vue +115 -0
  136. package/src/pages/Admin/Reports/AgentRanking.vue +246 -0
  137. package/src/pages/Admin/Reports/Cohorts.vue +180 -0
  138. package/src/pages/Admin/Reports/Comparison.vue +141 -0
  139. package/src/pages/Admin/Reports/CsatReport.vue +127 -0
  140. package/src/pages/Admin/Reports/Dashboard.vue +226 -0
  141. package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -0
  142. package/src/pages/Admin/Reports/ResponseTimes.vue +164 -0
  143. package/src/pages/Admin/Reports/SlaReport.vue +162 -0
  144. package/src/pages/Admin/Reports/SlaTrends.vue +183 -0
  145. package/src/pages/Admin/Reports.vue +206 -101
  146. package/src/pages/Admin/Roles/Form.vue +71 -0
  147. package/src/pages/Admin/Roles/Index.vue +122 -0
  148. package/src/pages/Admin/Settings/CsatSettings.vue +165 -0
  149. package/src/pages/Admin/Settings/DataRetention.vue +182 -0
  150. package/src/pages/Admin/Settings/EmailSettings.vue +243 -0
  151. package/src/pages/Admin/Settings/Sandbox.vue +135 -0
  152. package/src/pages/Admin/Settings/SsoSettings.vue +218 -0
  153. package/src/pages/Admin/Settings/TwoFactor.vue +110 -0
  154. package/src/pages/Admin/Settings.vue +847 -99
  155. package/src/pages/Admin/Skills/Form.vue +53 -0
  156. package/src/pages/Admin/Skills/Index.vue +94 -0
  157. package/src/pages/Admin/SlaPolicies/Form.vue +62 -19
  158. package/src/pages/Admin/SlaPolicies/Index.vue +85 -23
  159. package/src/pages/Admin/Statuses/Form.vue +114 -0
  160. package/src/pages/Admin/Statuses/Index.vue +153 -0
  161. package/src/pages/Admin/Tags/Index.vue +121 -35
  162. package/src/pages/Admin/Tickets/Index.vue +96 -9
  163. package/src/pages/Admin/Tickets/Show.vue +130 -37
  164. package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -0
  165. package/src/pages/Admin/Webhooks/Form.vue +110 -0
  166. package/src/pages/Admin/Webhooks/Index.vue +136 -0
  167. package/src/pages/Admin/Workflows/Builder.vue +207 -0
  168. package/src/pages/Admin/Workflows/Index.vue +235 -0
  169. package/src/pages/Admin/Workflows/Logs.vue +286 -0
  170. package/src/pages/Agent/Dashboard.vue +311 -186
  171. package/src/pages/Agent/TicketIndex.vue +80 -6
  172. package/src/pages/Agent/TicketShow.vue +399 -60
  173. package/src/pages/Customer/Create.vue +37 -10
  174. package/src/pages/Customer/Index.vue +9 -2
  175. package/src/pages/Customer/KnowledgeBase/Article.vue +129 -0
  176. package/src/pages/Customer/KnowledgeBase/Index.vue +151 -0
  177. package/src/pages/Customer/Show.vue +69 -63
  178. package/src/pages/Guest/Create.vue +40 -15
  179. package/src/pages/Guest/Show.vue +124 -93
  180. package/src/plugin.js +111 -1
  181. package/src/utils/constants.js +30 -0
  182. package/src/utils/formatting.js +35 -0
  183. package/src/utils/mentionHighlight.js +24 -0
  184. package/src/utils/sanitizeHtml.js +78 -64
  185. package/src/widget/EscalatedWidget.vue +804 -0
  186. package/src/widget/index.js +254 -0
  187. package/src/pages/Admin/Plugins.vue +0 -129
@@ -0,0 +1,193 @@
1
+ <script setup>
2
+ import { ref, watch, onMounted } from 'vue';
3
+ import { router } from '@inertiajs/vue3';
4
+
5
+ const props = defineProps({
6
+ ticketReference: { type: String, required: true },
7
+ });
8
+
9
+ const links = ref([]);
10
+ const searchTerm = ref('');
11
+ const searchResults = ref([]);
12
+ const linkType = ref('related');
13
+ const loading = ref(false);
14
+ const expanded = ref(true);
15
+ let debounceTimer = null;
16
+
17
+ const linkTypeLabels = {
18
+ problem_incident: 'Problem/Incident',
19
+ parent_child: 'Parent/Child',
20
+ related: 'Related',
21
+ };
22
+
23
+ onMounted(() => {
24
+ fetchLinks();
25
+ });
26
+
27
+ async function fetchLinks() {
28
+ try {
29
+ const response = await fetch(route('escalated.admin.tickets.links.index', props.ticketReference), {
30
+ headers: {
31
+ Accept: 'application/json',
32
+ 'X-Requested-With': 'XMLHttpRequest',
33
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
34
+ },
35
+ });
36
+ if (response.ok) {
37
+ const data = await response.json();
38
+ links.value = data.links || [];
39
+ }
40
+ } catch {
41
+ // silently ignore
42
+ }
43
+ }
44
+
45
+ watch(searchTerm, (val) => {
46
+ window.clearTimeout(debounceTimer);
47
+ if (!val || val.length < 2) {
48
+ searchResults.value = [];
49
+ return;
50
+ }
51
+ debounceTimer = window.setTimeout(() => searchTickets(val), 300);
52
+ });
53
+
54
+ async function searchTickets(term) {
55
+ loading.value = true;
56
+ try {
57
+ const response = await fetch(route('escalated.admin.tickets.merge-search', { q: term }), {
58
+ headers: {
59
+ Accept: 'application/json',
60
+ 'X-Requested-With': 'XMLHttpRequest',
61
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
62
+ },
63
+ });
64
+ if (response.ok) {
65
+ const data = await response.json();
66
+ searchResults.value = (data.tickets || []).filter((t) => t.reference !== props.ticketReference);
67
+ }
68
+ } catch {
69
+ // silently ignore
70
+ } finally {
71
+ loading.value = false;
72
+ }
73
+ }
74
+
75
+ function addLink(targetRef) {
76
+ router.post(
77
+ route('escalated.admin.tickets.links.store', props.ticketReference),
78
+ {
79
+ target_reference: targetRef,
80
+ link_type: linkType.value,
81
+ },
82
+ {
83
+ preserveScroll: true,
84
+ onSuccess: () => {
85
+ searchTerm.value = '';
86
+ searchResults.value = [];
87
+ fetchLinks();
88
+ },
89
+ },
90
+ );
91
+ }
92
+
93
+ function removeLink(linkId) {
94
+ router.delete(route('escalated.admin.tickets.links.destroy', [props.ticketReference, linkId]), {
95
+ preserveScroll: true,
96
+ onSuccess: () => {
97
+ fetchLinks();
98
+ },
99
+ });
100
+ }
101
+
102
+ function groupedLinks() {
103
+ const groups = {};
104
+ for (const link of links.value) {
105
+ const type = link.link_type;
106
+ if (!groups[type]) groups[type] = [];
107
+ groups[type].push(link);
108
+ }
109
+ return groups;
110
+ }
111
+ </script>
112
+
113
+ <template>
114
+ <div class="rounded-xl border border-white/[0.06] bg-neutral-900/60 p-4">
115
+ <button
116
+ class="flex w-full items-center justify-between text-sm font-semibold text-neutral-200"
117
+ @click="expanded = !expanded"
118
+ >
119
+ <span>Linked Tickets</span>
120
+ <svg
121
+ :class="['h-4 w-4 transition-transform text-neutral-500', expanded && 'rotate-180']"
122
+ fill="none"
123
+ stroke="currentColor"
124
+ stroke-width="1.5"
125
+ viewBox="0 0 24 24"
126
+ >
127
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
128
+ </svg>
129
+ </button>
130
+
131
+ <div v-if="expanded" class="mt-3 space-y-3">
132
+ <!-- Grouped links -->
133
+ <div v-for="(groupLinks, type) in groupedLinks()" :key="type">
134
+ <p class="text-xs font-semibold uppercase tracking-wider text-neutral-500">
135
+ {{ linkTypeLabels[type] || type }}
136
+ </p>
137
+ <div class="mt-1 space-y-1">
138
+ <div
139
+ v-for="link in groupLinks"
140
+ :key="link.id"
141
+ class="flex items-center justify-between rounded-lg bg-white/[0.02] px-2 py-1.5"
142
+ >
143
+ <div class="text-sm">
144
+ <span class="font-mono text-xs text-neutral-400">{{ link.ticket.reference }}</span>
145
+ <span class="ml-1 text-neutral-300">{{ link.ticket.subject }}</span>
146
+ </div>
147
+ <button
148
+ aria-label="Remove link"
149
+ class="text-rose-400 hover:text-rose-300 text-xs"
150
+ @click="removeLink(link.id)"
151
+ >
152
+ Unlink
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <div v-if="!links.length" class="text-xs text-neutral-500">No linked tickets.</div>
159
+
160
+ <!-- Add link form -->
161
+ <div class="border-t border-white/[0.06] pt-3">
162
+ <div class="flex gap-2 mb-2">
163
+ <select
164
+ v-model="linkType"
165
+ class="rounded-lg border border-white/10 bg-neutral-950 px-2 py-1 text-xs text-neutral-300 focus:border-white/20 focus:outline-none"
166
+ >
167
+ <option value="related">Related</option>
168
+ <option value="problem_incident">Problem/Incident</option>
169
+ <option value="parent_child">Parent/Child</option>
170
+ </select>
171
+ </div>
172
+ <input
173
+ v-model="searchTerm"
174
+ type="text"
175
+ placeholder="Search ticket to link..."
176
+ class="w-full rounded-lg border border-white/10 bg-neutral-950 px-2 py-1.5 text-xs text-neutral-200 placeholder-neutral-500 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10"
177
+ />
178
+ <div v-if="loading" class="mt-2 text-xs text-neutral-500">Searching...</div>
179
+ <div v-else-if="searchResults.length" class="mt-2 max-h-32 space-y-1 overflow-y-auto">
180
+ <button
181
+ v-for="ticket in searchResults"
182
+ :key="ticket.id"
183
+ class="w-full rounded-lg px-2 py-1 text-left text-xs text-neutral-300 transition-colors hover:bg-white/[0.04]"
184
+ @click="addLink(ticket.reference)"
185
+ >
186
+ <span class="font-mono text-neutral-400">{{ ticket.reference }}</span>
187
+ <span class="ml-1">{{ ticket.subject }}</span>
188
+ </button>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </template>