@escalated-dev/escalated 0.8.0 → 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 -361
- package/package.json +85 -84
- 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 -604
- 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 +3 -0
- 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 +102 -102
- 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 -13
- 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 +30 -10
- 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 -145
- 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 +56 -56
- 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,182 +1,182 @@
|
|
|
1
|
-
import { ref, onUnmounted, readonly } from 'vue';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Composable for real-time WebSocket event listening via Laravel Echo.
|
|
5
|
-
*
|
|
6
|
-
* If `window.Echo` is available (provided by the host app), this composable
|
|
7
|
-
* subscribes to private channels and listens for broadcast events. When Echo
|
|
8
|
-
* is not present, it silently falls back - the caller can check `echoAvailable`
|
|
9
|
-
* and implement polling as needed.
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* const { echoAvailable, subscribeToTicket, subscribeToTickets, subscribeToAgent } = useRealtime();
|
|
13
|
-
*
|
|
14
|
-
* All subscriptions are automatically cleaned up on component unmount.
|
|
15
|
-
*/
|
|
16
|
-
export function useRealtime() {
|
|
17
|
-
const echoAvailable = ref(typeof window !== 'undefined' && !!window.Echo);
|
|
18
|
-
const subscriptions = [];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Subscribe to a private channel and listen for an event.
|
|
22
|
-
* Returns an unsubscribe function.
|
|
23
|
-
*/
|
|
24
|
-
function listen(channelName, eventName, callback) {
|
|
25
|
-
if (!echoAvailable.value) return () => {};
|
|
26
|
-
|
|
27
|
-
const channel = window.Echo.private(channelName);
|
|
28
|
-
channel.listen(eventName, callback);
|
|
29
|
-
|
|
30
|
-
const unsub = () => {
|
|
31
|
-
channel.stopListening(eventName, callback);
|
|
32
|
-
window.Echo.leave(channelName);
|
|
33
|
-
};
|
|
34
|
-
subscriptions.push(unsub);
|
|
35
|
-
return unsub;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Subscribe to a specific ticket's channel for real-time updates.
|
|
40
|
-
*
|
|
41
|
-
* @param {number|string} ticketId - The ticket ID
|
|
42
|
-
* @param {Object} handlers - Event handler callbacks
|
|
43
|
-
* @param {Function} [handlers.onReplyCreated] - Called when a new reply is added
|
|
44
|
-
* @param {Function} [handlers.onTicketUpdated] - Called when ticket fields are updated
|
|
45
|
-
* @param {Function} [handlers.onStatusChanged] - Called when ticket status changes
|
|
46
|
-
* @param {Function} [handlers.onTicketAssigned] - Called when ticket is assigned
|
|
47
|
-
* @param {Function} [handlers.onTicketEscalated] - Called when ticket is escalated
|
|
48
|
-
* @returns {Function} Unsubscribe function
|
|
49
|
-
*/
|
|
50
|
-
function subscribeToTicket(ticketId, handlers = {}) {
|
|
51
|
-
if (!echoAvailable.value) return () => {};
|
|
52
|
-
|
|
53
|
-
const channelName = `escalated.tickets.${ticketId}`;
|
|
54
|
-
const channel = window.Echo.private(channelName);
|
|
55
|
-
const listeners = [];
|
|
56
|
-
|
|
57
|
-
if (handlers.onReplyCreated) {
|
|
58
|
-
channel.listen('.reply.created', handlers.onReplyCreated);
|
|
59
|
-
listeners.push(['.reply.created', handlers.onReplyCreated]);
|
|
60
|
-
}
|
|
61
|
-
if (handlers.onTicketUpdated) {
|
|
62
|
-
channel.listen('.ticket.updated', handlers.onTicketUpdated);
|
|
63
|
-
listeners.push(['.ticket.updated', handlers.onTicketUpdated]);
|
|
64
|
-
}
|
|
65
|
-
if (handlers.onStatusChanged) {
|
|
66
|
-
channel.listen('.ticket.status_changed', handlers.onStatusChanged);
|
|
67
|
-
listeners.push(['.ticket.status_changed', handlers.onStatusChanged]);
|
|
68
|
-
}
|
|
69
|
-
if (handlers.onTicketAssigned) {
|
|
70
|
-
channel.listen('.ticket.assigned', handlers.onTicketAssigned);
|
|
71
|
-
listeners.push(['.ticket.assigned', handlers.onTicketAssigned]);
|
|
72
|
-
}
|
|
73
|
-
if (handlers.onTicketEscalated) {
|
|
74
|
-
channel.listen('.ticket.escalated', handlers.onTicketEscalated);
|
|
75
|
-
listeners.push(['.ticket.escalated', handlers.onTicketEscalated]);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const unsub = () => {
|
|
79
|
-
listeners.forEach(([event, cb]) => channel.stopListening(event, cb));
|
|
80
|
-
window.Echo.leave(channelName);
|
|
81
|
-
};
|
|
82
|
-
subscriptions.push(unsub);
|
|
83
|
-
return unsub;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Subscribe to the global tickets channel for new ticket notifications.
|
|
88
|
-
*
|
|
89
|
-
* @param {Object} handlers - Event handler callbacks
|
|
90
|
-
* @param {Function} [handlers.onTicketCreated] - Called when a new ticket is created
|
|
91
|
-
* @returns {Function} Unsubscribe function
|
|
92
|
-
*/
|
|
93
|
-
function subscribeToTickets(handlers = {}) {
|
|
94
|
-
if (!echoAvailable.value) return () => {};
|
|
95
|
-
|
|
96
|
-
const channelName = 'escalated.tickets';
|
|
97
|
-
const channel = window.Echo.private(channelName);
|
|
98
|
-
const listeners = [];
|
|
99
|
-
|
|
100
|
-
if (handlers.onTicketCreated) {
|
|
101
|
-
channel.listen('.ticket.created', handlers.onTicketCreated);
|
|
102
|
-
listeners.push(['.ticket.created', handlers.onTicketCreated]);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const unsub = () => {
|
|
106
|
-
listeners.forEach(([event, cb]) => channel.stopListening(event, cb));
|
|
107
|
-
window.Echo.leave(channelName);
|
|
108
|
-
};
|
|
109
|
-
subscriptions.push(unsub);
|
|
110
|
-
return unsub;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Subscribe to an agent's personal channel for assignment notifications.
|
|
115
|
-
*
|
|
116
|
-
* @param {number|string} agentId - The agent's user ID
|
|
117
|
-
* @param {Object} handlers - Event handler callbacks
|
|
118
|
-
* @param {Function} [handlers.onTicketAssigned] - Called when a ticket is assigned to this agent
|
|
119
|
-
* @returns {Function} Unsubscribe function
|
|
120
|
-
*/
|
|
121
|
-
function subscribeToAgent(agentId, handlers = {}) {
|
|
122
|
-
if (!echoAvailable.value) return () => {};
|
|
123
|
-
|
|
124
|
-
const channelName = `escalated.agents.${agentId}`;
|
|
125
|
-
const channel = window.Echo.private(channelName);
|
|
126
|
-
const listeners = [];
|
|
127
|
-
|
|
128
|
-
if (handlers.onTicketAssigned) {
|
|
129
|
-
channel.listen('.ticket.assigned', handlers.onTicketAssigned);
|
|
130
|
-
listeners.push(['.ticket.assigned', handlers.onTicketAssigned]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const unsub = () => {
|
|
134
|
-
listeners.forEach(([event, cb]) => channel.stopListening(event, cb));
|
|
135
|
-
window.Echo.leave(channelName);
|
|
136
|
-
};
|
|
137
|
-
subscriptions.push(unsub);
|
|
138
|
-
return unsub;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get a presence channel for a ticket (if Echo supports it).
|
|
143
|
-
* Returns the channel instance or null.
|
|
144
|
-
*/
|
|
145
|
-
function joinTicketPresence(ticketId, callbacks = {}) {
|
|
146
|
-
if (!echoAvailable.value) return null;
|
|
147
|
-
|
|
148
|
-
const channelName = `escalated.tickets.${ticketId}`;
|
|
149
|
-
const channel = window.Echo.join(channelName);
|
|
150
|
-
|
|
151
|
-
if (callbacks.here) channel.here(callbacks.here);
|
|
152
|
-
if (callbacks.joining) channel.joining(callbacks.joining);
|
|
153
|
-
if (callbacks.leaving) channel.leaving(callbacks.leaving);
|
|
154
|
-
|
|
155
|
-
const unsub = () => {
|
|
156
|
-
window.Echo.leave(`presence-${channelName}`);
|
|
157
|
-
};
|
|
158
|
-
subscriptions.push(unsub);
|
|
159
|
-
return { channel, unsubscribe: unsub };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Cleanup all subscriptions on component unmount
|
|
163
|
-
onUnmounted(() => {
|
|
164
|
-
subscriptions.forEach((unsub) => {
|
|
165
|
-
try {
|
|
166
|
-
unsub();
|
|
167
|
-
} catch {
|
|
168
|
-
/* ignore */
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
subscriptions.length = 0;
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
echoAvailable: readonly(echoAvailable),
|
|
176
|
-
listen,
|
|
177
|
-
subscribeToTicket,
|
|
178
|
-
subscribeToTickets,
|
|
179
|
-
subscribeToAgent,
|
|
180
|
-
joinTicketPresence,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
1
|
+
import { ref, onUnmounted, readonly } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Composable for real-time WebSocket event listening via Laravel Echo.
|
|
5
|
+
*
|
|
6
|
+
* If `window.Echo` is available (provided by the host app), this composable
|
|
7
|
+
* subscribes to private channels and listens for broadcast events. When Echo
|
|
8
|
+
* is not present, it silently falls back - the caller can check `echoAvailable`
|
|
9
|
+
* and implement polling as needed.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const { echoAvailable, subscribeToTicket, subscribeToTickets, subscribeToAgent } = useRealtime();
|
|
13
|
+
*
|
|
14
|
+
* All subscriptions are automatically cleaned up on component unmount.
|
|
15
|
+
*/
|
|
16
|
+
export function useRealtime() {
|
|
17
|
+
const echoAvailable = ref(typeof window !== 'undefined' && !!window.Echo);
|
|
18
|
+
const subscriptions = [];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to a private channel and listen for an event.
|
|
22
|
+
* Returns an unsubscribe function.
|
|
23
|
+
*/
|
|
24
|
+
function listen(channelName, eventName, callback) {
|
|
25
|
+
if (!echoAvailable.value) return () => {};
|
|
26
|
+
|
|
27
|
+
const channel = window.Echo.private(channelName);
|
|
28
|
+
channel.listen(eventName, callback);
|
|
29
|
+
|
|
30
|
+
const unsub = () => {
|
|
31
|
+
channel.stopListening(eventName, callback);
|
|
32
|
+
window.Echo.leave(channelName);
|
|
33
|
+
};
|
|
34
|
+
subscriptions.push(unsub);
|
|
35
|
+
return unsub;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to a specific ticket's channel for real-time updates.
|
|
40
|
+
*
|
|
41
|
+
* @param {number|string} ticketId - The ticket ID
|
|
42
|
+
* @param {Object} handlers - Event handler callbacks
|
|
43
|
+
* @param {Function} [handlers.onReplyCreated] - Called when a new reply is added
|
|
44
|
+
* @param {Function} [handlers.onTicketUpdated] - Called when ticket fields are updated
|
|
45
|
+
* @param {Function} [handlers.onStatusChanged] - Called when ticket status changes
|
|
46
|
+
* @param {Function} [handlers.onTicketAssigned] - Called when ticket is assigned
|
|
47
|
+
* @param {Function} [handlers.onTicketEscalated] - Called when ticket is escalated
|
|
48
|
+
* @returns {Function} Unsubscribe function
|
|
49
|
+
*/
|
|
50
|
+
function subscribeToTicket(ticketId, handlers = {}) {
|
|
51
|
+
if (!echoAvailable.value) return () => {};
|
|
52
|
+
|
|
53
|
+
const channelName = `escalated.tickets.${ticketId}`;
|
|
54
|
+
const channel = window.Echo.private(channelName);
|
|
55
|
+
const listeners = [];
|
|
56
|
+
|
|
57
|
+
if (handlers.onReplyCreated) {
|
|
58
|
+
channel.listen('.reply.created', handlers.onReplyCreated);
|
|
59
|
+
listeners.push(['.reply.created', handlers.onReplyCreated]);
|
|
60
|
+
}
|
|
61
|
+
if (handlers.onTicketUpdated) {
|
|
62
|
+
channel.listen('.ticket.updated', handlers.onTicketUpdated);
|
|
63
|
+
listeners.push(['.ticket.updated', handlers.onTicketUpdated]);
|
|
64
|
+
}
|
|
65
|
+
if (handlers.onStatusChanged) {
|
|
66
|
+
channel.listen('.ticket.status_changed', handlers.onStatusChanged);
|
|
67
|
+
listeners.push(['.ticket.status_changed', handlers.onStatusChanged]);
|
|
68
|
+
}
|
|
69
|
+
if (handlers.onTicketAssigned) {
|
|
70
|
+
channel.listen('.ticket.assigned', handlers.onTicketAssigned);
|
|
71
|
+
listeners.push(['.ticket.assigned', handlers.onTicketAssigned]);
|
|
72
|
+
}
|
|
73
|
+
if (handlers.onTicketEscalated) {
|
|
74
|
+
channel.listen('.ticket.escalated', handlers.onTicketEscalated);
|
|
75
|
+
listeners.push(['.ticket.escalated', handlers.onTicketEscalated]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const unsub = () => {
|
|
79
|
+
listeners.forEach(([event, cb]) => channel.stopListening(event, cb));
|
|
80
|
+
window.Echo.leave(channelName);
|
|
81
|
+
};
|
|
82
|
+
subscriptions.push(unsub);
|
|
83
|
+
return unsub;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Subscribe to the global tickets channel for new ticket notifications.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} handlers - Event handler callbacks
|
|
90
|
+
* @param {Function} [handlers.onTicketCreated] - Called when a new ticket is created
|
|
91
|
+
* @returns {Function} Unsubscribe function
|
|
92
|
+
*/
|
|
93
|
+
function subscribeToTickets(handlers = {}) {
|
|
94
|
+
if (!echoAvailable.value) return () => {};
|
|
95
|
+
|
|
96
|
+
const channelName = 'escalated.tickets';
|
|
97
|
+
const channel = window.Echo.private(channelName);
|
|
98
|
+
const listeners = [];
|
|
99
|
+
|
|
100
|
+
if (handlers.onTicketCreated) {
|
|
101
|
+
channel.listen('.ticket.created', handlers.onTicketCreated);
|
|
102
|
+
listeners.push(['.ticket.created', handlers.onTicketCreated]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const unsub = () => {
|
|
106
|
+
listeners.forEach(([event, cb]) => channel.stopListening(event, cb));
|
|
107
|
+
window.Echo.leave(channelName);
|
|
108
|
+
};
|
|
109
|
+
subscriptions.push(unsub);
|
|
110
|
+
return unsub;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Subscribe to an agent's personal channel for assignment notifications.
|
|
115
|
+
*
|
|
116
|
+
* @param {number|string} agentId - The agent's user ID
|
|
117
|
+
* @param {Object} handlers - Event handler callbacks
|
|
118
|
+
* @param {Function} [handlers.onTicketAssigned] - Called when a ticket is assigned to this agent
|
|
119
|
+
* @returns {Function} Unsubscribe function
|
|
120
|
+
*/
|
|
121
|
+
function subscribeToAgent(agentId, handlers = {}) {
|
|
122
|
+
if (!echoAvailable.value) return () => {};
|
|
123
|
+
|
|
124
|
+
const channelName = `escalated.agents.${agentId}`;
|
|
125
|
+
const channel = window.Echo.private(channelName);
|
|
126
|
+
const listeners = [];
|
|
127
|
+
|
|
128
|
+
if (handlers.onTicketAssigned) {
|
|
129
|
+
channel.listen('.ticket.assigned', handlers.onTicketAssigned);
|
|
130
|
+
listeners.push(['.ticket.assigned', handlers.onTicketAssigned]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const unsub = () => {
|
|
134
|
+
listeners.forEach(([event, cb]) => channel.stopListening(event, cb));
|
|
135
|
+
window.Echo.leave(channelName);
|
|
136
|
+
};
|
|
137
|
+
subscriptions.push(unsub);
|
|
138
|
+
return unsub;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get a presence channel for a ticket (if Echo supports it).
|
|
143
|
+
* Returns the channel instance or null.
|
|
144
|
+
*/
|
|
145
|
+
function joinTicketPresence(ticketId, callbacks = {}) {
|
|
146
|
+
if (!echoAvailable.value) return null;
|
|
147
|
+
|
|
148
|
+
const channelName = `escalated.tickets.${ticketId}`;
|
|
149
|
+
const channel = window.Echo.join(channelName);
|
|
150
|
+
|
|
151
|
+
if (callbacks.here) channel.here(callbacks.here);
|
|
152
|
+
if (callbacks.joining) channel.joining(callbacks.joining);
|
|
153
|
+
if (callbacks.leaving) channel.leaving(callbacks.leaving);
|
|
154
|
+
|
|
155
|
+
const unsub = () => {
|
|
156
|
+
window.Echo.leave(`presence-${channelName}`);
|
|
157
|
+
};
|
|
158
|
+
subscriptions.push(unsub);
|
|
159
|
+
return { channel, unsubscribe: unsub };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Cleanup all subscriptions on component unmount
|
|
163
|
+
onUnmounted(() => {
|
|
164
|
+
subscriptions.forEach((unsub) => {
|
|
165
|
+
try {
|
|
166
|
+
unsub();
|
|
167
|
+
} catch {
|
|
168
|
+
/* ignore */
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
subscriptions.length = 0;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
echoAvailable: readonly(echoAvailable),
|
|
176
|
+
listen,
|
|
177
|
+
subscribeToTicket,
|
|
178
|
+
subscribeToTickets,
|
|
179
|
+
subscribeToAgent,
|
|
180
|
+
joinTicketPresence,
|
|
181
|
+
};
|
|
182
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,84 +1,85 @@
|
|
|
1
|
-
// Plugin
|
|
2
|
-
export { EscalatedPlugin } from './plugin';
|
|
3
|
-
|
|
4
|
-
// Components
|
|
5
|
-
export { default as ActiveChatsPanel } from './components/ActiveChatsPanel.vue';
|
|
6
|
-
export { default as ActivityTimeline } from './components/ActivityTimeline.vue';
|
|
7
|
-
export { default as AgentLoadIndicator } from './components/AgentLoadIndicator.vue';
|
|
8
|
-
export { default as AgentPerformanceCard } from './components/AgentPerformanceCard.vue';
|
|
9
|
-
export { default as ArticleEditor } from './components/ArticleEditor.vue';
|
|
10
|
-
export { default as AssigneeSelect } from './components/AssigneeSelect.vue';
|
|
11
|
-
export { default as AttachmentList } from './components/AttachmentList.vue';
|
|
12
|
-
export { default as AuditLogEntry } from './components/AuditLogEntry.vue';
|
|
13
|
-
export { default as BulkActionBar } from './components/BulkActionBar.vue';
|
|
14
|
-
export { default as CategoryTree } from './components/CategoryTree.vue';
|
|
15
|
-
export { default as ChatActionBar } from './components/ChatActionBar.vue';
|
|
16
|
-
export { default as ChatBubble } from './components/ChatBubble.vue';
|
|
17
|
-
export { default as ChatComposer } from './components/ChatComposer.vue';
|
|
18
|
-
export { default as ChatQueue } from './components/ChatQueue.vue';
|
|
19
|
-
export { default as ChatThread } from './components/ChatThread.vue';
|
|
20
|
-
export { default as ChartWidget } from './components/ChartWidget.vue';
|
|
21
|
-
export { default as CollisionWarning } from './components/CollisionWarning.vue';
|
|
22
|
-
export { default as ConditionalFieldRules } from './components/ConditionalFieldRules.vue';
|
|
23
|
-
export { default as ContextPanel } from './components/ContextPanel.vue';
|
|
24
|
-
export { default as ContextPanelSection } from './components/ContextPanelSection.vue';
|
|
25
|
-
export { default as CustomFieldRenderer } from './components/CustomFieldRenderer.vue';
|
|
26
|
-
export { default as
|
|
27
|
-
export { default as
|
|
28
|
-
export { default as
|
|
29
|
-
export { default as
|
|
30
|
-
export { default as
|
|
31
|
-
export { default as
|
|
32
|
-
export { default as
|
|
33
|
-
export { default as
|
|
34
|
-
export { default as
|
|
35
|
-
export { default as
|
|
36
|
-
export { default as
|
|
37
|
-
export { default as
|
|
38
|
-
export { default as
|
|
39
|
-
export { default as
|
|
40
|
-
export { default as
|
|
41
|
-
export { default as
|
|
42
|
-
export { default as
|
|
43
|
-
export { default as
|
|
44
|
-
export { default as
|
|
45
|
-
export { default as
|
|
46
|
-
export { default as
|
|
47
|
-
export { default as
|
|
48
|
-
export { default as
|
|
49
|
-
export { default as
|
|
50
|
-
export { default as
|
|
51
|
-
export { default as
|
|
52
|
-
export { default as
|
|
53
|
-
export { default as
|
|
54
|
-
export { default as
|
|
55
|
-
export { default as
|
|
56
|
-
export { default as
|
|
57
|
-
export { default as
|
|
58
|
-
export { default as
|
|
59
|
-
export { default as
|
|
60
|
-
export { default as
|
|
61
|
-
export { default as
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
export {
|
|
66
|
-
export {
|
|
67
|
-
export {
|
|
68
|
-
export {
|
|
69
|
-
export {
|
|
70
|
-
export {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
export {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
export { default as
|
|
82
|
-
export { default as
|
|
83
|
-
export { default as
|
|
84
|
-
export { default as
|
|
1
|
+
// Plugin
|
|
2
|
+
export { EscalatedPlugin } from './plugin';
|
|
3
|
+
|
|
4
|
+
// Components
|
|
5
|
+
export { default as ActiveChatsPanel } from './components/ActiveChatsPanel.vue';
|
|
6
|
+
export { default as ActivityTimeline } from './components/ActivityTimeline.vue';
|
|
7
|
+
export { default as AgentLoadIndicator } from './components/AgentLoadIndicator.vue';
|
|
8
|
+
export { default as AgentPerformanceCard } from './components/AgentPerformanceCard.vue';
|
|
9
|
+
export { default as ArticleEditor } from './components/ArticleEditor.vue';
|
|
10
|
+
export { default as AssigneeSelect } from './components/AssigneeSelect.vue';
|
|
11
|
+
export { default as AttachmentList } from './components/AttachmentList.vue';
|
|
12
|
+
export { default as AuditLogEntry } from './components/AuditLogEntry.vue';
|
|
13
|
+
export { default as BulkActionBar } from './components/BulkActionBar.vue';
|
|
14
|
+
export { default as CategoryTree } from './components/CategoryTree.vue';
|
|
15
|
+
export { default as ChatActionBar } from './components/ChatActionBar.vue';
|
|
16
|
+
export { default as ChatBubble } from './components/ChatBubble.vue';
|
|
17
|
+
export { default as ChatComposer } from './components/ChatComposer.vue';
|
|
18
|
+
export { default as ChatQueue } from './components/ChatQueue.vue';
|
|
19
|
+
export { default as ChatThread } from './components/ChatThread.vue';
|
|
20
|
+
export { default as ChartWidget } from './components/ChartWidget.vue';
|
|
21
|
+
export { default as CollisionWarning } from './components/CollisionWarning.vue';
|
|
22
|
+
export { default as ConditionalFieldRules } from './components/ConditionalFieldRules.vue';
|
|
23
|
+
export { default as ContextPanel } from './components/ContextPanel.vue';
|
|
24
|
+
export { default as ContextPanelSection } from './components/ContextPanelSection.vue';
|
|
25
|
+
export { default as CustomFieldRenderer } from './components/CustomFieldRenderer.vue';
|
|
26
|
+
export { default as CustomTicketActions } from './components/CustomTicketActions.vue';
|
|
27
|
+
export { default as EscalatedLayout } from './components/EscalatedLayout.vue';
|
|
28
|
+
export { default as FileDropzone } from './components/FileDropzone.vue';
|
|
29
|
+
export { default as FollowButton } from './components/FollowButton.vue';
|
|
30
|
+
export { default as KeyboardShortcutHelp } from './components/KeyboardShortcutHelp.vue';
|
|
31
|
+
export { default as KnowledgePanel } from './components/KnowledgePanel.vue';
|
|
32
|
+
export { default as KpiCard } from './components/KpiCard.vue';
|
|
33
|
+
export { default as MacroDropdown } from './components/MacroDropdown.vue';
|
|
34
|
+
export { default as MentionDropdown } from './components/MentionDropdown.vue';
|
|
35
|
+
export { default as PermissionMatrix } from './components/PermissionMatrix.vue';
|
|
36
|
+
export { default as PinnedNotes } from './components/PinnedNotes.vue';
|
|
37
|
+
export { default as PluginSlot } from './components/PluginSlot.vue';
|
|
38
|
+
export { default as PresenceIndicator } from './components/PresenceIndicator.vue';
|
|
39
|
+
export { default as PriorityBadge } from './components/PriorityBadge.vue';
|
|
40
|
+
export { default as QuickFilters } from './components/QuickFilters.vue';
|
|
41
|
+
export { default as ReplyComposer } from './components/ReplyComposer.vue';
|
|
42
|
+
export { default as ReplyThread } from './components/ReplyThread.vue';
|
|
43
|
+
export { default as SavedViewSidebar } from './components/SavedViewSidebar.vue';
|
|
44
|
+
export { default as SaveViewDialog } from './components/SaveViewDialog.vue';
|
|
45
|
+
export { default as SatisfactionRating } from './components/SatisfactionRating.vue';
|
|
46
|
+
export { default as ScheduleEditor } from './components/ScheduleEditor.vue';
|
|
47
|
+
export { default as SideConversation } from './components/SideConversation.vue';
|
|
48
|
+
export { default as SideConversationList } from './components/SideConversationList.vue';
|
|
49
|
+
export { default as SkillTagManager } from './components/SkillTagManager.vue';
|
|
50
|
+
export { default as SlaComplianceChart } from './components/SlaComplianceChart.vue';
|
|
51
|
+
export { default as SlaTimer } from './components/SlaTimer.vue';
|
|
52
|
+
export { default as StatsCard } from './components/StatsCard.vue';
|
|
53
|
+
export { default as StatusBadge } from './components/StatusBadge.vue';
|
|
54
|
+
export { default as TagSelect } from './components/TagSelect.vue';
|
|
55
|
+
export { default as TicketFilters } from './components/TicketFilters.vue';
|
|
56
|
+
export { default as TicketLinkPanel } from './components/TicketLinkPanel.vue';
|
|
57
|
+
export { default as TicketList } from './components/TicketList.vue';
|
|
58
|
+
export { default as TicketMergeDialog } from './components/TicketMergeDialog.vue';
|
|
59
|
+
export { default as TicketSidebar } from './components/TicketSidebar.vue';
|
|
60
|
+
export { default as TicketTypeSelector } from './components/TicketTypeSelector.vue';
|
|
61
|
+
export { default as TotpInput } from './components/TotpInput.vue';
|
|
62
|
+
export { default as TwoFactorSetup } from './components/TwoFactorSetup.vue';
|
|
63
|
+
|
|
64
|
+
// Composables
|
|
65
|
+
export { useDebouncedSearch } from './composables/useDebouncedSearch';
|
|
66
|
+
export { useKeyboardShortcuts } from './composables/useKeyboardShortcuts';
|
|
67
|
+
export { useMentions } from './composables/useMentions';
|
|
68
|
+
export { usePluginExtensions } from './composables/usePluginExtensions';
|
|
69
|
+
export { usePluginHooks } from './composables/usePluginHooks';
|
|
70
|
+
export { useChat } from './composables/useChat';
|
|
71
|
+
export { useRealtime } from './composables/useRealtime';
|
|
72
|
+
|
|
73
|
+
// Utilities
|
|
74
|
+
export { TICKET_STATUSES, TICKET_PRIORITIES, TICKET_TYPES, TICKET_TYPE_COLORS } from './utils/constants';
|
|
75
|
+
export { timeAgo, slaClass } from './utils/formatting';
|
|
76
|
+
|
|
77
|
+
// Plugin authoring helper
|
|
78
|
+
export { defineEscalatedPlugin } from './defineEscalatedPlugin';
|
|
79
|
+
|
|
80
|
+
// Import pages
|
|
81
|
+
export { default as AdminImportIndex } from './pages/Admin/Import/Index.vue';
|
|
82
|
+
export { default as AdminImportConnect } from './pages/Admin/Import/Connect.vue';
|
|
83
|
+
export { default as AdminImportMapping } from './pages/Admin/Import/Mapping.vue';
|
|
84
|
+
export { default as AdminImportReview } from './pages/Admin/Import/Review.vue';
|
|
85
|
+
export { default as AdminImportProgress } from './pages/Admin/Import/Progress.vue';
|
package/src/locales/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# Local locale overrides
|
|
2
|
-
|
|
3
|
-
The JSON files in this directory (`en.json`, `de.json`, …) are **local overrides** that win over the central source published by [`@escalated-dev/locale`](https://github.com/escalated-dev/escalated-locale).
|
|
4
|
-
|
|
5
|
-
The composable in `src/composables/useI18n.js` resolves keys in this order:
|
|
6
|
-
|
|
7
|
-
1. Host-app `customMessages` for the current locale
|
|
8
|
-
2. Local override (these JSON files) for the current locale
|
|
9
|
-
3. Central source (`@escalated-dev/locale`) for the current locale
|
|
10
|
-
4. Same three layers, but English fallback
|
|
11
|
-
5. Raw key
|
|
12
|
-
|
|
13
|
-
After the central package is populated with the canonical strings, files here can be trimmed to contain only the keys this plugin needs to phrase differently from the central source. Keys not present locally fall through to the central source unchanged.
|
|
1
|
+
# Local locale overrides
|
|
2
|
+
|
|
3
|
+
The JSON files in this directory (`en.json`, `de.json`, …) are **local overrides** that win over the central source published by [`@escalated-dev/locale`](https://github.com/escalated-dev/escalated-locale).
|
|
4
|
+
|
|
5
|
+
The composable in `src/composables/useI18n.js` resolves keys in this order:
|
|
6
|
+
|
|
7
|
+
1. Host-app `customMessages` for the current locale
|
|
8
|
+
2. Local override (these JSON files) for the current locale
|
|
9
|
+
3. Central source (`@escalated-dev/locale`) for the current locale
|
|
10
|
+
4. Same three layers, but English fallback
|
|
11
|
+
5. Raw key
|
|
12
|
+
|
|
13
|
+
After the central package is populated with the canonical strings, files here can be trimmed to contain only the keys this plugin needs to phrase differently from the central source. Keys not present locally fall through to the central source unchanged.
|