@escalated-dev/escalated 0.6.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.
Files changed (130) hide show
  1. package/README.md +94 -2
  2. package/package.json +31 -2
  3. package/src/components/ActivityTimeline.stories.js +55 -0
  4. package/src/components/ActivityTimeline.vue +24 -7
  5. package/src/components/AdminDashboard.stories.js +1095 -0
  6. package/src/components/AgentLoadIndicator.stories.js +37 -0
  7. package/src/components/AgentLoadIndicator.vue +49 -0
  8. package/src/components/AgentPerformanceCard.vue +44 -0
  9. package/src/components/ArticleEditor.vue +82 -0
  10. package/src/components/AssigneeSelect.vue +19 -5
  11. package/src/components/AttachmentList.vue +53 -37
  12. package/src/components/AuditLogEntry.vue +95 -0
  13. package/src/components/BulkActionBar.vue +99 -30
  14. package/src/components/CategoryTree.vue +89 -0
  15. package/src/components/ChartWidget.vue +46 -0
  16. package/src/components/CollisionWarning.stories.js +30 -0
  17. package/src/components/CollisionWarning.vue +68 -0
  18. package/src/components/ConditionalFieldRules.vue +135 -0
  19. package/src/components/ContextPanel.vue +66 -0
  20. package/src/components/ContextPanelSection.vue +65 -0
  21. package/src/components/CustomFieldRenderer.vue +159 -0
  22. package/src/components/EscalatedLayout.vue +400 -69
  23. package/src/components/FileDropzone.vue +26 -10
  24. package/src/components/FollowButton.stories.js +30 -0
  25. package/src/components/FollowButton.vue +53 -28
  26. package/src/components/KeyboardShortcutHelp.vue +129 -39
  27. package/src/components/KnowledgePanel.vue +122 -0
  28. package/src/components/KpiCard.stories.js +32 -0
  29. package/src/components/KpiCard.vue +88 -0
  30. package/src/components/MacroDropdown.vue +95 -35
  31. package/src/components/PermissionMatrix.vue +107 -0
  32. package/src/components/PinnedNotes.vue +70 -31
  33. package/src/components/PresenceIndicator.vue +93 -27
  34. package/src/components/PriorityBadge.stories.js +33 -0
  35. package/src/components/PriorityBadge.vue +9 -2
  36. package/src/components/QuickFilters.vue +65 -49
  37. package/src/components/ReplyComposer.vue +144 -30
  38. package/src/components/ReplyThread.vue +72 -20
  39. package/src/components/SatisfactionRating.vue +98 -46
  40. package/src/components/ScheduleEditor.vue +104 -0
  41. package/src/components/SideConversation.vue +165 -0
  42. package/src/components/SideConversationList.vue +147 -0
  43. package/src/components/SkillTagManager.vue +79 -0
  44. package/src/components/SlaComplianceChart.vue +53 -0
  45. package/src/components/SlaTimer.stories.js +35 -0
  46. package/src/components/SlaTimer.vue +17 -4
  47. package/src/components/StatsCard.stories.js +24 -0
  48. package/src/components/StatsCard.vue +58 -46
  49. package/src/components/StatusBadge.stories.js +53 -0
  50. package/src/components/StatusBadge.vue +36 -4
  51. package/src/components/TagSelect.vue +43 -12
  52. package/src/components/TicketFilters.vue +225 -69
  53. package/src/components/TicketLinkPanel.vue +193 -0
  54. package/src/components/TicketList.vue +594 -193
  55. package/src/components/TicketMergeDialog.vue +157 -0
  56. package/src/components/TicketSidebar.vue +167 -99
  57. package/src/components/TicketTypeSelector.vue +21 -0
  58. package/src/components/TotpInput.stories.js +9 -0
  59. package/src/components/TotpInput.vue +81 -0
  60. package/src/components/TwoFactorSetup.vue +113 -0
  61. package/src/composables/useDebouncedSearch.js +38 -0
  62. package/src/composables/usePluginExtensions.js +95 -3
  63. package/src/composables/usePluginHooks.js +65 -0
  64. package/src/defineEscalatedPlugin.js +34 -8
  65. package/src/index.js +72 -35
  66. package/src/locales/en.json +25 -0
  67. package/src/pages/Admin/ApiTokens/Index.vue +231 -67
  68. package/src/pages/Admin/AuditLog/Index.vue +154 -0
  69. package/src/pages/Admin/Automations/Form.vue +184 -0
  70. package/src/pages/Admin/Automations/Index.vue +129 -0
  71. package/src/pages/Admin/BusinessHours/Form.vue +172 -0
  72. package/src/pages/Admin/BusinessHours/Index.vue +113 -0
  73. package/src/pages/Admin/CannedResponses/Index.vue +127 -70
  74. package/src/pages/Admin/Capacity/Index.vue +134 -0
  75. package/src/pages/Admin/CustomFields/Form.vue +195 -0
  76. package/src/pages/Admin/CustomFields/Index.vue +134 -0
  77. package/src/pages/Admin/CustomObjects/Form.vue +202 -0
  78. package/src/pages/Admin/CustomObjects/Index.vue +127 -0
  79. package/src/pages/Admin/CustomObjects/Records.vue +228 -0
  80. package/src/pages/Admin/Departments/Form.vue +75 -51
  81. package/src/pages/Admin/Departments/Index.vue +109 -57
  82. package/src/pages/Admin/EscalationRules/Form.vue +157 -96
  83. package/src/pages/Admin/EscalationRules/Index.vue +178 -57
  84. package/src/pages/Admin/Import/Connect.vue +96 -0
  85. package/src/pages/Admin/Import/Index.vue +102 -0
  86. package/src/pages/Admin/Import/Mapping.vue +110 -0
  87. package/src/pages/Admin/Import/Progress.vue +153 -0
  88. package/src/pages/Admin/Import/Review.vue +76 -0
  89. package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -0
  90. package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -0
  91. package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -0
  92. package/src/pages/Admin/Macros/Index.vue +250 -89
  93. package/src/pages/Admin/Plugins/Index.vue +205 -75
  94. package/src/pages/Admin/Reports/AgentMetrics.vue +115 -0
  95. package/src/pages/Admin/Reports/CsatReport.vue +127 -0
  96. package/src/pages/Admin/Reports/Dashboard.vue +211 -0
  97. package/src/pages/Admin/Reports/SlaReport.vue +166 -0
  98. package/src/pages/Admin/Reports.vue +139 -34
  99. package/src/pages/Admin/Roles/Form.vue +71 -0
  100. package/src/pages/Admin/Roles/Index.vue +122 -0
  101. package/src/pages/Admin/Settings/CsatSettings.vue +165 -0
  102. package/src/pages/Admin/Settings/DataRetention.vue +182 -0
  103. package/src/pages/Admin/Settings/EmailSettings.vue +243 -0
  104. package/src/pages/Admin/Settings/Sandbox.vue +135 -0
  105. package/src/pages/Admin/Settings/SsoSettings.vue +218 -0
  106. package/src/pages/Admin/Settings/TwoFactor.vue +110 -0
  107. package/src/pages/Admin/Settings.vue +468 -260
  108. package/src/pages/Admin/Skills/Form.vue +53 -0
  109. package/src/pages/Admin/Skills/Index.vue +94 -0
  110. package/src/pages/Admin/SlaPolicies/Form.vue +118 -75
  111. package/src/pages/Admin/SlaPolicies/Index.vue +120 -59
  112. package/src/pages/Admin/Statuses/Form.vue +114 -0
  113. package/src/pages/Admin/Statuses/Index.vue +153 -0
  114. package/src/pages/Admin/Tags/Index.vue +191 -105
  115. package/src/pages/Admin/Tickets/Index.vue +91 -39
  116. package/src/pages/Admin/Tickets/Show.vue +101 -37
  117. package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -0
  118. package/src/pages/Admin/Webhooks/Form.vue +110 -0
  119. package/src/pages/Admin/Webhooks/Index.vue +136 -0
  120. package/src/pages/Agent/Dashboard.vue +311 -186
  121. package/src/pages/Agent/TicketIndex.vue +88 -38
  122. package/src/pages/Agent/TicketShow.vue +258 -51
  123. package/src/pages/Customer/Create.vue +36 -9
  124. package/src/pages/Customer/Index.vue +31 -24
  125. package/src/pages/Customer/KnowledgeBase/Article.vue +127 -0
  126. package/src/pages/Customer/KnowledgeBase/Index.vue +151 -0
  127. package/src/plugin.js +86 -1
  128. package/src/utils/constants.js +30 -0
  129. package/src/utils/formatting.js +35 -0
  130. package/src/pages/Admin/Plugins.vue +0 -129
