@escalated-dev/escalated 0.7.1 → 0.9.0
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/LICENSE +21 -21
- package/README.md +380 -357
- package/package.json +85 -81
- package/src/components/ActiveChatsPanel.vue +246 -246
- package/src/components/AdminDashboard.stories.js +1423 -1423
- package/src/components/AttachmentList.vue +65 -65
- package/src/components/ChatActionBar.vue +226 -226
- package/src/components/ChatBubble.vue +162 -162
- package/src/components/ChatComposer.vue +308 -308
- package/src/components/ChatQueue.vue +222 -222
- package/src/components/ChatThread.vue +197 -197
- package/src/components/CustomTicketActions.stories.js +71 -0
- package/src/components/CustomTicketActions.vue +101 -0
- package/src/components/EscalatedLayout.vue +619 -598
- package/src/components/FollowButton.vue +20 -1
- package/src/components/MentionDropdown.vue +154 -154
- package/src/components/MetricComparison.vue +89 -89
- package/src/components/PercentileChart.vue +67 -67
- package/src/components/PeriodSelector.vue +98 -98
- package/src/components/PresenceIndicator.vue +228 -213
- package/src/components/ReplyComposer.vue +336 -336
- package/src/components/ReplyThread.vue +153 -153
- package/src/components/ReportChart.vue +395 -395
- package/src/components/ReportExportButton.vue +108 -108
- package/src/components/SaveViewDialog.vue +172 -172
- package/src/components/SavedViewSidebar.vue +208 -208
- package/src/components/SkillTagManager.vue +29 -22
- package/src/components/SnoozeButton.vue +247 -247
- package/src/components/TicketFilters.vue +270 -270
- package/src/components/TicketList.vue +655 -655
- package/src/components/TicketSidebar.vue +4 -0
- package/src/components/TicketSplitDialog.vue +98 -98
- package/src/components/TicketSubjects.stories.js +63 -0
- package/src/components/TicketSubjects.vue +107 -0
- package/src/components/TwoFactorSetup.vue +1 -37
- package/src/components/VariableTokenInput.vue +111 -111
- package/src/components/WorkflowActionConfig.vue +287 -287
- package/src/components/WorkflowActionList.vue +350 -350
- package/src/components/WorkflowConditionBuilder.vue +265 -265
- package/src/components/WorkflowTriggerSelector.vue +125 -125
- package/src/components/admin/newsletters/AnalyticsTiles.stories.js +14 -0
- package/src/components/admin/newsletters/AnalyticsTiles.vue +55 -0
- package/src/components/admin/newsletters/DeliveriesTable.stories.js +28 -0
- package/src/components/admin/newsletters/DeliveriesTable.vue +104 -0
- package/src/components/admin/newsletters/DynamicFilterBuilder.stories.js +11 -0
- package/src/components/admin/newsletters/DynamicFilterBuilder.vue +53 -0
- package/src/components/admin/newsletters/ListMemberTable.stories.js +12 -0
- package/src/components/admin/newsletters/ListMemberTable.vue +53 -0
- package/src/components/admin/newsletters/MarkdownEditor.stories.js +6 -0
- package/src/components/admin/newsletters/MarkdownEditor.vue +53 -0
- package/src/components/admin/newsletters/MergeFieldDropdown.stories.js +6 -0
- package/src/components/admin/newsletters/MergeFieldDropdown.vue +84 -0
- package/src/components/admin/newsletters/PreviewIframe.stories.js +7 -0
- package/src/components/admin/newsletters/PreviewIframe.vue +40 -0
- package/src/components/admin/newsletters/SummaryTiles.stories.js +8 -0
- package/src/components/admin/newsletters/SummaryTiles.vue +63 -0
- package/src/composables/useChat.js +187 -187
- package/src/composables/useI18n.js +30 -8
- package/src/composables/useMentions.js +134 -134
- package/src/composables/usePermissions.js +31 -0
- package/src/composables/useRealtime.js +182 -182
- package/src/index.js +85 -84
- package/src/locales/README.md +13 -0
- package/src/locales/ar.json +727 -727
- package/src/locales/de.json +690 -690
- package/src/locales/en.json +889 -729
- package/src/locales/es.json +691 -691
- package/src/locales/fr.json +691 -691
- package/src/locales/index.js +16 -16
- package/src/locales/it.json +727 -727
- package/src/locales/ja.json +727 -727
- package/src/locales/ko.json +727 -727
- package/src/locales/nl.json +727 -727
- package/src/locales/pl.json +727 -727
- package/src/locales/pt-BR.json +727 -727
- package/src/locales/ru.json +727 -727
- package/src/locales/tr.json +727 -727
- package/src/locales/zh-CN.json +727 -727
- package/src/pages/Admin/Newsletters/Compose.vue +192 -0
- package/src/pages/Admin/Newsletters/Edit.vue +24 -0
- package/src/pages/Admin/Newsletters/Index.vue +141 -0
- package/src/pages/Admin/Newsletters/Lists/Create.vue +64 -0
- package/src/pages/Admin/Newsletters/Lists/Index.vue +59 -0
- package/src/pages/Admin/Newsletters/Lists/Show.vue +53 -0
- package/src/pages/Admin/Newsletters/Settings.vue +85 -0
- package/src/pages/Admin/Newsletters/Show.vue +140 -0
- package/src/pages/Admin/Newsletters/Templates/Create.vue +11 -0
- package/src/pages/Admin/Newsletters/Templates/Index.vue +54 -0
- package/src/pages/Admin/Newsletters/Templates/Show.vue +65 -0
- package/src/pages/Admin/Newsletters/usePreview.js +44 -0
- package/src/pages/Admin/Reports/AgentDetail.vue +132 -132
- package/src/pages/Admin/Reports/AgentRanking.vue +246 -246
- package/src/pages/Admin/Reports/Cohorts.vue +180 -180
- package/src/pages/Admin/Reports/Comparison.vue +141 -141
- package/src/pages/Admin/Reports/Dashboard.vue +226 -226
- package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -164
- package/src/pages/Admin/Reports/ResponseTimes.vue +164 -164
- package/src/pages/Admin/Reports/SlaTrends.vue +183 -183
- package/src/pages/Admin/Settings/SsoSettings.vue +157 -4
- package/src/pages/Admin/Settings/TwoFactor.vue +32 -6
- package/src/pages/Admin/Settings.vue +1008 -1008
- package/src/pages/Admin/Skills/Form.vue +164 -4
- package/src/pages/Admin/Skills/Index.vue +14 -3
- package/src/pages/Admin/Tickets/Index.vue +126 -126
- package/src/pages/Admin/Tickets/Show.vue +254 -249
- package/src/pages/Admin/Users/Index.vue +145 -0
- package/src/pages/Admin/Workflows/Builder.vue +207 -207
- package/src/pages/Admin/Workflows/Index.vue +235 -235
- package/src/pages/Admin/Workflows/Logs.vue +286 -286
- package/src/pages/Agent/TicketIndex.vue +112 -112
- package/src/pages/Agent/TicketShow.vue +547 -494
- package/src/pages/Customer/Index.vue +26 -1
- package/src/pages/Customer/KnowledgeBase/Article.vue +129 -129
- package/src/plugin.js +175 -175
- package/src/utils/mentionHighlight.js +24 -24
- package/src/widget/EscalatedWidget.vue +804 -804
- package/src/widget/index.js +254 -254
|
@@ -1,249 +1,254 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
-
import StatusBadge from '../../../components/StatusBadge.vue';
|
|
4
|
-
import PriorityBadge from '../../../components/PriorityBadge.vue';
|
|
5
|
-
import ReplyThread from '../../../components/ReplyThread.vue';
|
|
6
|
-
import ReplyComposer from '../../../components/ReplyComposer.vue';
|
|
7
|
-
import TicketSidebar from '../../../components/TicketSidebar.vue';
|
|
8
|
-
import AttachmentList from '../../../components/AttachmentList.vue';
|
|
9
|
-
import MacroDropdown from '../../../components/MacroDropdown.vue';
|
|
10
|
-
import FollowButton from '../../../components/FollowButton.vue';
|
|
11
|
-
import PresenceIndicator from '../../../components/PresenceIndicator.vue';
|
|
12
|
-
import PinnedNotes from '../../../components/PinnedNotes.vue';
|
|
13
|
-
import KeyboardShortcutHelp from '../../../components/KeyboardShortcutHelp.vue';
|
|
14
|
-
import PluginSlot from '../../../components/PluginSlot.vue';
|
|
15
|
-
import SnoozeButton from '../../../components/SnoozeButton.vue';
|
|
16
|
-
import { useKeyboardShortcuts } from '../../../composables/useKeyboardShortcuts';
|
|
17
|
-
import { usePluginExtensions } from '../../../composables/usePluginExtensions';
|
|
18
|
-
import { router, useForm, usePage } from '@inertiajs/vue3';
|
|
19
|
-
import { ref } from 'vue';
|
|
20
|
-
|
|
21
|
-
const props = defineProps({
|
|
22
|
-
ticket: Object,
|
|
23
|
-
departments: Array,
|
|
24
|
-
tags: Array,
|
|
25
|
-
cannedResponses: Array,
|
|
26
|
-
agents: Array,
|
|
27
|
-
macros: { type: Array, default: () => [] },
|
|
28
|
-
is_following: { type: Boolean, default: false },
|
|
29
|
-
followers_count: { type: Number, default: 0 },
|
|
30
|
-
is_snoozed: { type: Boolean, default: false },
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const page = usePage();
|
|
34
|
-
const activeTab = ref('reply');
|
|
35
|
-
const showShortcutHelp = ref(false);
|
|
36
|
-
const replyComposerRef = ref(null);
|
|
37
|
-
const statusSelectRef = ref(null);
|
|
38
|
-
const prioritySelectRef = ref(null);
|
|
39
|
-
|
|
40
|
-
const statusForm = useForm({ status: '' });
|
|
41
|
-
const priorityForm = useForm({ priority: '' });
|
|
42
|
-
const assignForm = useForm({ agent_id: '' });
|
|
43
|
-
|
|
44
|
-
function changeStatus(status) {
|
|
45
|
-
statusForm.status = status;
|
|
46
|
-
statusForm.post(route('escalated.admin.tickets.status', props.ticket.reference), { preserveScroll: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function changePriority(priority) {
|
|
50
|
-
priorityForm.priority = priority;
|
|
51
|
-
priorityForm.post(route('escalated.admin.tickets.priority', props.ticket.reference), { preserveScroll: true });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function assignToMe() {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
:
|
|
135
|
-
:action="route('escalated.admin.tickets.
|
|
136
|
-
/>
|
|
137
|
-
<
|
|
138
|
-
v-if="
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<option value="
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
:action="route('escalated.admin.tickets.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1
|
+
<script setup>
|
|
2
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
+
import StatusBadge from '../../../components/StatusBadge.vue';
|
|
4
|
+
import PriorityBadge from '../../../components/PriorityBadge.vue';
|
|
5
|
+
import ReplyThread from '../../../components/ReplyThread.vue';
|
|
6
|
+
import ReplyComposer from '../../../components/ReplyComposer.vue';
|
|
7
|
+
import TicketSidebar from '../../../components/TicketSidebar.vue';
|
|
8
|
+
import AttachmentList from '../../../components/AttachmentList.vue';
|
|
9
|
+
import MacroDropdown from '../../../components/MacroDropdown.vue';
|
|
10
|
+
import FollowButton from '../../../components/FollowButton.vue';
|
|
11
|
+
import PresenceIndicator from '../../../components/PresenceIndicator.vue';
|
|
12
|
+
import PinnedNotes from '../../../components/PinnedNotes.vue';
|
|
13
|
+
import KeyboardShortcutHelp from '../../../components/KeyboardShortcutHelp.vue';
|
|
14
|
+
import PluginSlot from '../../../components/PluginSlot.vue';
|
|
15
|
+
import SnoozeButton from '../../../components/SnoozeButton.vue';
|
|
16
|
+
import { useKeyboardShortcuts } from '../../../composables/useKeyboardShortcuts';
|
|
17
|
+
import { usePluginExtensions } from '../../../composables/usePluginExtensions';
|
|
18
|
+
import { router, useForm, usePage } from '@inertiajs/vue3';
|
|
19
|
+
import { ref } from 'vue';
|
|
20
|
+
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
ticket: Object,
|
|
23
|
+
departments: Array,
|
|
24
|
+
tags: Array,
|
|
25
|
+
cannedResponses: Array,
|
|
26
|
+
agents: Array,
|
|
27
|
+
macros: { type: Array, default: () => [] },
|
|
28
|
+
is_following: { type: Boolean, default: false },
|
|
29
|
+
followers_count: { type: Number, default: 0 },
|
|
30
|
+
is_snoozed: { type: Boolean, default: false },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const page = usePage();
|
|
34
|
+
const activeTab = ref('reply');
|
|
35
|
+
const showShortcutHelp = ref(false);
|
|
36
|
+
const replyComposerRef = ref(null);
|
|
37
|
+
const statusSelectRef = ref(null);
|
|
38
|
+
const prioritySelectRef = ref(null);
|
|
39
|
+
|
|
40
|
+
const statusForm = useForm({ status: '' });
|
|
41
|
+
const priorityForm = useForm({ priority: '' });
|
|
42
|
+
const assignForm = useForm({ agent_id: '' });
|
|
43
|
+
|
|
44
|
+
function changeStatus(status) {
|
|
45
|
+
statusForm.status = status;
|
|
46
|
+
statusForm.post(route('escalated.admin.tickets.status', props.ticket.reference), { preserveScroll: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function changePriority(priority) {
|
|
50
|
+
priorityForm.priority = priority;
|
|
51
|
+
priorityForm.post(route('escalated.admin.tickets.priority', props.ticket.reference), { preserveScroll: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function assignToMe() {
|
|
55
|
+
// auth.user may be absent if the host app hasn't shared it (other call
|
|
56
|
+
// sites in this page already guard with auth?.user?.id). Bail rather than
|
|
57
|
+
// crash on the click.
|
|
58
|
+
const userId = page.props.auth?.user?.id;
|
|
59
|
+
if (userId == null) return;
|
|
60
|
+
assignForm.agent_id = userId;
|
|
61
|
+
assignForm.post(route('escalated.admin.tickets.assign', props.ticket.reference), { preserveScroll: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toggleFollow() {
|
|
65
|
+
router.post(route('escalated.admin.tickets.follow', props.ticket.reference), {}, { preserveScroll: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { getPageComponents } = usePluginExtensions();
|
|
69
|
+
|
|
70
|
+
// Keyboard shortcuts
|
|
71
|
+
useKeyboardShortcuts({
|
|
72
|
+
r: () => {
|
|
73
|
+
activeTab.value = 'reply';
|
|
74
|
+
replyComposerRef.value?.$el?.querySelector('textarea')?.focus();
|
|
75
|
+
},
|
|
76
|
+
n: () => {
|
|
77
|
+
activeTab.value = 'note';
|
|
78
|
+
},
|
|
79
|
+
s: () => {
|
|
80
|
+
statusSelectRef.value?.focus();
|
|
81
|
+
},
|
|
82
|
+
p: () => {
|
|
83
|
+
prioritySelectRef.value?.focus();
|
|
84
|
+
},
|
|
85
|
+
f: () => {
|
|
86
|
+
toggleFollow();
|
|
87
|
+
},
|
|
88
|
+
'?': () => {
|
|
89
|
+
showShortcutHelp.value = true;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<EscalatedLayout :title="ticket.subject">
|
|
96
|
+
<!-- Plugin: ticket show header slot -->
|
|
97
|
+
<PluginSlot slot="ticket.show.header" :components="getPageComponents('ticket.show', 'header')" />
|
|
98
|
+
|
|
99
|
+
<!-- Snoozed banner (visible only when ticket is snoozed) -->
|
|
100
|
+
<div
|
|
101
|
+
v-if="ticket.is_snoozed || (ticket.snoozed_until && new Date(ticket.snoozed_until) > new Date())"
|
|
102
|
+
class="mb-4 flex items-center gap-3 rounded-lg px-4 py-2.5 text-sm font-medium border border-amber-500/20 bg-amber-500/10 text-amber-300"
|
|
103
|
+
>
|
|
104
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
|
105
|
+
<path
|
|
106
|
+
fill-rule="evenodd"
|
|
107
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.828a1 1 0 101.415-1.414L11 9.586V6z"
|
|
108
|
+
clip-rule="evenodd"
|
|
109
|
+
/>
|
|
110
|
+
</svg>
|
|
111
|
+
<span
|
|
112
|
+
>Snoozed until
|
|
113
|
+
{{
|
|
114
|
+
new Date(ticket.snoozed_until).toLocaleString(undefined, {
|
|
115
|
+
month: 'short',
|
|
116
|
+
day: 'numeric',
|
|
117
|
+
year: 'numeric',
|
|
118
|
+
hour: 'numeric',
|
|
119
|
+
minute: '2-digit',
|
|
120
|
+
})
|
|
121
|
+
}}</span
|
|
122
|
+
>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div class="mb-5 flex flex-wrap items-center gap-3">
|
|
126
|
+
<span class="text-sm font-mono font-medium text-[var(--esc-panel-text)]">{{ ticket.reference }}</span>
|
|
127
|
+
<StatusBadge :status="ticket.status" />
|
|
128
|
+
<PriorityBadge :priority="ticket.priority" />
|
|
129
|
+
<span class="text-sm text-[var(--esc-panel-text-muted)]">by {{ ticket.requester?.name }}</span>
|
|
130
|
+
<PresenceIndicator :ticket-reference="ticket.reference" route-prefix="escalated.admin" />
|
|
131
|
+
<div class="ml-auto flex items-center gap-2">
|
|
132
|
+
<FollowButton
|
|
133
|
+
:is-following="is_following"
|
|
134
|
+
:followers-count="followers_count"
|
|
135
|
+
:action="route('escalated.admin.tickets.follow', ticket.reference)"
|
|
136
|
+
/>
|
|
137
|
+
<MacroDropdown
|
|
138
|
+
v-if="macros.length"
|
|
139
|
+
:macros="macros"
|
|
140
|
+
:action="route('escalated.admin.tickets.macro', ticket.reference)"
|
|
141
|
+
/>
|
|
142
|
+
<button
|
|
143
|
+
v-if="!ticket.assigned_to"
|
|
144
|
+
class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)]"
|
|
145
|
+
@click="assignToMe"
|
|
146
|
+
>
|
|
147
|
+
Assign to Me
|
|
148
|
+
</button>
|
|
149
|
+
<SnoozeButton :ticket="ticket" route-prefix="escalated.admin" />
|
|
150
|
+
<select
|
|
151
|
+
ref="statusSelectRef"
|
|
152
|
+
class="rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-1.5 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)]"
|
|
153
|
+
@change="
|
|
154
|
+
changeStatus($event.target.value);
|
|
155
|
+
$event.target.value = '';
|
|
156
|
+
"
|
|
157
|
+
>
|
|
158
|
+
<option value="">Change Status...</option>
|
|
159
|
+
<option value="in_progress">In Progress</option>
|
|
160
|
+
<option value="waiting_on_customer">Waiting on Customer</option>
|
|
161
|
+
<option value="resolved">Resolved</option>
|
|
162
|
+
<option value="closed">Closed</option>
|
|
163
|
+
</select>
|
|
164
|
+
<select
|
|
165
|
+
ref="prioritySelectRef"
|
|
166
|
+
class="rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-1.5 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)]"
|
|
167
|
+
@change="
|
|
168
|
+
changePriority($event.target.value);
|
|
169
|
+
$event.target.value = '';
|
|
170
|
+
"
|
|
171
|
+
>
|
|
172
|
+
<option value="">Change Priority...</option>
|
|
173
|
+
<option value="low">Low</option>
|
|
174
|
+
<option value="medium">Medium</option>
|
|
175
|
+
<option value="high">High</option>
|
|
176
|
+
<option value="urgent">Urgent</option>
|
|
177
|
+
<option value="critical">Critical</option>
|
|
178
|
+
</select>
|
|
179
|
+
<!-- Plugin: ticket show actions slot -->
|
|
180
|
+
<PluginSlot slot="ticket.show.actions" :components="getPageComponents('ticket.show', 'actions')" />
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
|
184
|
+
<div class="lg:col-span-2 space-y-6">
|
|
185
|
+
<PinnedNotes
|
|
186
|
+
v-if="ticket.pinned_notes?.length"
|
|
187
|
+
:notes="ticket.pinned_notes"
|
|
188
|
+
:ticket-reference="ticket.reference"
|
|
189
|
+
route-prefix="escalated.admin"
|
|
190
|
+
/>
|
|
191
|
+
<div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-5">
|
|
192
|
+
<p class="whitespace-pre-wrap text-sm text-[var(--esc-panel-text-secondary)]">
|
|
193
|
+
{{ ticket.description }}
|
|
194
|
+
</p>
|
|
195
|
+
<AttachmentList v-if="ticket.attachments?.length" :attachments="ticket.attachments" class="mt-3" />
|
|
196
|
+
</div>
|
|
197
|
+
<div>
|
|
198
|
+
<div class="mb-4 flex gap-4 border-b border-[var(--esc-panel-border)]">
|
|
199
|
+
<button
|
|
200
|
+
:class="[
|
|
201
|
+
'pb-2 text-sm font-medium transition-colors',
|
|
202
|
+
activeTab === 'reply'
|
|
203
|
+
? 'border-b-2 border-[var(--esc-panel-accent)] text-[var(--esc-panel-text)]'
|
|
204
|
+
: 'text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]',
|
|
205
|
+
]"
|
|
206
|
+
@click="activeTab = 'reply'"
|
|
207
|
+
>
|
|
208
|
+
Reply
|
|
209
|
+
</button>
|
|
210
|
+
<button
|
|
211
|
+
:class="[
|
|
212
|
+
'pb-2 text-sm font-medium transition-colors',
|
|
213
|
+
activeTab === 'note'
|
|
214
|
+
? 'border-b-2 border-amber-500 text-amber-400'
|
|
215
|
+
: 'text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]',
|
|
216
|
+
]"
|
|
217
|
+
@click="activeTab = 'note'"
|
|
218
|
+
>
|
|
219
|
+
Internal Note
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
<ReplyComposer
|
|
223
|
+
v-if="activeTab === 'reply'"
|
|
224
|
+
ref="replyComposerRef"
|
|
225
|
+
:action="route('escalated.admin.tickets.reply', ticket.reference)"
|
|
226
|
+
:canned-responses="cannedResponses"
|
|
227
|
+
/>
|
|
228
|
+
<ReplyComposer
|
|
229
|
+
v-else
|
|
230
|
+
:action="route('escalated.admin.tickets.note', ticket.reference)"
|
|
231
|
+
placeholder="Write an internal note..."
|
|
232
|
+
submit-label="Add Note"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
<div>
|
|
236
|
+
<h2 class="mb-4 text-lg font-semibold text-[var(--esc-panel-text-secondary)]">Conversation</h2>
|
|
237
|
+
<ReplyThread
|
|
238
|
+
:replies="ticket.replies || []"
|
|
239
|
+
:current-user-id="page.props.auth?.user?.id"
|
|
240
|
+
:ticket-reference="ticket.reference"
|
|
241
|
+
route-prefix="escalated.admin"
|
|
242
|
+
pinnable
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="space-y-6">
|
|
247
|
+
<TicketSidebar :ticket="ticket" :tags="tags" :departments="departments" />
|
|
248
|
+
<!-- Plugin: ticket show sidebar slot -->
|
|
249
|
+
<PluginSlot slot="ticket.show.sidebar" :components="getPageComponents('ticket.show', 'sidebar')" />
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
<KeyboardShortcutHelp v-model:show="showShortcutHelp" context="detail" />
|
|
253
|
+
</EscalatedLayout>
|
|
254
|
+
</template>
|