@escalated-dev/escalated 0.5.0 → 0.7.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/README.md +102 -0
- package/package.json +37 -6
- package/src/components/ActivityTimeline.stories.js +55 -0
- package/src/components/ActivityTimeline.vue +83 -66
- package/src/components/AdminDashboard.stories.js +1095 -0
- package/src/components/AgentLoadIndicator.stories.js +37 -0
- package/src/components/AgentLoadIndicator.vue +49 -0
- package/src/components/AgentPerformanceCard.vue +44 -0
- package/src/components/ArticleEditor.vue +82 -0
- package/src/components/AssigneeSelect.vue +37 -23
- package/src/components/AttachmentList.vue +53 -37
- package/src/components/AuditLogEntry.vue +95 -0
- package/src/components/BulkActionBar.vue +99 -30
- package/src/components/CategoryTree.vue +89 -0
- package/src/components/ChartWidget.vue +46 -0
- package/src/components/CollisionWarning.stories.js +30 -0
- package/src/components/CollisionWarning.vue +68 -0
- package/src/components/ConditionalFieldRules.vue +135 -0
- package/src/components/ContextPanel.vue +66 -0
- package/src/components/ContextPanelSection.vue +65 -0
- package/src/components/CustomFieldRenderer.vue +159 -0
- package/src/components/EscalatedLayout.vue +400 -69
- package/src/components/FileDropzone.vue +57 -41
- package/src/components/FollowButton.stories.js +30 -0
- package/src/components/FollowButton.vue +53 -28
- package/src/components/KeyboardShortcutHelp.vue +129 -39
- package/src/components/KnowledgePanel.vue +122 -0
- package/src/components/KpiCard.stories.js +32 -0
- package/src/components/KpiCard.vue +88 -0
- package/src/components/MacroDropdown.vue +95 -35
- package/src/components/PermissionMatrix.vue +107 -0
- package/src/components/PinnedNotes.vue +75 -33
- package/src/components/PresenceIndicator.vue +93 -27
- package/src/components/PriorityBadge.stories.js +33 -0
- package/src/components/PriorityBadge.vue +46 -39
- package/src/components/QuickFilters.vue +65 -49
- package/src/components/ReplyComposer.vue +273 -159
- package/src/components/ReplyThread.vue +78 -23
- package/src/components/SatisfactionRating.vue +98 -46
- package/src/components/ScheduleEditor.vue +104 -0
- package/src/components/SideConversation.vue +165 -0
- package/src/components/SideConversationList.vue +147 -0
- package/src/components/SkillTagManager.vue +79 -0
- package/src/components/SlaComplianceChart.vue +53 -0
- package/src/components/SlaTimer.stories.js +35 -0
- package/src/components/SlaTimer.vue +62 -49
- package/src/components/StatsCard.stories.js +24 -0
- package/src/components/StatsCard.vue +58 -46
- package/src/components/StatusBadge.stories.js +53 -0
- package/src/components/StatusBadge.vue +78 -46
- package/src/components/TagSelect.vue +84 -53
- package/src/components/TicketFilters.vue +225 -69
- package/src/components/TicketLinkPanel.vue +193 -0
- package/src/components/TicketList.vue +594 -193
- package/src/components/TicketMergeDialog.vue +157 -0
- package/src/components/TicketSidebar.vue +167 -99
- package/src/components/TicketTypeSelector.vue +21 -0
- package/src/components/TotpInput.stories.js +9 -0
- package/src/components/TotpInput.vue +81 -0
- package/src/components/TwoFactorSetup.vue +113 -0
- package/src/composables/useDebouncedSearch.js +38 -0
- package/src/composables/useI18n.js +80 -0
- package/src/composables/usePluginExtensions.js +95 -3
- package/src/composables/usePluginHooks.js +65 -0
- package/src/defineEscalatedPlugin.js +34 -8
- package/src/index.js +72 -35
- package/src/locales/de.json +442 -0
- package/src/locales/en.json +467 -0
- package/src/locales/es.json +442 -0
- package/src/locales/fr.json +442 -0
- package/src/locales/index.js +6 -0
- package/src/pages/Admin/ApiTokens/Index.vue +231 -67
- package/src/pages/Admin/AuditLog/Index.vue +154 -0
- package/src/pages/Admin/Automations/Form.vue +184 -0
- package/src/pages/Admin/Automations/Index.vue +129 -0
- package/src/pages/Admin/BusinessHours/Form.vue +172 -0
- package/src/pages/Admin/BusinessHours/Index.vue +113 -0
- package/src/pages/Admin/CannedResponses/Index.vue +127 -70
- package/src/pages/Admin/Capacity/Index.vue +134 -0
- package/src/pages/Admin/CustomFields/Form.vue +195 -0
- package/src/pages/Admin/CustomFields/Index.vue +134 -0
- package/src/pages/Admin/CustomObjects/Form.vue +202 -0
- package/src/pages/Admin/CustomObjects/Index.vue +127 -0
- package/src/pages/Admin/CustomObjects/Records.vue +228 -0
- package/src/pages/Admin/Departments/Form.vue +75 -51
- package/src/pages/Admin/Departments/Index.vue +109 -57
- package/src/pages/Admin/EscalationRules/Form.vue +157 -96
- package/src/pages/Admin/EscalationRules/Index.vue +178 -57
- package/src/pages/Admin/Import/Connect.vue +96 -0
- package/src/pages/Admin/Import/Index.vue +102 -0
- package/src/pages/Admin/Import/Mapping.vue +110 -0
- package/src/pages/Admin/Import/Progress.vue +153 -0
- package/src/pages/Admin/Import/Review.vue +76 -0
- package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -0
- package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -0
- package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -0
- package/src/pages/Admin/Macros/Index.vue +250 -89
- package/src/pages/Admin/Plugins/Index.vue +205 -75
- package/src/pages/Admin/Reports/AgentMetrics.vue +115 -0
- package/src/pages/Admin/Reports/CsatReport.vue +127 -0
- package/src/pages/Admin/Reports/Dashboard.vue +211 -0
- package/src/pages/Admin/Reports/SlaReport.vue +166 -0
- package/src/pages/Admin/Reports.vue +139 -34
- package/src/pages/Admin/Roles/Form.vue +71 -0
- package/src/pages/Admin/Roles/Index.vue +122 -0
- package/src/pages/Admin/Settings/CsatSettings.vue +165 -0
- package/src/pages/Admin/Settings/DataRetention.vue +182 -0
- package/src/pages/Admin/Settings/EmailSettings.vue +243 -0
- package/src/pages/Admin/Settings/Sandbox.vue +135 -0
- package/src/pages/Admin/Settings/SsoSettings.vue +218 -0
- package/src/pages/Admin/Settings/TwoFactor.vue +110 -0
- package/src/pages/Admin/Settings.vue +468 -260
- package/src/pages/Admin/Skills/Form.vue +53 -0
- package/src/pages/Admin/Skills/Index.vue +94 -0
- package/src/pages/Admin/SlaPolicies/Form.vue +118 -75
- package/src/pages/Admin/SlaPolicies/Index.vue +120 -59
- package/src/pages/Admin/Statuses/Form.vue +114 -0
- package/src/pages/Admin/Statuses/Index.vue +153 -0
- package/src/pages/Admin/Tags/Index.vue +191 -105
- package/src/pages/Admin/Tickets/Index.vue +91 -39
- package/src/pages/Admin/Tickets/Show.vue +101 -37
- package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -0
- package/src/pages/Admin/Webhooks/Form.vue +110 -0
- package/src/pages/Admin/Webhooks/Index.vue +136 -0
- package/src/pages/Agent/Dashboard.vue +311 -186
- package/src/pages/Agent/TicketIndex.vue +88 -38
- package/src/pages/Agent/TicketShow.vue +258 -51
- package/src/pages/Customer/Create.vue +36 -9
- package/src/pages/Customer/Index.vue +31 -24
- package/src/pages/Customer/KnowledgeBase/Article.vue +127 -0
- package/src/pages/Customer/KnowledgeBase/Index.vue +151 -0
- package/src/plugin.js +86 -1
- package/src/utils/constants.js +30 -0
- package/src/utils/formatting.js +35 -0
- package/src/utils/sanitizeHtml.js +64 -0
- package/src/pages/Admin/Plugins.vue +0 -129
|
@@ -4,9 +4,16 @@ import { ref, computed, inject, onMounted, onUnmounted } from 'vue';
|
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
ticketReference: { type: String, required: true },
|
|
6
6
|
routePrefix: { type: String, required: true },
|
|
7
|
+
pollInterval: { type: Number, default: 30000 },
|
|
8
|
+
showLabel: { type: Boolean, default: false },
|
|
7
9
|
});
|
|
8
10
|
|
|
9
|
-
const
|
|
11
|
+
const effectiveInterval = computed(() => Math.max(props.pollInterval, 5000));
|
|
12
|
+
|
|
13
|
+
const escDark = inject(
|
|
14
|
+
'esc-dark',
|
|
15
|
+
computed(() => false),
|
|
16
|
+
);
|
|
10
17
|
const viewers = ref([]);
|
|
11
18
|
const hovering = ref(false);
|
|
12
19
|
let intervalId = null;
|
|
@@ -16,7 +23,7 @@ async function fetchPresence() {
|
|
|
16
23
|
const response = await fetch(route(`${props.routePrefix}.tickets.presence`, props.ticketReference), {
|
|
17
24
|
method: 'POST',
|
|
18
25
|
headers: {
|
|
19
|
-
|
|
26
|
+
Accept: 'application/json',
|
|
20
27
|
'Content-Type': 'application/json',
|
|
21
28
|
'X-Requested-With': 'XMLHttpRequest',
|
|
22
29
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
|
|
@@ -33,7 +40,7 @@ async function fetchPresence() {
|
|
|
33
40
|
|
|
34
41
|
const tooltipText = computed(() => {
|
|
35
42
|
if (!viewers.value.length) return '';
|
|
36
|
-
const names = viewers.value.map(v => v.name);
|
|
43
|
+
const names = viewers.value.map((v) => v.name);
|
|
37
44
|
return 'Also viewing: ' + names.join(', ');
|
|
38
45
|
});
|
|
39
46
|
|
|
@@ -46,8 +53,14 @@ function getInitial(viewer) {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
const colors = [
|
|
49
|
-
'bg-cyan-500',
|
|
50
|
-
'bg-
|
|
56
|
+
'bg-cyan-500',
|
|
57
|
+
'bg-violet-500',
|
|
58
|
+
'bg-amber-500',
|
|
59
|
+
'bg-emerald-500',
|
|
60
|
+
'bg-rose-500',
|
|
61
|
+
'bg-blue-500',
|
|
62
|
+
'bg-pink-500',
|
|
63
|
+
'bg-teal-500',
|
|
51
64
|
];
|
|
52
65
|
|
|
53
66
|
function getColor(index) {
|
|
@@ -56,7 +69,7 @@ function getColor(index) {
|
|
|
56
69
|
|
|
57
70
|
onMounted(() => {
|
|
58
71
|
fetchPresence();
|
|
59
|
-
intervalId = setInterval(fetchPresence,
|
|
72
|
+
intervalId = setInterval(fetchPresence, effectiveInterval.value);
|
|
60
73
|
});
|
|
61
74
|
|
|
62
75
|
onUnmounted(() => {
|
|
@@ -68,35 +81,88 @@ onUnmounted(() => {
|
|
|
68
81
|
</script>
|
|
69
82
|
|
|
70
83
|
<template>
|
|
71
|
-
<div
|
|
84
|
+
<div
|
|
85
|
+
v-if="viewers.length > 0"
|
|
86
|
+
class="relative flex items-center"
|
|
87
|
+
@mouseenter="hovering = true"
|
|
88
|
+
@mouseleave="hovering = false"
|
|
89
|
+
>
|
|
72
90
|
<!-- Avatar stack -->
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
91
|
+
<TransitionGroup
|
|
92
|
+
tag="div"
|
|
93
|
+
class="flex -space-x-2"
|
|
94
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
95
|
+
enter-from-class="opacity-0 scale-75 -ml-7"
|
|
96
|
+
enter-to-class="opacity-100 scale-100 ml-0"
|
|
97
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
98
|
+
leave-from-class="opacity-100 scale-100"
|
|
99
|
+
leave-to-class="opacity-0 scale-75 -ml-7"
|
|
100
|
+
>
|
|
101
|
+
<div
|
|
102
|
+
v-for="(viewer, index) in visibleViewers"
|
|
103
|
+
:key="viewer.id || viewer.name"
|
|
104
|
+
:aria-label="viewer.name"
|
|
105
|
+
:class="[
|
|
106
|
+
'relative flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold text-white ring-2',
|
|
107
|
+
getColor(index),
|
|
108
|
+
escDark ? 'ring-neutral-900' : 'ring-white',
|
|
109
|
+
]"
|
|
110
|
+
>
|
|
78
111
|
{{ getInitial(viewer) }}
|
|
112
|
+
<!-- Pulsing live indicator on first avatar -->
|
|
113
|
+
<span v-if="index === 0" class="absolute -right-0.5 -top-0.5 flex h-2.5 w-2.5">
|
|
114
|
+
<span
|
|
115
|
+
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75"
|
|
116
|
+
></span>
|
|
117
|
+
<span class="relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500"></span>
|
|
118
|
+
</span>
|
|
79
119
|
</div>
|
|
80
|
-
<div
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
<div
|
|
121
|
+
v-if="overflowCount > 0"
|
|
122
|
+
:key="'overflow'"
|
|
123
|
+
:class="[
|
|
124
|
+
'flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold ring-2',
|
|
125
|
+
escDark
|
|
126
|
+
? 'bg-white/[0.08] text-neutral-300 ring-neutral-900'
|
|
127
|
+
: 'bg-gray-200 text-gray-600 ring-white',
|
|
128
|
+
]"
|
|
129
|
+
>
|
|
85
130
|
+{{ overflowCount }}
|
|
86
131
|
</div>
|
|
87
|
-
</
|
|
132
|
+
</TransitionGroup>
|
|
133
|
+
|
|
134
|
+
<!-- Viewer count label -->
|
|
135
|
+
<span
|
|
136
|
+
v-if="showLabel"
|
|
137
|
+
aria-live="polite"
|
|
138
|
+
:class="['ml-2 text-xs font-medium', escDark ? 'text-neutral-400' : 'text-gray-500']"
|
|
139
|
+
>
|
|
140
|
+
{{ viewers.length }} viewing
|
|
141
|
+
</span>
|
|
88
142
|
|
|
89
143
|
<!-- Tooltip -->
|
|
90
|
-
<Transition
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
144
|
+
<Transition
|
|
145
|
+
enter-active-class="transition duration-100 ease-out"
|
|
146
|
+
enter-from-class="opacity-0 translate-y-1"
|
|
147
|
+
enter-to-class="opacity-100 translate-y-0"
|
|
148
|
+
leave-active-class="transition duration-75 ease-in"
|
|
149
|
+
leave-from-class="opacity-100 translate-y-0"
|
|
150
|
+
leave-to-class="opacity-0 translate-y-1"
|
|
151
|
+
>
|
|
152
|
+
<div
|
|
153
|
+
v-if="hovering"
|
|
154
|
+
:class="[
|
|
155
|
+
'absolute left-1/2 top-full z-50 mt-2 -translate-x-1/2 whitespace-nowrap rounded-lg px-3 py-1.5 text-xs font-medium shadow-lg',
|
|
156
|
+
escDark ? 'border border-white/[0.06] bg-neutral-800 text-neutral-200' : 'bg-gray-900 text-white',
|
|
157
|
+
]"
|
|
158
|
+
>
|
|
97
159
|
{{ tooltipText }}
|
|
98
|
-
<div
|
|
99
|
-
|
|
160
|
+
<div
|
|
161
|
+
:class="[
|
|
162
|
+
'absolute -top-1 left-1/2 h-2 w-2 -translate-x-1/2 rotate-45',
|
|
163
|
+
escDark ? 'bg-neutral-800' : 'bg-gray-900',
|
|
164
|
+
]"
|
|
165
|
+
></div>
|
|
100
166
|
</div>
|
|
101
167
|
</Transition>
|
|
102
168
|
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import PriorityBadge from './PriorityBadge.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/PriorityBadge',
|
|
5
|
+
component: PriorityBadge,
|
|
6
|
+
argTypes: {
|
|
7
|
+
priority: {
|
|
8
|
+
control: 'select',
|
|
9
|
+
options: ['low', 'medium', 'high', 'urgent', 'critical'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const Low = { args: { priority: 'low' } };
|
|
15
|
+
export const Medium = { args: { priority: 'medium' } };
|
|
16
|
+
export const High = { args: { priority: 'high' } };
|
|
17
|
+
export const Urgent = { args: { priority: 'urgent' } };
|
|
18
|
+
export const Critical = { args: { priority: 'critical' } };
|
|
19
|
+
|
|
20
|
+
export const AllPriorities = {
|
|
21
|
+
render: () => ({
|
|
22
|
+
components: { PriorityBadge },
|
|
23
|
+
template: `
|
|
24
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
25
|
+
<PriorityBadge priority="low" />
|
|
26
|
+
<PriorityBadge priority="medium" />
|
|
27
|
+
<PriorityBadge priority="high" />
|
|
28
|
+
<PriorityBadge priority="urgent" />
|
|
29
|
+
<PriorityBadge priority="critical" />
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
@@ -1,39 +1,46 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { inject, computed } from 'vue';
|
|
3
|
-
|
|
4
|
-
const props = defineProps({
|
|
5
|
-
priority: { type: String, required: true },
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const escDark = inject(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject, computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
priority: { type: String, required: true },
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const escDark = inject(
|
|
9
|
+
'esc-dark',
|
|
10
|
+
computed(() => false),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const darkConfig = {
|
|
14
|
+
low: { label: 'Low', color: 'bg-gray-500/10 text-gray-400 ring-1 ring-gray-500/20' },
|
|
15
|
+
medium: { label: 'Medium', color: 'bg-cyan-500/10 text-white ring-1 ring-cyan-500/20' },
|
|
16
|
+
high: { label: 'High', color: 'bg-amber-500/10 text-amber-400 ring-1 ring-amber-500/20' },
|
|
17
|
+
urgent: { label: 'Urgent', color: 'bg-orange-500/10 text-orange-400 ring-1 ring-orange-500/20' },
|
|
18
|
+
critical: { label: 'Critical', color: 'bg-rose-500/10 text-rose-300 ring-1 ring-rose-500/20' },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const lightConfig = {
|
|
22
|
+
low: { label: 'Low', color: 'bg-gray-100 text-gray-800' },
|
|
23
|
+
medium: { label: 'Medium', color: 'bg-blue-100 text-blue-800' },
|
|
24
|
+
high: { label: 'High', color: 'bg-yellow-100 text-yellow-800' },
|
|
25
|
+
urgent: { label: 'Urgent', color: 'bg-orange-100 text-orange-800' },
|
|
26
|
+
critical: { label: 'Critical', color: 'bg-red-100 text-red-800' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const config = computed(() => {
|
|
30
|
+
const map = escDark.value ? darkConfig : lightConfig;
|
|
31
|
+
const fallback = escDark.value
|
|
32
|
+
? { label: props.priority, color: 'bg-gray-500/10 text-gray-400 ring-1 ring-gray-500/20' }
|
|
33
|
+
: { label: props.priority, color: 'bg-gray-100 text-gray-800' };
|
|
34
|
+
return map[props.priority] || fallback;
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<span
|
|
40
|
+
role="status"
|
|
41
|
+
:aria-label="'Priority: ' + config.label"
|
|
42
|
+
:class="['inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium', config.color]"
|
|
43
|
+
>
|
|
44
|
+
{{ config.label }}
|
|
45
|
+
</span>
|
|
46
|
+
</template>
|
|
@@ -1,49 +1,65 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { ref, computed, inject } from 'vue';
|
|
3
|
-
|
|
4
|
-
const props = defineProps({
|
|
5
|
-
currentUserId: { type: [Number, String], default: null },
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const emit = defineEmits(['filter']);
|
|
9
|
-
const escDark = inject(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{ key:
|
|
18
|
-
{ key: '
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, inject } from 'vue';
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
currentUserId: { type: [Number, String], default: null },
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const emit = defineEmits(['filter']);
|
|
9
|
+
const escDark = inject(
|
|
10
|
+
'esc-dark',
|
|
11
|
+
computed(() => false),
|
|
12
|
+
);
|
|
13
|
+
const activeTab = ref(null);
|
|
14
|
+
|
|
15
|
+
const tabs = computed(() => {
|
|
16
|
+
const list = [
|
|
17
|
+
{ key: null, label: 'All Tickets', filter: {} },
|
|
18
|
+
{ key: 'assigned_to_me', label: 'Assigned to Me', filter: { assigned_to: props.currentUserId } },
|
|
19
|
+
{ key: 'unassigned', label: 'Unassigned', filter: { unassigned: true } },
|
|
20
|
+
{ key: 'urgent', label: 'Urgent', filter: { priority: 'urgent' } },
|
|
21
|
+
{ key: 'sla_breaching', label: 'SLA Breaching', filter: { sla_breached: true } },
|
|
22
|
+
{ key: 'following', label: 'Following', filter: { following: true } },
|
|
23
|
+
];
|
|
24
|
+
if (props.currentUserId) return list;
|
|
25
|
+
return list.filter((t) => t.key !== 'assigned_to_me');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function selectTab(tab) {
|
|
29
|
+
activeTab.value = tab.key;
|
|
30
|
+
emit('filter', tab.filter);
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div
|
|
36
|
+
:class="['flex items-center gap-0 border-b', escDark ? 'border-white/[0.08]' : 'border-gray-200']"
|
|
37
|
+
role="tablist"
|
|
38
|
+
aria-label="Ticket view tabs"
|
|
39
|
+
>
|
|
40
|
+
<button
|
|
41
|
+
v-for="tab in tabs"
|
|
42
|
+
:key="tab.key"
|
|
43
|
+
role="tab"
|
|
44
|
+
:aria-selected="activeTab === tab.key"
|
|
45
|
+
:class="[
|
|
46
|
+
'relative px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap',
|
|
47
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-0',
|
|
48
|
+
activeTab === tab.key
|
|
49
|
+
? escDark
|
|
50
|
+
? 'text-cyan-400 focus-visible:ring-cyan-500/40'
|
|
51
|
+
: 'text-blue-600 focus-visible:ring-blue-500/40'
|
|
52
|
+
: escDark
|
|
53
|
+
? 'text-neutral-500 hover:text-neutral-300 focus-visible:ring-cyan-500/40'
|
|
54
|
+
: 'text-gray-500 hover:text-gray-700 focus-visible:ring-blue-500/40',
|
|
55
|
+
]"
|
|
56
|
+
@click="selectTab(tab)"
|
|
57
|
+
>
|
|
58
|
+
{{ tab.label }}
|
|
59
|
+
<span
|
|
60
|
+
v-if="activeTab === tab.key"
|
|
61
|
+
:class="['absolute bottom-0 left-0 right-0 h-0.5 rounded-t', escDark ? 'bg-cyan-400' : 'bg-blue-600']"
|
|
62
|
+
></span>
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|