@escalated-dev/escalated 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +31 -0
- package/package.json +12 -6
- package/src/components/ActiveChatsPanel.vue +246 -0
- package/src/components/ActivityTimeline.stories.js +55 -55
- package/src/components/ActivityTimeline.vue +83 -83
- package/src/components/AdminDashboard.stories.js +1423 -1095
- package/src/components/AgentLoadIndicator.stories.js +37 -37
- package/src/components/AgentLoadIndicator.vue +49 -49
- package/src/components/AgentPerformanceCard.vue +44 -44
- package/src/components/ArticleEditor.vue +82 -82
- package/src/components/AssigneeSelect.vue +37 -37
- package/src/components/AttachmentList.vue +13 -1
- package/src/components/AuditLogEntry.vue +95 -95
- package/src/components/BulkActionBar.vue +166 -166
- package/src/components/CategoryTree.vue +89 -89
- package/src/components/ChartWidget.vue +46 -46
- package/src/components/ChatActionBar.vue +226 -0
- package/src/components/ChatBubble.vue +162 -0
- package/src/components/ChatComposer.vue +308 -0
- package/src/components/ChatQueue.vue +222 -0
- package/src/components/ChatThread.vue +197 -0
- package/src/components/CollisionWarning.stories.js +30 -30
- package/src/components/CollisionWarning.vue +68 -68
- package/src/components/ConditionalFieldRules.vue +135 -135
- package/src/components/ContextPanel.vue +66 -66
- package/src/components/ContextPanelSection.vue +65 -65
- package/src/components/CustomFieldRenderer.vue +159 -159
- package/src/components/EscalatedLayout.vue +29 -6
- package/src/components/FileDropzone.vue +57 -57
- package/src/components/FollowButton.stories.js +30 -30
- package/src/components/FollowButton.vue +88 -88
- package/src/components/KeyboardShortcutHelp.vue +213 -213
- package/src/components/KnowledgePanel.vue +122 -122
- package/src/components/KpiCard.stories.js +32 -32
- package/src/components/KpiCard.vue +88 -88
- package/src/components/MacroDropdown.vue +150 -150
- package/src/components/MentionDropdown.vue +154 -0
- package/src/components/MetricComparison.vue +89 -0
- package/src/components/PercentileChart.vue +67 -0
- package/src/components/PeriodSelector.vue +98 -0
- package/src/components/PermissionMatrix.vue +107 -107
- package/src/components/PinnedNotes.vue +129 -129
- package/src/components/PluginSlot.vue +68 -62
- package/src/components/PresenceIndicator.vue +46 -2
- package/src/components/PriorityBadge.stories.js +33 -33
- package/src/components/PriorityBadge.vue +46 -46
- package/src/components/ReplyComposer.vue +81 -18
- package/src/components/ReplyThread.vue +40 -2
- package/src/components/ReportChart.vue +395 -0
- package/src/components/ReportExportButton.vue +108 -0
- package/src/components/SatisfactionRating.vue +180 -180
- package/src/components/SaveViewDialog.vue +172 -0
- package/src/components/SavedViewSidebar.vue +208 -0
- package/src/components/ScheduleEditor.vue +104 -104
- package/src/components/SideConversation.vue +165 -165
- package/src/components/SideConversationList.vue +147 -147
- package/src/components/SkillTagManager.vue +79 -79
- package/src/components/SlaComplianceChart.vue +46 -53
- package/src/components/SlaTimer.stories.js +35 -35
- package/src/components/SlaTimer.vue +62 -62
- package/src/components/SnoozeButton.vue +247 -0
- package/src/components/StatsCard.stories.js +24 -24
- package/src/components/StatsCard.vue +58 -58
- package/src/components/StatusBadge.stories.js +53 -53
- package/src/components/StatusBadge.vue +78 -78
- package/src/components/TagSelect.vue +84 -84
- package/src/components/TicketFilters.vue +270 -225
- package/src/components/TicketLinkPanel.vue +193 -193
- package/src/components/TicketList.vue +655 -594
- package/src/components/TicketMergeDialog.vue +157 -157
- package/src/components/TicketSplitDialog.vue +98 -0
- package/src/components/TotpInput.stories.js +9 -9
- package/src/components/TotpInput.vue +81 -81
- package/src/components/TwoFactorSetup.vue +113 -113
- package/src/components/VariableTokenInput.vue +111 -0
- package/src/components/WorkflowActionConfig.vue +287 -0
- package/src/components/WorkflowActionList.vue +350 -0
- package/src/components/WorkflowConditionBuilder.vue +265 -0
- package/src/components/WorkflowTriggerSelector.vue +125 -0
- package/src/composables/useChat.js +187 -0
- package/src/composables/useI18n.js +80 -80
- package/src/composables/useKeyboardShortcuts.js +46 -46
- package/src/composables/useMentions.js +134 -0
- package/src/composables/usePluginExtensions.js +159 -159
- package/src/composables/usePluginHooks.js +65 -65
- package/src/composables/useRealtime.js +182 -0
- package/src/defineEscalatedPlugin.js +76 -76
- package/src/index.js +84 -72
- package/src/locales/ar.json +727 -0
- package/src/locales/de.json +678 -430
- package/src/locales/en.json +717 -455
- package/src/locales/es.json +679 -430
- package/src/locales/fr.json +680 -431
- package/src/locales/index.js +12 -2
- package/src/locales/it.json +727 -0
- package/src/locales/ja.json +727 -0
- package/src/locales/ko.json +727 -0
- package/src/locales/nl.json +727 -0
- package/src/locales/pl.json +727 -0
- package/src/locales/pt-BR.json +727 -0
- package/src/locales/ru.json +727 -0
- package/src/locales/tr.json +727 -0
- package/src/locales/zh-CN.json +727 -0
- package/src/pages/Admin/ApiTokens/Index.vue +381 -381
- package/src/pages/Admin/AuditLog/Index.vue +154 -154
- package/src/pages/Admin/BusinessHours/Form.vue +172 -172
- package/src/pages/Admin/BusinessHours/Index.vue +113 -113
- package/src/pages/Admin/CannedResponses/Index.vue +127 -127
- package/src/pages/Admin/Capacity/Index.vue +134 -134
- package/src/pages/Admin/CustomFields/Form.vue +195 -195
- package/src/pages/Admin/CustomFields/Index.vue +134 -134
- package/src/pages/Admin/CustomObjects/Form.vue +202 -202
- package/src/pages/Admin/CustomObjects/Index.vue +127 -127
- package/src/pages/Admin/CustomObjects/Records.vue +228 -228
- package/src/pages/Admin/Departments/Form.vue +75 -75
- package/src/pages/Admin/Departments/Index.vue +110 -109
- package/src/pages/Admin/EscalationRules/Form.vue +157 -157
- package/src/pages/Admin/EscalationRules/Index.vue +178 -178
- package/src/pages/Admin/Import/Connect.vue +0 -1
- package/src/pages/Admin/Import/Index.vue +1 -1
- package/src/pages/Admin/Import/Progress.vue +1 -1
- package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -142
- package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -195
- package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -193
- package/src/pages/Admin/Macros/Index.vue +448 -448
- package/src/pages/Admin/Plugins/Index.vue +327 -327
- package/src/pages/Admin/Reports/AgentDetail.vue +132 -0
- package/src/pages/Admin/Reports/AgentMetrics.vue +115 -115
- package/src/pages/Admin/Reports/AgentRanking.vue +246 -0
- package/src/pages/Admin/Reports/Cohorts.vue +180 -0
- package/src/pages/Admin/Reports/Comparison.vue +141 -0
- package/src/pages/Admin/Reports/CsatReport.vue +127 -127
- package/src/pages/Admin/Reports/Dashboard.vue +36 -21
- package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -0
- package/src/pages/Admin/Reports/ResponseTimes.vue +164 -0
- package/src/pages/Admin/Reports/SlaReport.vue +162 -166
- package/src/pages/Admin/Reports/SlaTrends.vue +183 -0
- package/src/pages/Admin/Reports.vue +206 -206
- package/src/pages/Admin/Roles/Form.vue +71 -71
- package/src/pages/Admin/Roles/Index.vue +122 -122
- package/src/pages/Admin/Settings/CsatSettings.vue +165 -165
- package/src/pages/Admin/Settings/DataRetention.vue +182 -182
- package/src/pages/Admin/Settings/EmailSettings.vue +243 -243
- package/src/pages/Admin/Settings/Sandbox.vue +135 -135
- package/src/pages/Admin/Settings/SsoSettings.vue +218 -218
- package/src/pages/Admin/Settings/TwoFactor.vue +110 -110
- package/src/pages/Admin/Settings.vue +1008 -468
- package/src/pages/Admin/Skills/Form.vue +53 -53
- package/src/pages/Admin/Skills/Index.vue +94 -94
- package/src/pages/Admin/SlaPolicies/Form.vue +118 -118
- package/src/pages/Admin/SlaPolicies/Index.vue +121 -120
- package/src/pages/Admin/Statuses/Form.vue +114 -114
- package/src/pages/Admin/Statuses/Index.vue +153 -153
- package/src/pages/Admin/Tags/Index.vue +191 -191
- package/src/pages/Admin/Tickets/Index.vue +126 -91
- package/src/pages/Admin/Tickets/Show.vue +29 -0
- package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -120
- package/src/pages/Admin/Webhooks/Form.vue +110 -110
- package/src/pages/Admin/Webhooks/Index.vue +136 -136
- package/src/pages/Admin/Workflows/Builder.vue +207 -0
- package/src/pages/Admin/Workflows/Index.vue +235 -0
- package/src/pages/Admin/Workflows/Logs.vue +286 -0
- package/src/pages/Agent/TicketIndex.vue +112 -88
- package/src/pages/Agent/TicketShow.vue +192 -60
- package/src/pages/Customer/Create.vue +1 -1
- package/src/pages/Customer/Index.vue +31 -31
- package/src/pages/Customer/KnowledgeBase/Article.vue +4 -2
- package/src/pages/Customer/KnowledgeBase/Index.vue +151 -151
- package/src/pages/Customer/Show.vue +69 -63
- package/src/pages/Guest/Create.vue +40 -15
- package/src/pages/Guest/Show.vue +124 -93
- package/src/plugin.js +25 -0
- package/src/utils/mentionHighlight.js +24 -0
- package/src/utils/sanitizeHtml.js +78 -64
- package/src/widget/EscalatedWidget.vue +804 -0
- package/src/widget/index.js +254 -0
- package/src/pages/Admin/Automations/Form.vue +0 -184
- package/src/pages/Admin/Automations/Index.vue +0 -129
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
-
import { useForm, router } from '@inertiajs/vue3';
|
|
4
|
-
import { ref } from 'vue';
|
|
5
|
-
|
|
6
|
-
defineProps({ responses: Array });
|
|
7
|
-
|
|
8
|
-
const showForm = ref(false);
|
|
9
|
-
const form = useForm({ title: '', body: '', category: '', is_shared: true });
|
|
10
|
-
|
|
11
|
-
function create() {
|
|
12
|
-
form.post(route('escalated.admin.canned-responses.store'), {
|
|
13
|
-
onSuccess: () => {
|
|
14
|
-
form.reset();
|
|
15
|
-
showForm.value = false;
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function destroy(id) {
|
|
21
|
-
if (confirm('Delete this canned response?')) {
|
|
22
|
-
router.delete(route('escalated.admin.canned-responses.destroy', id));
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
</script>
|
|
26
|
-
|
|
27
|
-
<template>
|
|
28
|
-
<EscalatedLayout title="Canned Responses">
|
|
29
|
-
<div class="mb-4 flex justify-end">
|
|
30
|
-
<button
|
|
31
|
-
:class="
|
|
32
|
-
showForm
|
|
33
|
-
? '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)]'
|
|
34
|
-
: 'rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-2 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)]'
|
|
35
|
-
"
|
|
36
|
-
@click="showForm = !showForm"
|
|
37
|
-
>
|
|
38
|
-
{{ showForm ? 'Cancel' : 'Add Response' }}
|
|
39
|
-
</button>
|
|
40
|
-
</div>
|
|
41
|
-
<form
|
|
42
|
-
v-if="showForm"
|
|
43
|
-
class="mb-6 space-y-3 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-5"
|
|
44
|
-
@submit.prevent="create"
|
|
45
|
-
>
|
|
46
|
-
<input
|
|
47
|
-
v-model="form.title"
|
|
48
|
-
type="text"
|
|
49
|
-
placeholder="Title"
|
|
50
|
-
required
|
|
51
|
-
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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
52
|
-
/>
|
|
53
|
-
<textarea
|
|
54
|
-
v-model="form.body"
|
|
55
|
-
rows="4"
|
|
56
|
-
placeholder="Response body..."
|
|
57
|
-
required
|
|
58
|
-
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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
59
|
-
></textarea>
|
|
60
|
-
<div class="flex items-center gap-3">
|
|
61
|
-
<input
|
|
62
|
-
v-model="form.category"
|
|
63
|
-
type="text"
|
|
64
|
-
placeholder="Category (optional)"
|
|
65
|
-
class="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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
66
|
-
/>
|
|
67
|
-
<label class="flex items-center gap-2">
|
|
68
|
-
<input
|
|
69
|
-
v-model="form.is_shared"
|
|
70
|
-
type="checkbox"
|
|
71
|
-
class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-[var(--esc-panel-text)] focus:ring-[var(--esc-panel-border-input)]"
|
|
72
|
-
/>
|
|
73
|
-
<span class="text-sm text-[var(--esc-panel-text-secondary)]">Shared</span>
|
|
74
|
-
</label>
|
|
75
|
-
<button
|
|
76
|
-
type="submit"
|
|
77
|
-
:disabled="form.processing"
|
|
78
|
-
class="ml-auto rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-2 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)] disabled:opacity-50"
|
|
79
|
-
>
|
|
80
|
-
Create
|
|
81
|
-
</button>
|
|
82
|
-
</div>
|
|
83
|
-
</form>
|
|
84
|
-
<div class="space-y-3">
|
|
85
|
-
<div
|
|
86
|
-
v-if="!responses?.length"
|
|
87
|
-
class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] px-4 py-12 text-center"
|
|
88
|
-
>
|
|
89
|
-
<svg
|
|
90
|
-
class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
|
|
91
|
-
fill="none"
|
|
92
|
-
stroke="currentColor"
|
|
93
|
-
stroke-width="1.5"
|
|
94
|
-
viewBox="0 0 24 24"
|
|
95
|
-
>
|
|
96
|
-
<path
|
|
97
|
-
stroke-linecap="round"
|
|
98
|
-
stroke-linejoin="round"
|
|
99
|
-
d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
|
|
100
|
-
/>
|
|
101
|
-
</svg>
|
|
102
|
-
<p class="text-sm text-[var(--esc-panel-text-muted)]">No canned responses yet</p>
|
|
103
|
-
<p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
|
|
104
|
-
Create reusable templates for common replies
|
|
105
|
-
</p>
|
|
106
|
-
</div>
|
|
107
|
-
<div
|
|
108
|
-
v-for="resp in responses"
|
|
109
|
-
:key="resp.id"
|
|
110
|
-
class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-4 transition-colors hover:bg-gray-900/80"
|
|
111
|
-
>
|
|
112
|
-
<div class="mb-2 flex items-center justify-between">
|
|
113
|
-
<div class="flex items-center gap-2">
|
|
114
|
-
<span class="font-medium text-[var(--esc-panel-text-secondary)]">{{ resp.title }}</span>
|
|
115
|
-
<span
|
|
116
|
-
v-if="resp.category"
|
|
117
|
-
class="rounded-md bg-[var(--esc-panel-hover)] px-2 py-0.5 text-xs text-[var(--esc-panel-text-tertiary)] ring-1 ring-white/[0.06]"
|
|
118
|
-
>{{ resp.category }}</span
|
|
119
|
-
>
|
|
120
|
-
</div>
|
|
121
|
-
<button class="text-sm text-rose-400 hover:text-rose-300" @click="destroy(resp.id)">Delete</button>
|
|
122
|
-
</div>
|
|
123
|
-
<p class="text-sm text-[var(--esc-panel-text-tertiary)]">{{ resp.body }}</p>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</EscalatedLayout>
|
|
127
|
-
</template>
|
|
1
|
+
<script setup>
|
|
2
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
+
import { useForm, router } from '@inertiajs/vue3';
|
|
4
|
+
import { ref } from 'vue';
|
|
5
|
+
|
|
6
|
+
defineProps({ responses: Array });
|
|
7
|
+
|
|
8
|
+
const showForm = ref(false);
|
|
9
|
+
const form = useForm({ title: '', body: '', category: '', is_shared: true });
|
|
10
|
+
|
|
11
|
+
function create() {
|
|
12
|
+
form.post(route('escalated.admin.canned-responses.store'), {
|
|
13
|
+
onSuccess: () => {
|
|
14
|
+
form.reset();
|
|
15
|
+
showForm.value = false;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function destroy(id) {
|
|
21
|
+
if (confirm('Delete this canned response?')) {
|
|
22
|
+
router.delete(route('escalated.admin.canned-responses.destroy', id));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<EscalatedLayout title="Canned Responses">
|
|
29
|
+
<div class="mb-4 flex justify-end">
|
|
30
|
+
<button
|
|
31
|
+
:class="
|
|
32
|
+
showForm
|
|
33
|
+
? '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)]'
|
|
34
|
+
: 'rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-2 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)]'
|
|
35
|
+
"
|
|
36
|
+
@click="showForm = !showForm"
|
|
37
|
+
>
|
|
38
|
+
{{ showForm ? 'Cancel' : 'Add Response' }}
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
<form
|
|
42
|
+
v-if="showForm"
|
|
43
|
+
class="mb-6 space-y-3 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-5"
|
|
44
|
+
@submit.prevent="create"
|
|
45
|
+
>
|
|
46
|
+
<input
|
|
47
|
+
v-model="form.title"
|
|
48
|
+
type="text"
|
|
49
|
+
placeholder="Title"
|
|
50
|
+
required
|
|
51
|
+
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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
52
|
+
/>
|
|
53
|
+
<textarea
|
|
54
|
+
v-model="form.body"
|
|
55
|
+
rows="4"
|
|
56
|
+
placeholder="Response body..."
|
|
57
|
+
required
|
|
58
|
+
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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
59
|
+
></textarea>
|
|
60
|
+
<div class="flex items-center gap-3">
|
|
61
|
+
<input
|
|
62
|
+
v-model="form.category"
|
|
63
|
+
type="text"
|
|
64
|
+
placeholder="Category (optional)"
|
|
65
|
+
class="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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
66
|
+
/>
|
|
67
|
+
<label class="flex items-center gap-2">
|
|
68
|
+
<input
|
|
69
|
+
v-model="form.is_shared"
|
|
70
|
+
type="checkbox"
|
|
71
|
+
class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-[var(--esc-panel-text)] focus:ring-[var(--esc-panel-border-input)]"
|
|
72
|
+
/>
|
|
73
|
+
<span class="text-sm text-[var(--esc-panel-text-secondary)]">Shared</span>
|
|
74
|
+
</label>
|
|
75
|
+
<button
|
|
76
|
+
type="submit"
|
|
77
|
+
:disabled="form.processing"
|
|
78
|
+
class="ml-auto rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-2 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)] disabled:opacity-50"
|
|
79
|
+
>
|
|
80
|
+
Create
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
</form>
|
|
84
|
+
<div class="space-y-3">
|
|
85
|
+
<div
|
|
86
|
+
v-if="!responses?.length"
|
|
87
|
+
class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] px-4 py-12 text-center"
|
|
88
|
+
>
|
|
89
|
+
<svg
|
|
90
|
+
class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
|
|
91
|
+
fill="none"
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
stroke-width="1.5"
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
>
|
|
96
|
+
<path
|
|
97
|
+
stroke-linecap="round"
|
|
98
|
+
stroke-linejoin="round"
|
|
99
|
+
d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
|
|
100
|
+
/>
|
|
101
|
+
</svg>
|
|
102
|
+
<p class="text-sm text-[var(--esc-panel-text-muted)]">No canned responses yet</p>
|
|
103
|
+
<p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
|
|
104
|
+
Create reusable templates for common replies
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
<div
|
|
108
|
+
v-for="resp in responses"
|
|
109
|
+
:key="resp.id"
|
|
110
|
+
class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-4 transition-colors hover:bg-gray-900/80"
|
|
111
|
+
>
|
|
112
|
+
<div class="mb-2 flex items-center justify-between">
|
|
113
|
+
<div class="flex items-center gap-2">
|
|
114
|
+
<span class="font-medium text-[var(--esc-panel-text-secondary)]">{{ resp.title }}</span>
|
|
115
|
+
<span
|
|
116
|
+
v-if="resp.category"
|
|
117
|
+
class="rounded-md bg-[var(--esc-panel-hover)] px-2 py-0.5 text-xs text-[var(--esc-panel-text-tertiary)] ring-1 ring-white/[0.06]"
|
|
118
|
+
>{{ resp.category }}</span
|
|
119
|
+
>
|
|
120
|
+
</div>
|
|
121
|
+
<button class="text-sm text-rose-400 hover:text-rose-300" @click="destroy(resp.id)">Delete</button>
|
|
122
|
+
</div>
|
|
123
|
+
<p class="text-sm text-[var(--esc-panel-text-tertiary)]">{{ resp.body }}</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</EscalatedLayout>
|
|
127
|
+
</template>
|
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
-
import AgentLoadIndicator from '../../../components/AgentLoadIndicator.vue';
|
|
4
|
-
import { useForm } from '@inertiajs/vue3';
|
|
5
|
-
import { ref } from 'vue';
|
|
6
|
-
|
|
7
|
-
defineProps({ capacities: Array });
|
|
8
|
-
|
|
9
|
-
const editingId = ref(null);
|
|
10
|
-
const editForm = useForm({ max_concurrent: 10 });
|
|
11
|
-
|
|
12
|
-
function startEdit(cap) {
|
|
13
|
-
editingId.value = cap.id;
|
|
14
|
-
editForm.max_concurrent = cap.max_concurrent;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function saveEdit(id) {
|
|
18
|
-
editForm.put(route('escalated.admin.capacity.update', id), {
|
|
19
|
-
preserveScroll: true,
|
|
20
|
-
onSuccess: () => {
|
|
21
|
-
editingId.value = null;
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
</script>
|
|
26
|
-
|
|
27
|
-
<template>
|
|
28
|
-
<EscalatedLayout title="Agent Capacity">
|
|
29
|
-
<div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
|
|
30
|
-
<table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
|
|
31
|
-
<thead>
|
|
32
|
-
<tr class="bg-[var(--esc-panel-hover)]">
|
|
33
|
-
<th
|
|
34
|
-
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
35
|
-
>
|
|
36
|
-
Agent
|
|
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
|
-
Channel
|
|
42
|
-
</th>
|
|
43
|
-
<th
|
|
44
|
-
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
45
|
-
>
|
|
46
|
-
Max Concurrent
|
|
47
|
-
</th>
|
|
48
|
-
<th
|
|
49
|
-
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
50
|
-
>
|
|
51
|
-
Current Load
|
|
52
|
-
</th>
|
|
53
|
-
<th
|
|
54
|
-
class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
55
|
-
>
|
|
56
|
-
Actions
|
|
57
|
-
</th>
|
|
58
|
-
</tr>
|
|
59
|
-
</thead>
|
|
60
|
-
<tbody class="divide-y divide-[var(--esc-panel-border)]">
|
|
61
|
-
<tr v-if="!capacities?.length">
|
|
62
|
-
<td colspan="5" class="px-4 py-12 text-center">
|
|
63
|
-
<svg
|
|
64
|
-
class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
|
|
65
|
-
fill="none"
|
|
66
|
-
stroke="currentColor"
|
|
67
|
-
stroke-width="1.5"
|
|
68
|
-
viewBox="0 0 24 24"
|
|
69
|
-
>
|
|
70
|
-
<path
|
|
71
|
-
stroke-linecap="round"
|
|
72
|
-
stroke-linejoin="round"
|
|
73
|
-
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
|
74
|
-
/>
|
|
75
|
-
</svg>
|
|
76
|
-
<p class="text-sm text-[var(--esc-panel-text-muted)]">No capacity records yet</p>
|
|
77
|
-
<p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
|
|
78
|
-
Capacity records are created when agents are assigned tickets
|
|
79
|
-
</p>
|
|
80
|
-
</td>
|
|
81
|
-
</tr>
|
|
82
|
-
<tr
|
|
83
|
-
v-for="cap in capacities"
|
|
84
|
-
:key="cap.id"
|
|
85
|
-
class="transition-colors hover:bg-[var(--esc-panel-hover)]"
|
|
86
|
-
>
|
|
87
|
-
<td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
|
|
88
|
-
{{ cap.agent_name }}
|
|
89
|
-
</td>
|
|
90
|
-
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">{{ cap.channel }}</td>
|
|
91
|
-
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-secondary)]">
|
|
92
|
-
<template v-if="editingId === cap.id">
|
|
93
|
-
<form class="flex items-center gap-2" @submit.prevent="saveEdit(cap.id)">
|
|
94
|
-
<input
|
|
95
|
-
v-model.number="editForm.max_concurrent"
|
|
96
|
-
type="number"
|
|
97
|
-
min="1"
|
|
98
|
-
max="999"
|
|
99
|
-
class="w-20 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-2 py-1 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)]"
|
|
100
|
-
/>
|
|
101
|
-
<button
|
|
102
|
-
type="submit"
|
|
103
|
-
class="text-xs text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
|
|
104
|
-
>
|
|
105
|
-
Save
|
|
106
|
-
</button>
|
|
107
|
-
<button
|
|
108
|
-
type="button"
|
|
109
|
-
class="text-xs text-[var(--esc-panel-text-tertiary)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
110
|
-
@click="editingId = null"
|
|
111
|
-
>
|
|
112
|
-
Cancel
|
|
113
|
-
</button>
|
|
114
|
-
</form>
|
|
115
|
-
</template>
|
|
116
|
-
<template v-else>{{ cap.max_concurrent }}</template>
|
|
117
|
-
</td>
|
|
118
|
-
<td class="px-4 py-3">
|
|
119
|
-
<AgentLoadIndicator :current="cap.current_count" :max="cap.max_concurrent" />
|
|
120
|
-
</td>
|
|
121
|
-
<td class="px-4 py-3 text-right text-sm">
|
|
122
|
-
<button
|
|
123
|
-
class="text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
|
|
124
|
-
@click="startEdit(cap)"
|
|
125
|
-
>
|
|
126
|
-
Edit
|
|
127
|
-
</button>
|
|
128
|
-
</td>
|
|
129
|
-
</tr>
|
|
130
|
-
</tbody>
|
|
131
|
-
</table>
|
|
132
|
-
</div>
|
|
133
|
-
</EscalatedLayout>
|
|
134
|
-
</template>
|
|
1
|
+
<script setup>
|
|
2
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
+
import AgentLoadIndicator from '../../../components/AgentLoadIndicator.vue';
|
|
4
|
+
import { useForm } from '@inertiajs/vue3';
|
|
5
|
+
import { ref } from 'vue';
|
|
6
|
+
|
|
7
|
+
defineProps({ capacities: Array });
|
|
8
|
+
|
|
9
|
+
const editingId = ref(null);
|
|
10
|
+
const editForm = useForm({ max_concurrent: 10 });
|
|
11
|
+
|
|
12
|
+
function startEdit(cap) {
|
|
13
|
+
editingId.value = cap.id;
|
|
14
|
+
editForm.max_concurrent = cap.max_concurrent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function saveEdit(id) {
|
|
18
|
+
editForm.put(route('escalated.admin.capacity.update', id), {
|
|
19
|
+
preserveScroll: true,
|
|
20
|
+
onSuccess: () => {
|
|
21
|
+
editingId.value = null;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<EscalatedLayout title="Agent Capacity">
|
|
29
|
+
<div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
|
|
30
|
+
<table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
|
|
31
|
+
<thead>
|
|
32
|
+
<tr class="bg-[var(--esc-panel-hover)]">
|
|
33
|
+
<th
|
|
34
|
+
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
35
|
+
>
|
|
36
|
+
Agent
|
|
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
|
+
Channel
|
|
42
|
+
</th>
|
|
43
|
+
<th
|
|
44
|
+
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
45
|
+
>
|
|
46
|
+
Max Concurrent
|
|
47
|
+
</th>
|
|
48
|
+
<th
|
|
49
|
+
class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
50
|
+
>
|
|
51
|
+
Current Load
|
|
52
|
+
</th>
|
|
53
|
+
<th
|
|
54
|
+
class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
55
|
+
>
|
|
56
|
+
Actions
|
|
57
|
+
</th>
|
|
58
|
+
</tr>
|
|
59
|
+
</thead>
|
|
60
|
+
<tbody class="divide-y divide-[var(--esc-panel-border)]">
|
|
61
|
+
<tr v-if="!capacities?.length">
|
|
62
|
+
<td colspan="5" class="px-4 py-12 text-center">
|
|
63
|
+
<svg
|
|
64
|
+
class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
|
|
65
|
+
fill="none"
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
stroke-width="1.5"
|
|
68
|
+
viewBox="0 0 24 24"
|
|
69
|
+
>
|
|
70
|
+
<path
|
|
71
|
+
stroke-linecap="round"
|
|
72
|
+
stroke-linejoin="round"
|
|
73
|
+
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
76
|
+
<p class="text-sm text-[var(--esc-panel-text-muted)]">No capacity records yet</p>
|
|
77
|
+
<p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
|
|
78
|
+
Capacity records are created when agents are assigned tickets
|
|
79
|
+
</p>
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
<tr
|
|
83
|
+
v-for="cap in capacities"
|
|
84
|
+
:key="cap.id"
|
|
85
|
+
class="transition-colors hover:bg-[var(--esc-panel-hover)]"
|
|
86
|
+
>
|
|
87
|
+
<td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
|
|
88
|
+
{{ cap.agent_name }}
|
|
89
|
+
</td>
|
|
90
|
+
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">{{ cap.channel }}</td>
|
|
91
|
+
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-secondary)]">
|
|
92
|
+
<template v-if="editingId === cap.id">
|
|
93
|
+
<form class="flex items-center gap-2" @submit.prevent="saveEdit(cap.id)">
|
|
94
|
+
<input
|
|
95
|
+
v-model.number="editForm.max_concurrent"
|
|
96
|
+
type="number"
|
|
97
|
+
min="1"
|
|
98
|
+
max="999"
|
|
99
|
+
class="w-20 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-2 py-1 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)]"
|
|
100
|
+
/>
|
|
101
|
+
<button
|
|
102
|
+
type="submit"
|
|
103
|
+
class="text-xs text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
|
|
104
|
+
>
|
|
105
|
+
Save
|
|
106
|
+
</button>
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="text-xs text-[var(--esc-panel-text-tertiary)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
110
|
+
@click="editingId = null"
|
|
111
|
+
>
|
|
112
|
+
Cancel
|
|
113
|
+
</button>
|
|
114
|
+
</form>
|
|
115
|
+
</template>
|
|
116
|
+
<template v-else>{{ cap.max_concurrent }}</template>
|
|
117
|
+
</td>
|
|
118
|
+
<td class="px-4 py-3">
|
|
119
|
+
<AgentLoadIndicator :current="cap.current_count" :max="cap.max_concurrent" />
|
|
120
|
+
</td>
|
|
121
|
+
<td class="px-4 py-3 text-right text-sm">
|
|
122
|
+
<button
|
|
123
|
+
class="text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
|
|
124
|
+
@click="startEdit(cap)"
|
|
125
|
+
>
|
|
126
|
+
Edit
|
|
127
|
+
</button>
|
|
128
|
+
</td>
|
|
129
|
+
</tr>
|
|
130
|
+
</tbody>
|
|
131
|
+
</table>
|
|
132
|
+
</div>
|
|
133
|
+
</EscalatedLayout>
|
|
134
|
+
</template>
|