@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,15 +1,27 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { computed } from 'vue';
|
|
2
3
|
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
-
import
|
|
4
|
+
import SkillTagManager from '../../../components/SkillTagManager.vue';
|
|
5
|
+
import { Link, useForm } from '@inertiajs/vue3';
|
|
4
6
|
|
|
5
7
|
const props = defineProps({
|
|
6
8
|
skill: { type: Object, default: null },
|
|
9
|
+
availableAgents: { type: Array, default: () => [] },
|
|
10
|
+
availableTags: { type: Array, default: () => [] },
|
|
11
|
+
availableDepartments: { type: Array, default: () => [] },
|
|
7
12
|
});
|
|
8
13
|
|
|
9
14
|
const form = useForm({
|
|
10
15
|
name: props.skill?.name || '',
|
|
16
|
+
routing_tag_ids: props.skill?.routing_tag_ids || [],
|
|
17
|
+
routing_department_ids: props.skill?.routing_department_ids || [],
|
|
18
|
+
agents: props.skill?.agents?.length ? props.skill.agents.map((agent) => ({ ...agent })) : [],
|
|
11
19
|
});
|
|
12
20
|
|
|
21
|
+
const unassignedAgents = computed(() =>
|
|
22
|
+
props.availableAgents.filter((agent) => !form.agents.some((entry) => entry.user_id === agent.id)),
|
|
23
|
+
);
|
|
24
|
+
|
|
13
25
|
function submit() {
|
|
14
26
|
if (props.skill) {
|
|
15
27
|
form.put(route('escalated.admin.skills.update', props.skill.id));
|
|
@@ -17,11 +29,27 @@ function submit() {
|
|
|
17
29
|
form.post(route('escalated.admin.skills.store'));
|
|
18
30
|
}
|
|
19
31
|
}
|
|
32
|
+
|
|
33
|
+
function addAgent() {
|
|
34
|
+
const nextAgent = unassignedAgents.value[0];
|
|
35
|
+
if (!nextAgent) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
form.agents.push({ user_id: nextAgent.id, proficiency: 3 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function removeAgent(index) {
|
|
43
|
+
form.agents.splice(index, 1);
|
|
44
|
+
}
|
|
20
45
|
</script>
|
|
21
46
|
|
|
22
47
|
<template>
|
|
23
48
|
<EscalatedLayout :title="skill ? 'Edit Skill' : 'Create Skill'">
|
|
24
|
-
<form
|
|
49
|
+
<form
|
|
50
|
+
class="mx-auto max-w-4xl space-y-5 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
|
|
51
|
+
@submit.prevent="submit"
|
|
52
|
+
>
|
|
25
53
|
<div>
|
|
26
54
|
<label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Name</label>
|
|
27
55
|
<input
|
|
@@ -33,6 +61,138 @@ function submit() {
|
|
|
33
61
|
<p v-if="form.errors.name" class="mt-1 text-xs text-rose-400">{{ form.errors.name }}</p>
|
|
34
62
|
</div>
|
|
35
63
|
|
|
64
|
+
<div class="grid gap-5 lg:grid-cols-2">
|
|
65
|
+
<div
|
|
66
|
+
class="space-y-3 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface-alt)] p-4"
|
|
67
|
+
>
|
|
68
|
+
<div>
|
|
69
|
+
<h2 class="text-sm font-semibold text-[var(--esc-panel-text-secondary)]">Routing Rules</h2>
|
|
70
|
+
<p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
|
|
71
|
+
Match tickets by tag or department to require this skill during auto-assignment.
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
<div>
|
|
75
|
+
<label
|
|
76
|
+
class="mb-2 block text-xs font-medium uppercase tracking-wide text-[var(--esc-panel-text-muted)]"
|
|
77
|
+
>
|
|
78
|
+
Required Tags
|
|
79
|
+
</label>
|
|
80
|
+
<SkillTagManager
|
|
81
|
+
v-model="form.routing_tag_ids"
|
|
82
|
+
:items="availableTags"
|
|
83
|
+
add-label="Add Tag"
|
|
84
|
+
empty-label="No ticket tags mapped"
|
|
85
|
+
/>
|
|
86
|
+
<p v-if="form.errors.routing_tag_ids" class="mt-1 text-xs text-rose-400">
|
|
87
|
+
{{ form.errors.routing_tag_ids }}
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
<div>
|
|
91
|
+
<label
|
|
92
|
+
class="mb-2 block text-xs font-medium uppercase tracking-wide text-[var(--esc-panel-text-muted)]"
|
|
93
|
+
>
|
|
94
|
+
Departments
|
|
95
|
+
</label>
|
|
96
|
+
<SkillTagManager
|
|
97
|
+
v-model="form.routing_department_ids"
|
|
98
|
+
:items="availableDepartments"
|
|
99
|
+
add-label="Add Department"
|
|
100
|
+
empty-label="No departments mapped"
|
|
101
|
+
/>
|
|
102
|
+
<p v-if="form.errors.routing_department_ids" class="mt-1 text-xs text-rose-400">
|
|
103
|
+
{{ form.errors.routing_department_ids }}
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div
|
|
109
|
+
class="space-y-3 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface-alt)] p-4"
|
|
110
|
+
>
|
|
111
|
+
<div class="flex items-start justify-between gap-3">
|
|
112
|
+
<div>
|
|
113
|
+
<h2 class="text-sm font-semibold text-[var(--esc-panel-text-secondary)]">
|
|
114
|
+
Agent Proficiency
|
|
115
|
+
</h2>
|
|
116
|
+
<p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
|
|
117
|
+
Assign eligible agents and tune their proficiency from 1 to 5.
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
<button
|
|
121
|
+
v-if="unassignedAgents.length"
|
|
122
|
+
type="button"
|
|
123
|
+
class="rounded-lg border border-dashed border-[var(--esc-panel-border-input)] px-3 py-1.5 text-xs font-medium text-[var(--esc-panel-text-secondary)] transition-colors hover:bg-[var(--esc-panel-hover)]"
|
|
124
|
+
@click="addAgent"
|
|
125
|
+
>
|
|
126
|
+
Add Agent
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div v-if="form.agents.length" class="space-y-3">
|
|
131
|
+
<div
|
|
132
|
+
v-for="(agent, index) in form.agents"
|
|
133
|
+
:key="`${agent.user_id}-${index}`"
|
|
134
|
+
class="grid gap-3 rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-3 md:grid-cols-[minmax(0,1fr)_110px_auto]"
|
|
135
|
+
>
|
|
136
|
+
<div>
|
|
137
|
+
<label
|
|
138
|
+
class="mb-1 block text-xs font-medium uppercase tracking-wide text-[var(--esc-panel-text-muted)]"
|
|
139
|
+
>
|
|
140
|
+
Agent
|
|
141
|
+
</label>
|
|
142
|
+
<select
|
|
143
|
+
v-model="agent.user_id"
|
|
144
|
+
class="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)]"
|
|
145
|
+
>
|
|
146
|
+
<option
|
|
147
|
+
v-for="option in [
|
|
148
|
+
...unassignedAgents,
|
|
149
|
+
availableAgents.find((entry) => entry.id === agent.user_id),
|
|
150
|
+
].filter(Boolean)"
|
|
151
|
+
:key="option.id"
|
|
152
|
+
:value="option.id"
|
|
153
|
+
>
|
|
154
|
+
{{ option.name }} / {{ option.email }}
|
|
155
|
+
</option>
|
|
156
|
+
</select>
|
|
157
|
+
</div>
|
|
158
|
+
<div>
|
|
159
|
+
<label
|
|
160
|
+
class="mb-1 block text-xs font-medium uppercase tracking-wide text-[var(--esc-panel-text-muted)]"
|
|
161
|
+
>
|
|
162
|
+
Proficiency
|
|
163
|
+
</label>
|
|
164
|
+
<select
|
|
165
|
+
v-model="agent.proficiency"
|
|
166
|
+
class="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)]"
|
|
167
|
+
>
|
|
168
|
+
<option :value="1">1</option>
|
|
169
|
+
<option :value="2">2</option>
|
|
170
|
+
<option :value="3">3</option>
|
|
171
|
+
<option :value="4">4</option>
|
|
172
|
+
<option :value="5">5</option>
|
|
173
|
+
</select>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="flex items-end">
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
class="rounded-lg px-3 py-2 text-sm font-medium text-rose-300 transition-colors hover:bg-rose-500/10"
|
|
179
|
+
@click="removeAgent(index)"
|
|
180
|
+
>
|
|
181
|
+
Remove
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<div
|
|
188
|
+
v-else
|
|
189
|
+
class="rounded-lg border border-dashed border-[var(--esc-panel-border)] px-4 py-6 text-center text-sm text-[var(--esc-panel-text-muted)]"
|
|
190
|
+
>
|
|
191
|
+
No agents assigned yet.
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
36
196
|
<div class="flex gap-3">
|
|
37
197
|
<button
|
|
38
198
|
type="submit"
|
|
@@ -41,12 +201,12 @@ function submit() {
|
|
|
41
201
|
>
|
|
42
202
|
{{ skill ? 'Update' : 'Create' }}
|
|
43
203
|
</button>
|
|
44
|
-
<
|
|
204
|
+
<Link
|
|
45
205
|
:href="route('escalated.admin.skills.index')"
|
|
46
206
|
class="rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-hover)] px-4 py-2 text-sm font-medium text-[var(--esc-panel-text-secondary)] transition-colors hover:bg-[var(--esc-panel-hover)]"
|
|
47
207
|
>
|
|
48
208
|
Cancel
|
|
49
|
-
</
|
|
209
|
+
</Link>
|
|
50
210
|
</div>
|
|
51
211
|
</form>
|
|
52
212
|
</EscalatedLayout>
|
|
@@ -33,7 +33,12 @@ function destroy(id) {
|
|
|
33
33
|
<th
|
|
34
34
|
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
35
35
|
>
|
|
36
|
-
|
|
36
|
+
Routing
|
|
37
|
+
</th>
|
|
38
|
+
<th
|
|
39
|
+
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
40
|
+
>
|
|
41
|
+
Updated
|
|
37
42
|
</th>
|
|
38
43
|
<th
|
|
39
44
|
class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
@@ -44,7 +49,7 @@ function destroy(id) {
|
|
|
44
49
|
</thead>
|
|
45
50
|
<tbody class="divide-y divide-[var(--esc-panel-border)]">
|
|
46
51
|
<tr v-if="!skills?.length">
|
|
47
|
-
<td colspan="
|
|
52
|
+
<td colspan="4" class="px-4 py-12 text-center">
|
|
48
53
|
<svg
|
|
49
54
|
class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
|
|
50
55
|
fill="none"
|
|
@@ -73,7 +78,13 @@ function destroy(id) {
|
|
|
73
78
|
{{ skill.name }}
|
|
74
79
|
</td>
|
|
75
80
|
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
|
|
76
|
-
{{ skill.agents_count }}
|
|
81
|
+
<div>{{ skill.agents_count }} agents</div>
|
|
82
|
+
<div class="text-xs text-[var(--esc-panel-text-muted)]">
|
|
83
|
+
{{ skill.routing_tags_count }} tags / {{ skill.routing_departments_count }} departments
|
|
84
|
+
</div>
|
|
85
|
+
</td>
|
|
86
|
+
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
|
|
87
|
+
{{ skill.updated_at ? new Date(skill.updated_at).toLocaleDateString() : '-' }}
|
|
77
88
|
</td>
|
|
78
89
|
<td class="px-4 py-3 text-right text-sm">
|
|
79
90
|
<Link
|
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { ref } from 'vue';
|
|
3
|
-
import { usePage, router } from '@inertiajs/vue3';
|
|
4
|
-
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
5
|
-
import TicketList from '../../../components/TicketList.vue';
|
|
6
|
-
import TicketFilters from '../../../components/TicketFilters.vue';
|
|
7
|
-
import QuickFilters from '../../../components/QuickFilters.vue';
|
|
8
|
-
import BulkActionBar from '../../../components/BulkActionBar.vue';
|
|
9
|
-
import KeyboardShortcutHelp from '../../../components/KeyboardShortcutHelp.vue';
|
|
10
|
-
import PluginSlot from '../../../components/PluginSlot.vue';
|
|
11
|
-
import SavedViewSidebar from '../../../components/SavedViewSidebar.vue';
|
|
12
|
-
import SaveViewDialog from '../../../components/SaveViewDialog.vue';
|
|
13
|
-
import { useKeyboardShortcuts } from '../../../composables/useKeyboardShortcuts';
|
|
14
|
-
import { usePluginExtensions } from '../../../composables/usePluginExtensions';
|
|
15
|
-
|
|
16
|
-
defineProps({
|
|
17
|
-
tickets: Object,
|
|
18
|
-
filters: Object,
|
|
19
|
-
departments: Array,
|
|
20
|
-
tags: Array,
|
|
21
|
-
agents: Array,
|
|
22
|
-
savedViews: { type: Array, default: () => [] },
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const page = usePage();
|
|
26
|
-
const selectedIds = ref([]);
|
|
27
|
-
const ticketListRef = ref(null);
|
|
28
|
-
const showShortcutHelp = ref(false);
|
|
29
|
-
const showSaveDialog = ref(false);
|
|
30
|
-
const ticketFiltersRef = ref(null);
|
|
31
|
-
|
|
32
|
-
useKeyboardShortcuts({
|
|
33
|
-
j: () => {
|
|
34
|
-
ticketListRef.value?.moveDown();
|
|
35
|
-
},
|
|
36
|
-
k: () => {
|
|
37
|
-
ticketListRef.value?.moveUp();
|
|
38
|
-
},
|
|
39
|
-
x: () => {
|
|
40
|
-
ticketListRef.value?.toggleSelectFocused();
|
|
41
|
-
},
|
|
42
|
-
Enter: () => {
|
|
43
|
-
ticketListRef.value?.openFocused();
|
|
44
|
-
},
|
|
45
|
-
'?': () => {
|
|
46
|
-
showShortcutHelp.value = true;
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const { getPageComponents } = usePluginExtensions();
|
|
51
|
-
|
|
52
|
-
function applyQuickFilter(filter) {
|
|
53
|
-
router.get(route('escalated.admin.tickets.index'), filter, { preserveState: true });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function onViewSaved() {
|
|
57
|
-
router.reload({ only: ['savedViews'] });
|
|
58
|
-
}
|
|
59
|
-
</script>
|
|
60
|
-
|
|
61
|
-
<template>
|
|
62
|
-
<EscalatedLayout title="All Tickets">
|
|
63
|
-
<div class="flex gap-6">
|
|
64
|
-
<!-- Saved Views Sidebar -->
|
|
65
|
-
<aside v-if="savedViews && savedViews.length" class="hidden w-52 shrink-0 lg:block">
|
|
66
|
-
<div class="sticky top-4">
|
|
67
|
-
<h3 class="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-gray-400">Views</h3>
|
|
68
|
-
<SavedViewSidebar
|
|
69
|
-
:saved-views="savedViews"
|
|
70
|
-
:current-filters="filters"
|
|
71
|
-
:route="route('escalated.admin.tickets.index')"
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
</aside>
|
|
75
|
-
|
|
76
|
-
<!-- Main content -->
|
|
77
|
-
<div class="min-w-0 flex-1">
|
|
78
|
-
<div class="mb-4">
|
|
79
|
-
<QuickFilters :current-user-id="page.props.auth?.user?.id" @filter="applyQuickFilter" />
|
|
80
|
-
</div>
|
|
81
|
-
<PluginSlot
|
|
82
|
-
slot="ticket.list.before_filters"
|
|
83
|
-
:components="getPageComponents('ticket.list', 'before_filters')"
|
|
84
|
-
/>
|
|
85
|
-
<div class="mb-6">
|
|
86
|
-
<TicketFilters
|
|
87
|
-
ref="ticketFiltersRef"
|
|
88
|
-
:filters="filters"
|
|
89
|
-
:route="route('escalated.admin.tickets.index')"
|
|
90
|
-
:departments="departments"
|
|
91
|
-
:tags="tags"
|
|
92
|
-
:agents="agents"
|
|
93
|
-
show-assignee
|
|
94
|
-
show-following
|
|
95
|
-
@save-view="showSaveDialog = true"
|
|
96
|
-
/>
|
|
97
|
-
</div>
|
|
98
|
-
<TicketList
|
|
99
|
-
ref="ticketListRef"
|
|
100
|
-
v-model:selected-ids="selectedIds"
|
|
101
|
-
:tickets="tickets"
|
|
102
|
-
route-prefix="escalated.admin.tickets"
|
|
103
|
-
:columns="['reference', 'subject', 'requester', 'status', 'priority', 'assignee', 'updated']"
|
|
104
|
-
selectable
|
|
105
|
-
navigable
|
|
106
|
-
/>
|
|
107
|
-
<BulkActionBar
|
|
108
|
-
:selected-ids="selectedIds"
|
|
109
|
-
:bulk-route="route('escalated.admin.tickets.bulk')"
|
|
110
|
-
:agents="agents"
|
|
111
|
-
:departments="departments"
|
|
112
|
-
@clear="selectedIds = []"
|
|
113
|
-
/>
|
|
114
|
-
<PluginSlot slot="ticket.list.footer" :components="getPageComponents('ticket.list', 'footer')" />
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<SaveViewDialog
|
|
119
|
-
:show="showSaveDialog"
|
|
120
|
-
:filters="ticketFiltersRef?.filterData || filters"
|
|
121
|
-
@close="showSaveDialog = false"
|
|
122
|
-
@saved="onViewSaved"
|
|
123
|
-
/>
|
|
124
|
-
<KeyboardShortcutHelp v-model:show="showShortcutHelp" context="list" />
|
|
125
|
-
</EscalatedLayout>
|
|
126
|
-
</template>
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import { usePage, router } from '@inertiajs/vue3';
|
|
4
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
5
|
+
import TicketList from '../../../components/TicketList.vue';
|
|
6
|
+
import TicketFilters from '../../../components/TicketFilters.vue';
|
|
7
|
+
import QuickFilters from '../../../components/QuickFilters.vue';
|
|
8
|
+
import BulkActionBar from '../../../components/BulkActionBar.vue';
|
|
9
|
+
import KeyboardShortcutHelp from '../../../components/KeyboardShortcutHelp.vue';
|
|
10
|
+
import PluginSlot from '../../../components/PluginSlot.vue';
|
|
11
|
+
import SavedViewSidebar from '../../../components/SavedViewSidebar.vue';
|
|
12
|
+
import SaveViewDialog from '../../../components/SaveViewDialog.vue';
|
|
13
|
+
import { useKeyboardShortcuts } from '../../../composables/useKeyboardShortcuts';
|
|
14
|
+
import { usePluginExtensions } from '../../../composables/usePluginExtensions';
|
|
15
|
+
|
|
16
|
+
defineProps({
|
|
17
|
+
tickets: Object,
|
|
18
|
+
filters: Object,
|
|
19
|
+
departments: Array,
|
|
20
|
+
tags: Array,
|
|
21
|
+
agents: Array,
|
|
22
|
+
savedViews: { type: Array, default: () => [] },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const page = usePage();
|
|
26
|
+
const selectedIds = ref([]);
|
|
27
|
+
const ticketListRef = ref(null);
|
|
28
|
+
const showShortcutHelp = ref(false);
|
|
29
|
+
const showSaveDialog = ref(false);
|
|
30
|
+
const ticketFiltersRef = ref(null);
|
|
31
|
+
|
|
32
|
+
useKeyboardShortcuts({
|
|
33
|
+
j: () => {
|
|
34
|
+
ticketListRef.value?.moveDown();
|
|
35
|
+
},
|
|
36
|
+
k: () => {
|
|
37
|
+
ticketListRef.value?.moveUp();
|
|
38
|
+
},
|
|
39
|
+
x: () => {
|
|
40
|
+
ticketListRef.value?.toggleSelectFocused();
|
|
41
|
+
},
|
|
42
|
+
Enter: () => {
|
|
43
|
+
ticketListRef.value?.openFocused();
|
|
44
|
+
},
|
|
45
|
+
'?': () => {
|
|
46
|
+
showShortcutHelp.value = true;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { getPageComponents } = usePluginExtensions();
|
|
51
|
+
|
|
52
|
+
function applyQuickFilter(filter) {
|
|
53
|
+
router.get(route('escalated.admin.tickets.index'), filter, { preserveState: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onViewSaved() {
|
|
57
|
+
router.reload({ only: ['savedViews'] });
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<EscalatedLayout title="All Tickets">
|
|
63
|
+
<div class="flex gap-6">
|
|
64
|
+
<!-- Saved Views Sidebar -->
|
|
65
|
+
<aside v-if="savedViews && savedViews.length" class="hidden w-52 shrink-0 lg:block">
|
|
66
|
+
<div class="sticky top-4">
|
|
67
|
+
<h3 class="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-gray-400">Views</h3>
|
|
68
|
+
<SavedViewSidebar
|
|
69
|
+
:saved-views="savedViews"
|
|
70
|
+
:current-filters="filters"
|
|
71
|
+
:route="route('escalated.admin.tickets.index')"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</aside>
|
|
75
|
+
|
|
76
|
+
<!-- Main content -->
|
|
77
|
+
<div class="min-w-0 flex-1">
|
|
78
|
+
<div class="mb-4">
|
|
79
|
+
<QuickFilters :current-user-id="page.props.auth?.user?.id" @filter="applyQuickFilter" />
|
|
80
|
+
</div>
|
|
81
|
+
<PluginSlot
|
|
82
|
+
slot="ticket.list.before_filters"
|
|
83
|
+
:components="getPageComponents('ticket.list', 'before_filters')"
|
|
84
|
+
/>
|
|
85
|
+
<div class="mb-6">
|
|
86
|
+
<TicketFilters
|
|
87
|
+
ref="ticketFiltersRef"
|
|
88
|
+
:filters="filters"
|
|
89
|
+
:route="route('escalated.admin.tickets.index')"
|
|
90
|
+
:departments="departments"
|
|
91
|
+
:tags="tags"
|
|
92
|
+
:agents="agents"
|
|
93
|
+
show-assignee
|
|
94
|
+
show-following
|
|
95
|
+
@save-view="showSaveDialog = true"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<TicketList
|
|
99
|
+
ref="ticketListRef"
|
|
100
|
+
v-model:selected-ids="selectedIds"
|
|
101
|
+
:tickets="tickets"
|
|
102
|
+
route-prefix="escalated.admin.tickets"
|
|
103
|
+
:columns="['reference', 'subject', 'requester', 'status', 'priority', 'assignee', 'updated']"
|
|
104
|
+
selectable
|
|
105
|
+
navigable
|
|
106
|
+
/>
|
|
107
|
+
<BulkActionBar
|
|
108
|
+
:selected-ids="selectedIds"
|
|
109
|
+
:bulk-route="route('escalated.admin.tickets.bulk')"
|
|
110
|
+
:agents="agents"
|
|
111
|
+
:departments="departments"
|
|
112
|
+
@clear="selectedIds = []"
|
|
113
|
+
/>
|
|
114
|
+
<PluginSlot slot="ticket.list.footer" :components="getPageComponents('ticket.list', 'footer')" />
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<SaveViewDialog
|
|
119
|
+
:show="showSaveDialog"
|
|
120
|
+
:filters="ticketFiltersRef?.filterData || filters"
|
|
121
|
+
@close="showSaveDialog = false"
|
|
122
|
+
@saved="onViewSaved"
|
|
123
|
+
/>
|
|
124
|
+
<KeyboardShortcutHelp v-model:show="showShortcutHelp" context="list" />
|
|
125
|
+
</EscalatedLayout>
|
|
126
|
+
</template>
|