@@ -0,0 +1,53 @@
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { useForm } from '@inertiajs/vue3';
4
+
5
+ const props = defineProps({
6
+ skill: { type: Object, default: null },
7
+ });
8
+
9
+ const form = useForm({
10
+ name: props.skill?.name || '',
11
+ });
12
+
13
+ function submit() {
14
+ if (props.skill) {
15
+ form.put(route('escalated.admin.skills.update', props.skill.id));
16
+ } else {
17
+ form.post(route('escalated.admin.skills.store'));
18
+ }
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <EscalatedLayout :title="skill ? 'Edit Skill' : 'Create Skill'">
24
+ <form class="max-w-lg space-y-4" @submit.prevent="submit">
25
+ <div>
26
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Name</label>
27
+ <input
28
+ v-model="form.name"
29
+ type="text"
30
+ required
31
+ class="mt-1 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)]"
32
+ />
33
+ <p v-if="form.errors.name" class="mt-1 text-xs text-rose-400">{{ form.errors.name }}</p>
34
+ </div>
35
+
36
+ <div class="flex gap-3">
37
+ <button
38
+ type="submit"
39
+ :disabled="form.processing"
40
+ class="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"
41
+ >
42
+ {{ skill ? 'Update' : 'Create' }}
43
+ </button>
44
+ <a
45
+ :href="route('escalated.admin.skills.index')"
46
+ 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
+ >
48
+ Cancel
49
+ </a>
50
+ </div>
51
+ </form>
52
+ </EscalatedLayout>
53
+ </template>
@@ -0,0 +1,94 @@
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { Link, router } from '@inertiajs/vue3';
4
+
5
+ defineProps({ skills: Array });
6
+
7
+ function destroy(id) {
8
+ if (confirm('Delete this skill?')) {
9
+ router.delete(route('escalated.admin.skills.destroy', id));
10
+ }
11
+ }
12
+ </script>
13
+
14
+ <template>
15
+ <EscalatedLayout title="Skills">
16
+ <div class="mb-4 flex justify-end">
17
+ <Link
18
+ :href="route('escalated.admin.skills.create')"
19
+ class="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)]"
20
+ >
21
+ Add Skill
22
+ </Link>
23
+ </div>
24
+ <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
25
+ <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
26
+ <thead>
27
+ <tr class="bg-[var(--esc-panel-hover)]">
28
+ <th
29
+ class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
30
+ >
31
+ Name
32
+ </th>
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
+ Agents
37
+ </th>
38
+ <th
39
+ class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
40
+ >
41
+ Actions
42
+ </th>
43
+ </tr>
44
+ </thead>
45
+ <tbody class="divide-y divide-[var(--esc-panel-border)]">
46
+ <tr v-if="!skills?.length">
47
+ <td colspan="3" class="px-4 py-12 text-center">
48
+ <svg
49
+ class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
50
+ fill="none"
51
+ stroke="currentColor"
52
+ stroke-width="1.5"
53
+ viewBox="0 0 24 24"
54
+ >
55
+ <path
56
+ stroke-linecap="round"
57
+ stroke-linejoin="round"
58
+ d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
59
+ />
60
+ </svg>
61
+ <p class="text-sm text-[var(--esc-panel-text-muted)]">No skills yet</p>
62
+ <p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
63
+ Create skills for agent routing
64
+ </p>
65
+ </td>
66
+ </tr>
67
+ <tr
68
+ v-for="skill in skills"
69
+ :key="skill.id"
70
+ class="transition-colors hover:bg-[var(--esc-panel-hover)]"
71
+ >
72
+ <td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
73
+ {{ skill.name }}
74
+ </td>
75
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
76
+ {{ skill.agents_count }}
77
+ </td>
78
+ <td class="px-4 py-3 text-right text-sm">
79
+ <Link
80
+ :href="route('escalated.admin.skills.edit', skill.id)"
81
+ class="text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
82
+ >
83
+ Edit
84
+ </Link>
85
+ <button class="ml-3 text-rose-400 hover:text-rose-300" @click="destroy(skill.id)">
86
+ Delete
87
+ </button>
88
+ </td>
89
+ </tr>
90
+ </tbody>
91
+ </table>
92
+ </div>
93
+ </EscalatedLayout>
94
+ </template>
@@ -1,75 +1,118 @@
1
- <script setup>
2
- import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
- import { useForm } from '@inertiajs/vue3';
4
-
5
- const props = defineProps({ policy: { type: Object, default: null }, priorities: Array });
6
-
7
- const form = useForm({
8
- name: props.policy?.name || '',
9
- description: props.policy?.description || '',
10
- is_default: props.policy?.is_default ?? false,
11
- first_response_hours: props.policy?.first_response_hours || {},
12
- resolution_hours: props.policy?.resolution_hours || {},
13
- business_hours_only: props.policy?.business_hours_only ?? false,
14
- is_active: props.policy?.is_active ?? true,
15
- });
16
-
17
- function submit() {
18
- if (props.policy) {
19
- form.put(route('escalated.admin.sla-policies.update', props.policy.id));
20
- } else {
21
- form.post(route('escalated.admin.sla-policies.store'));
22
- }
23
- }
24
- </script>
25
-
26
- <template>
27
- <EscalatedLayout :title="policy ? 'Edit SLA Policy' : 'New SLA Policy'">
28
- <form @submit.prevent="submit" class="mx-auto max-w-lg space-y-5 rounded-xl border border-white/[0.06] bg-neutral-900/60 p-6">
29
- <div>
30
- <label class="block text-sm font-medium text-neutral-300">Name</label>
31
- <input v-model="form.name" type="text" required class="mt-1 w-full rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
32
- </div>
33
- <div>
34
- <label class="block text-sm font-medium text-neutral-300">Description</label>
35
- <textarea v-model="form.description" rows="2" class="mt-1 w-full rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10"></textarea>
36
- </div>
37
- <div>
38
- <h3 class="mb-2 text-sm font-medium text-neutral-300">First Response Hours (by priority)</h3>
39
- <div class="grid grid-cols-2 gap-3">
40
- <div v-for="p in priorities" :key="p">
41
- <label class="text-xs capitalize text-neutral-400">{{ p }}</label>
42
- <input v-model.number="form.first_response_hours[p]" type="number" step="0.5" min="0" class="w-full rounded-lg border border-white/10 bg-neutral-950 px-2 py-1.5 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
43
- </div>
44
- </div>
45
- </div>
46
- <div>
47
- <h3 class="mb-2 text-sm font-medium text-neutral-300">Resolution Hours (by priority)</h3>
48
- <div class="grid grid-cols-2 gap-3">
49
- <div v-for="p in priorities" :key="p">
50
- <label class="text-xs capitalize text-neutral-400">{{ p }}</label>
51
- <input v-model.number="form.resolution_hours[p]" type="number" step="0.5" min="0" class="w-full rounded-lg border border-white/10 bg-neutral-950 px-2 py-1.5 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
52
- </div>
53
- </div>
54
- </div>
55
- <label class="flex items-center gap-2">
56
- <input v-model="form.business_hours_only" type="checkbox" class="rounded border-white/20 bg-neutral-900 text-white focus:ring-white/10" />
57
- <span class="text-sm text-neutral-300">Business hours only</span>
58
- </label>
59
- <label class="flex items-center gap-2">
60
- <input v-model="form.is_default" type="checkbox" class="rounded border-white/20 bg-neutral-900 text-white focus:ring-white/10" />
61
- <span class="text-sm text-neutral-300">Default policy</span>
62
- </label>
63
- <label class="flex items-center gap-2">
64
- <input v-model="form.is_active" type="checkbox" class="rounded border-white/20 bg-neutral-900 text-white focus:ring-white/10" />
65
- <span class="text-sm text-neutral-300">Active</span>
66
- </label>
67
- <div class="flex justify-end">
68
- <button type="submit" :disabled="form.processing"
69
- class="rounded-lg bg-gradient-to-r from-cyan-500 to-violet-500 px-5 py-2 text-sm font-medium text-white shadow-lg shadow-black/20 transition-all hover:from-cyan-400 hover:to-violet-400 disabled:opacity-50">
70
- {{ policy ? 'Update' : 'Create' }}
71
- </button>
72
- </div>
73
- </form>
74
- </EscalatedLayout>
75
- </template>
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { useForm } from '@inertiajs/vue3';
4
+
5
+ const props = defineProps({ policy: { type: Object, default: null }, priorities: Array });
6
+
7
+ const form = useForm({
8
+ name: props.policy?.name || '',
9
+ description: props.policy?.description || '',
10
+ is_default: props.policy?.is_default ?? false,
11
+ first_response_hours: props.policy?.first_response_hours || {},
12
+ resolution_hours: props.policy?.resolution_hours || {},
13
+ business_hours_only: props.policy?.business_hours_only ?? false,
14
+ is_active: props.policy?.is_active ?? true,
15
+ });
16
+
17
+ function submit() {
18
+ if (props.policy) {
19
+ form.put(route('escalated.admin.sla-policies.update', props.policy.id));
20
+ } else {
21
+ form.post(route('escalated.admin.sla-policies.store'));
22
+ }
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+ <EscalatedLayout :title="policy ? 'Edit SLA Policy' : 'New SLA Policy'">
28
+ <form
29
+ class="mx-auto max-w-lg space-y-5 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
30
+ @submit.prevent="submit"
31
+ >
32
+ <div>
33
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Name</label>
34
+ <input
35
+ v-model="form.name"
36
+ type="text"
37
+ required
38
+ class="mt-1 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)]"
39
+ />
40
+ </div>
41
+ <div>
42
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Description</label>
43
+ <textarea
44
+ v-model="form.description"
45
+ rows="2"
46
+ class="mt-1 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)]"
47
+ ></textarea>
48
+ </div>
49
+ <div>
50
+ <h3 class="mb-2 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
51
+ First Response Hours (by priority)
52
+ </h3>
53
+ <div class="grid grid-cols-2 gap-3">
54
+ <div v-for="p in priorities" :key="p">
55
+ <label class="text-xs capitalize text-[var(--esc-panel-text-tertiary)]">{{ p }}</label>
56
+ <input
57
+ v-model.number="form.first_response_hours[p]"
58
+ type="number"
59
+ step="0.5"
60
+ min="0"
61
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-2 py-1.5 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
62
+ />
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <div>
67
+ <h3 class="mb-2 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
68
+ Resolution Hours (by priority)
69
+ </h3>
70
+ <div class="grid grid-cols-2 gap-3">
71
+ <div v-for="p in priorities" :key="p">
72
+ <label class="text-xs capitalize text-[var(--esc-panel-text-tertiary)]">{{ p }}</label>
73
+ <input
74
+ v-model.number="form.resolution_hours[p]"
75
+ type="number"
76
+ step="0.5"
77
+ min="0"
78
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-2 py-1.5 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
79
+ />
80
+ </div>
81
+ </div>
82
+ </div>
83
+ <label class="flex items-center gap-2">
84
+ <input
85
+ v-model="form.business_hours_only"
86
+ type="checkbox"
87
+ class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-white focus:ring-[var(--esc-panel-border-input)]"
88
+ />
89
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">Business hours only</span>
90
+ </label>
91
+ <label class="flex items-center gap-2">
92
+ <input
93
+ v-model="form.is_default"
94
+ type="checkbox"
95
+ class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-white focus:ring-[var(--esc-panel-border-input)]"
96
+ />
97
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">Default policy</span>
98
+ </label>
99
+ <label class="flex items-center gap-2">
100
+ <input
101
+ v-model="form.is_active"
102
+ type="checkbox"
103
+ class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-white focus:ring-[var(--esc-panel-border-input)]"
104
+ />
105
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">Active</span>
106
+ </label>
107
+ <div class="flex justify-end">
108
+ <button
109
+ type="submit"
110
+ :disabled="form.processing"
111
+ class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 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"
112
+ >
113
+ {{ policy ? 'Update' : 'Create' }}
114
+ </button>
115
+ </div>
116
+ </form>
117
+ </EscalatedLayout>
118
+ </template>
@@ -1,59 +1,120 @@
1
- <script setup>
2
- import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
- import { Link, router } from '@inertiajs/vue3';
4
-
5
- defineProps({ policies: Array });
6
-
7
- function destroy(id) {
8
- if (confirm('Delete this SLA policy?')) {
9
- router.delete(route('escalated.admin.sla-policies.destroy', id));
10
- }
11
- }
12
- </script>
13
-
14
- <template>
15
- <EscalatedLayout title="SLA Policies">
16
- <div class="mb-4 flex justify-end">
17
- <Link :href="route('escalated.admin.sla-policies.create')"
18
- class="rounded-lg bg-gradient-to-r from-cyan-500 to-violet-500 px-4 py-2 text-sm font-medium text-white shadow-lg shadow-black/20 transition-all hover:from-cyan-400 hover:to-violet-400">
19
- Add Policy
20
- </Link>
21
- </div>
22
- <div class="overflow-hidden rounded-xl border border-white/[0.06] bg-neutral-900/60">
23
- <table class="min-w-full divide-y divide-white/[0.06]">
24
- <thead>
25
- <tr class="bg-white/[0.02]">
26
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Name</th>
27
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Default</th>
28
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Business Hours</th>
29
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Tickets</th>
30
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Active</th>
31
- <th class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Actions</th>
32
- </tr>
33
- </thead>
34
- <tbody class="divide-y divide-white/[0.04]">
35
- <tr v-if="!policies?.length">
36
- <td colspan="6" class="px-4 py-12 text-center">
37
- <svg class="mx-auto mb-3 h-8 w-8 text-neutral-700" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
38
- <p class="text-sm text-neutral-500">No SLA policies yet</p>
39
- <p class="mt-1 text-xs text-neutral-600">Define response and resolution time targets</p>
40
- </td>
41
- </tr>
42
- <tr v-for="policy in policies" :key="policy.id" class="transition-colors hover:bg-white/[0.03]">
43
- <td class="px-4 py-3 text-sm font-medium text-neutral-200">{{ policy.name }}</td>
44
- <td class="px-4 py-3 text-sm text-neutral-400">{{ policy.is_default ? 'Yes' : 'No' }}</td>
45
- <td class="px-4 py-3 text-sm text-neutral-400">{{ policy.business_hours_only ? 'Yes' : 'No' }}</td>
46
- <td class="px-4 py-3 text-sm text-neutral-400">{{ policy.tickets_count }}</td>
47
- <td class="px-4 py-3 text-sm">
48
- <span :class="policy.is_active ? 'text-emerald-400' : 'text-neutral-500'">{{ policy.is_active ? 'Yes' : 'No' }}</span>
49
- </td>
50
- <td class="px-4 py-3 text-right text-sm">
51
- <Link :href="route('escalated.admin.sla-policies.edit', policy.id)" class="text-neutral-300 hover:text-white">Edit</Link>
52
- <button @click="destroy(policy.id)" class="ml-3 text-rose-400 hover:text-rose-300">Delete</button>
53
- </td>
54
- </tr>
55
- </tbody>
56
- </table>
57
- </div>
58
- </EscalatedLayout>
59
- </template>
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { Link, router } from '@inertiajs/vue3';
4
+
5
+ defineProps({ policies: Array });
6
+
7
+ function destroy(id) {
8
+ if (confirm('Delete this SLA policy?')) {
9
+ router.delete(route('escalated.admin.sla-policies.destroy', id));
10
+ }
11
+ }
12
+ </script>
13
+
14
+ <template>
15
+ <EscalatedLayout title="SLA Policies">
16
+ <div class="mb-4 flex justify-end">
17
+ <Link
18
+ :href="route('escalated.admin.sla-policies.create')"
19
+ class="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)]"
20
+ >
21
+ Add Policy
22
+ </Link>
23
+ </div>
24
+ <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
25
+ <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
26
+ <thead>
27
+ <tr class="bg-[var(--esc-panel-hover)]">
28
+ <th
29
+ class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
30
+ >
31
+ Name
32
+ </th>
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
+ Default
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
+ Business Hours
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
+ Tickets
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
+ Active
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="!policies?.length">
62
+ <td colspan="6" 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="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
74
+ />
75
+ </svg>
76
+ <p class="text-sm text-[var(--esc-panel-text-muted)]">No SLA policies yet</p>
77
+ <p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
78
+ Define response and resolution time targets
79
+ </p>
80
+ </td>
81
+ </tr>
82
+ <tr
83
+ v-for="policy in policies"
84
+ :key="policy.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
+ {{ policy.name }}
89
+ </td>
90
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
91
+ {{ policy.is_default ? 'Yes' : 'No' }}
92
+ </td>
93
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
94
+ {{ policy.business_hours_only ? 'Yes' : 'No' }}
95
+ </td>
96
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
97
+ {{ policy.tickets_count }}
98
+ </td>
99
+ <td class="px-4 py-3 text-sm">
100
+ <span
101
+ :class="policy.is_active ? 'text-emerald-400' : 'text-[var(--esc-panel-text-muted)]'"
102
+ >{{ policy.is_active ? 'Yes' : 'No' }}</span
103
+ >
104
+ </td>
105
+ <td class="px-4 py-3 text-right text-sm">
106
+ <Link
107
+ :href="route('escalated.admin.sla-policies.edit', policy.id)"
108
+ class="text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
109
+ >Edit</Link
110
+ >
111
+ <button class="ml-3 text-rose-400 hover:text-rose-300" @click="destroy(policy.id)">
112
+ Delete
113
+ </button>
114
+ </td>
115
+ </tr>
116
+ </tbody>
117
+ </table>
118
+ </div>
119
+ </EscalatedLayout>
120
+ </template>