@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,228 @@
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { useForm, router } from '@inertiajs/vue3';
4
+ import { ref, computed } from 'vue';
5
+
6
+ const props = defineProps({
7
+ object: Object,
8
+ records: { type: Array, default: () => [] },
9
+ });
10
+
11
+ const fields = computed(() => props.object.fields_schema || []);
12
+ const editingId = ref(null);
13
+ const showAddForm = ref(false);
14
+
15
+ const newRecordForm = useForm({
16
+ data: {},
17
+ });
18
+
19
+ const editForm = useForm({
20
+ data: {},
21
+ });
22
+
23
+ function startAdd() {
24
+ const data = {};
25
+ fields.value.forEach((f) => {
26
+ data[f.name] = '';
27
+ });
28
+ newRecordForm.data = data;
29
+ showAddForm.value = true;
30
+ }
31
+
32
+ function addRecord() {
33
+ newRecordForm.post(route('escalated.admin.custom-objects.records.store', props.object.id), {
34
+ preserveScroll: true,
35
+ onSuccess: () => {
36
+ showAddForm.value = false;
37
+ newRecordForm.reset();
38
+ },
39
+ });
40
+ }
41
+
42
+ function startEdit(record) {
43
+ editingId.value = record.id;
44
+ editForm.data = { ...(record.data || {}) };
45
+ }
46
+
47
+ function saveEdit(record) {
48
+ editForm.put(route('escalated.admin.custom-objects.records.update', [props.object.id, record.id]), {
49
+ preserveScroll: true,
50
+ onSuccess: () => {
51
+ editingId.value = null;
52
+ },
53
+ });
54
+ }
55
+
56
+ function cancelEdit() {
57
+ editingId.value = null;
58
+ }
59
+
60
+ function deleteRecord(record) {
61
+ if (confirm('Delete this record?')) {
62
+ router.delete(route('escalated.admin.custom-objects.records.destroy', [props.object.id, record.id]), {
63
+ preserveScroll: true,
64
+ });
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <template>
70
+ <EscalatedLayout :title="`${object.name} Records`">
71
+ <div class="mb-6 flex items-center justify-between">
72
+ <div>
73
+ <h2 class="text-lg font-semibold text-[var(--esc-panel-text-secondary)]">{{ object.name }} Records</h2>
74
+ <p class="mt-1 text-sm text-[var(--esc-panel-text-muted)]">{{ records.length }} records</p>
75
+ </div>
76
+ <button
77
+ type="button"
78
+ 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)]"
79
+ @click="startAdd"
80
+ >
81
+ Add Record
82
+ </button>
83
+ </div>
84
+
85
+ <!-- Add Record Form -->
86
+ <div
87
+ v-if="showAddForm"
88
+ class="mb-6 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
89
+ >
90
+ <h3 class="mb-4 text-sm font-semibold text-[var(--esc-panel-text)]">New Record</h3>
91
+ <form class="space-y-3" @submit.prevent="addRecord">
92
+ <div v-for="field in fields" :key="field.name">
93
+ <label class="block text-xs font-medium text-[var(--esc-panel-text-tertiary)]">
94
+ {{ field.name }}
95
+ <span v-if="field.required" class="text-rose-400">*</span>
96
+ </label>
97
+ <select
98
+ v-if="field.type === 'select'"
99
+ v-model="newRecordForm.data[field.name]"
100
+ 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)]"
101
+ >
102
+ <option value="">Select...</option>
103
+ <option v-for="opt in field.options" :key="opt" :value="opt">{{ opt }}</option>
104
+ </select>
105
+ <input
106
+ v-else-if="field.type === 'date'"
107
+ v-model="newRecordForm.data[field.name]"
108
+ type="date"
109
+ 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)]"
110
+ />
111
+ <input
112
+ v-else-if="field.type === 'number'"
113
+ v-model="newRecordForm.data[field.name]"
114
+ type="number"
115
+ 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)]"
116
+ />
117
+ <input
118
+ v-else
119
+ v-model="newRecordForm.data[field.name]"
120
+ type="text"
121
+ 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)]"
122
+ />
123
+ </div>
124
+ <div class="flex gap-2">
125
+ <button
126
+ type="submit"
127
+ :disabled="newRecordForm.processing"
128
+ class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-1.5 text-sm font-medium text-white"
129
+ >
130
+ Save
131
+ </button>
132
+ <button
133
+ type="button"
134
+ class="rounded-lg border border-[var(--esc-panel-border-input)] px-4 py-1.5 text-sm text-[var(--esc-panel-text-tertiary)]"
135
+ @click="showAddForm = false"
136
+ >
137
+ Cancel
138
+ </button>
139
+ </div>
140
+ </form>
141
+ </div>
142
+
143
+ <!-- Records Table -->
144
+ <div v-if="records.length" class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)]">
145
+ <table class="w-full">
146
+ <thead>
147
+ <tr class="border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)]">
148
+ <th
149
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
150
+ >
151
+ ID
152
+ </th>
153
+ <th
154
+ v-for="field in fields"
155
+ :key="field.name"
156
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
157
+ >
158
+ {{ field.name }}
159
+ </th>
160
+ <th
161
+ class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
162
+ >
163
+ Actions
164
+ </th>
165
+ </tr>
166
+ </thead>
167
+ <tbody class="divide-y divide-[var(--esc-panel-border)]">
168
+ <tr v-for="record in records" :key="record.id" class="hover:bg-[var(--esc-panel-hover)]">
169
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-muted)]">{{ record.id }}</td>
170
+ <td v-for="field in fields" :key="field.name" class="px-4 py-3">
171
+ <!-- Edit mode -->
172
+ <template v-if="editingId === record.id">
173
+ <input
174
+ v-model="editForm.data[field.name]"
175
+ :type="field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'"
176
+ class="w-full rounded border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-2 py-1 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none"
177
+ />
178
+ </template>
179
+ <!-- View mode -->
180
+ <template v-else>
181
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">
182
+ {{ record.data?.[field.name] || '---' }}
183
+ </span>
184
+ </template>
185
+ </td>
186
+ <td class="px-4 py-3 text-right">
187
+ <template v-if="editingId === record.id">
188
+ <button
189
+ class="mr-2 text-sm text-emerald-400 hover:text-emerald-300"
190
+ @click="saveEdit(record)"
191
+ >
192
+ Save
193
+ </button>
194
+ <button
195
+ class="text-sm text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
196
+ @click="cancelEdit"
197
+ >
198
+ Cancel
199
+ </button>
200
+ </template>
201
+ <template v-else>
202
+ <button
203
+ class="mr-2 text-sm text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
204
+ @click="startEdit(record)"
205
+ >
206
+ Edit
207
+ </button>
208
+ <button
209
+ class="text-sm text-red-500/60 hover:text-red-400"
210
+ @click="deleteRecord(record)"
211
+ >
212
+ Delete
213
+ </button>
214
+ </template>
215
+ </td>
216
+ </tr>
217
+ </tbody>
218
+ </table>
219
+ </div>
220
+
221
+ <div
222
+ v-else-if="!showAddForm"
223
+ class="rounded-xl border border-dashed border-[var(--esc-panel-border)] px-6 py-12 text-center"
224
+ >
225
+ <p class="text-sm text-[var(--esc-panel-text-muted)]">No records yet.</p>
226
+ </div>
227
+ </EscalatedLayout>
228
+ </template>
@@ -1,51 +1,75 @@
1
- <script setup>
2
- import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
- import { useForm } from '@inertiajs/vue3';
4
-
5
- const props = defineProps({ department: { type: Object, default: null } });
6
-
7
- const form = useForm({
8
- name: props.department?.name || '',
9
- slug: props.department?.slug || '',
10
- description: props.department?.description || '',
11
- is_active: props.department?.is_active ?? true,
12
- });
13
-
14
- function submit() {
15
- if (props.department) {
16
- form.put(route('escalated.admin.departments.update', props.department.id));
17
- } else {
18
- form.post(route('escalated.admin.departments.store'));
19
- }
20
- }
21
- </script>
22
-
23
- <template>
24
- <EscalatedLayout :title="department ? 'Edit Department' : 'New Department'">
25
- <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">
26
- <div>
27
- <label class="block text-sm font-medium text-neutral-300">Name</label>
28
- <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" />
29
- <div v-if="form.errors.name" class="mt-1 text-sm text-rose-400">{{ form.errors.name }}</div>
30
- </div>
31
- <div>
32
- <label class="block text-sm font-medium text-neutral-300">Slug</label>
33
- <input v-model="form.slug" type="text" placeholder="Auto-generated if empty" class="mt-1 w-full rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 placeholder-gray-500 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
34
- </div>
35
- <div>
36
- <label class="block text-sm font-medium text-neutral-300">Description</label>
37
- <textarea v-model="form.description" rows="3" 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>
38
- </div>
39
- <label class="flex items-center gap-2">
40
- <input v-model="form.is_active" type="checkbox" class="rounded border-white/20 bg-neutral-900 text-white focus:ring-white/10" />
41
- <span class="text-sm text-neutral-300">Active</span>
42
- </label>
43
- <div class="flex justify-end">
44
- <button type="submit" :disabled="form.processing"
45
- 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">
46
- {{ department ? 'Update' : 'Create' }}
47
- </button>
48
- </div>
49
- </form>
50
- </EscalatedLayout>
51
- </template>
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { useForm } from '@inertiajs/vue3';
4
+
5
+ const props = defineProps({ department: { type: Object, default: null } });
6
+
7
+ const form = useForm({
8
+ name: props.department?.name || '',
9
+ slug: props.department?.slug || '',
10
+ description: props.department?.description || '',
11
+ is_active: props.department?.is_active ?? true,
12
+ });
13
+
14
+ function submit() {
15
+ if (props.department) {
16
+ form.put(route('escalated.admin.departments.update', props.department.id));
17
+ } else {
18
+ form.post(route('escalated.admin.departments.store'));
19
+ }
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <EscalatedLayout :title="department ? 'Edit Department' : 'New Department'">
25
+ <form
26
+ class="mx-auto max-w-lg space-y-5 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
27
+ @submit.prevent="submit"
28
+ >
29
+ <div>
30
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Name</label>
31
+ <input
32
+ v-model="form.name"
33
+ type="text"
34
+ required
35
+ 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)]"
36
+ />
37
+ <div v-if="form.errors.name" class="mt-1 text-sm text-rose-400">{{ form.errors.name }}</div>
38
+ </div>
39
+ <div>
40
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Slug</label>
41
+ <input
42
+ v-model="form.slug"
43
+ type="text"
44
+ placeholder="Auto-generated if empty"
45
+ 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)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
46
+ />
47
+ </div>
48
+ <div>
49
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Description</label>
50
+ <textarea
51
+ v-model="form.description"
52
+ rows="3"
53
+ 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)]"
54
+ ></textarea>
55
+ </div>
56
+ <label class="flex items-center gap-2">
57
+ <input
58
+ v-model="form.is_active"
59
+ type="checkbox"
60
+ 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)]"
61
+ />
62
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">Active</span>
63
+ </label>
64
+ <div class="flex justify-end">
65
+ <button
66
+ type="submit"
67
+ :disabled="form.processing"
68
+ 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"
69
+ >
70
+ {{ department ? 'Update' : 'Create' }}
71
+ </button>
72
+ </div>
73
+ </form>
74
+ </EscalatedLayout>
75
+ </template>
@@ -1,57 +1,109 @@
1
- <script setup>
2
- import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
- import { Link, router } from '@inertiajs/vue3';
4
-
5
- defineProps({ departments: Array });
6
-
7
- function destroy(id) {
8
- if (confirm('Delete this department?')) {
9
- router.delete(route('escalated.admin.departments.destroy', id));
10
- }
11
- }
12
- </script>
13
-
14
- <template>
15
- <EscalatedLayout title="Departments">
16
- <div class="mb-4 flex justify-end">
17
- <Link :href="route('escalated.admin.departments.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 Department
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">Tickets</th>
28
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Agents</th>
29
- <th class="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Active</th>
30
- <th class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-neutral-500">Actions</th>
31
- </tr>
32
- </thead>
33
- <tbody class="divide-y divide-white/[0.04]">
34
- <tr v-for="dept in departments" :key="dept.id" class="transition-colors hover:bg-white/[0.03]">
35
- <td class="px-4 py-3 text-sm font-medium text-neutral-200">{{ dept.name }}</td>
36
- <td class="px-4 py-3 text-sm text-neutral-400">{{ dept.tickets_count }}</td>
37
- <td class="px-4 py-3 text-sm text-neutral-400">{{ dept.agents_count }}</td>
38
- <td class="px-4 py-3 text-sm">
39
- <span :class="dept.is_active ? 'text-emerald-400' : 'text-neutral-500'">{{ dept.is_active ? 'Yes' : 'No' }}</span>
40
- </td>
41
- <td class="px-4 py-3 text-right text-sm">
42
- <Link :href="route('escalated.admin.departments.edit', dept.id)" class="text-neutral-300 hover:text-white">Edit</Link>
43
- <button @click="destroy(dept.id)" class="ml-3 text-rose-400 hover:text-rose-300">Delete</button>
44
- </td>
45
- </tr>
46
- <tr v-if="!departments?.length">
47
- <td colspan="5" class="px-4 py-12 text-center">
48
- <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="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" /></svg>
49
- <p class="text-sm text-neutral-500">No departments yet</p>
50
- <p class="mt-1 text-xs text-neutral-600">Create your first department to organize tickets</p>
51
- </td>
52
- </tr>
53
- </tbody>
54
- </table>
55
- </div>
56
- </EscalatedLayout>
57
- </template>
1
+ <script setup>
2
+ import EscalatedLayout from '../../../components/EscalatedLayout.vue';
3
+ import { Link, router } from '@inertiajs/vue3';
4
+
5
+ defineProps({ departments: Array });
6
+
7
+ function destroy(id) {
8
+ if (confirm('Delete this department?')) {
9
+ router.delete(route('escalated.admin.departments.destroy', id));
10
+ }
11
+ }
12
+ </script>
13
+
14
+ <template>
15
+ <EscalatedLayout title="Departments">
16
+ <div class="mb-4 flex justify-end">
17
+ <Link
18
+ :href="route('escalated.admin.departments.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 Department
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
+ Tickets
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
+ Agents
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
+ Active
47
+ </th>
48
+ <th
49
+ class="px-4 py-3 text-right text-[11px] font-semibold uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
50
+ >
51
+ Actions
52
+ </th>
53
+ </tr>
54
+ </thead>
55
+ <tbody class="divide-y divide-[var(--esc-panel-border)]">
56
+ <tr
57
+ v-for="dept in departments"
58
+ :key="dept.id"
59
+ class="transition-colors hover:bg-[var(--esc-panel-hover)]"
60
+ >
61
+ <td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
62
+ {{ dept.name }}
63
+ </td>
64
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
65
+ {{ dept.tickets_count }}
66
+ </td>
67
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">{{ dept.agents_count }}</td>
68
+ <td class="px-4 py-3 text-sm">
69
+ <span :class="dept.is_active ? 'text-emerald-400' : 'text-[var(--esc-panel-text-muted)]'">{{
70
+ dept.is_active ? 'Yes' : 'No'
71
+ }}</span>
72
+ </td>
73
+ <td class="px-4 py-3 text-right text-sm">
74
+ <Link
75
+ :href="route('escalated.admin.departments.edit', dept.id)"
76
+ class="text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
77
+ >Edit</Link
78
+ >
79
+ <button class="ml-3 text-rose-400 hover:text-rose-300" @click="destroy(dept.id)">
80
+ Delete
81
+ </button>
82
+ </td>
83
+ </tr>
84
+ <tr v-if="!departments?.length">
85
+ <td colspan="5" class="px-4 py-12 text-center">
86
+ <svg
87
+ class="mx-auto mb-3 h-8 w-8 text-[var(--esc-panel-text-muted)]"
88
+ fill="none"
89
+ stroke="currentColor"
90
+ stroke-width="1.5"
91
+ viewBox="0 0 24 24"
92
+ >
93
+ <path
94
+ stroke-linecap="round"
95
+ stroke-linejoin="round"
96
+ d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z"
97
+ />
98
+ </svg>
99
+ <p class="text-sm text-[var(--esc-panel-text-muted)]">No departments yet</p>
100
+ <p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">
101
+ Create your first department to organize tickets
102
+ </p>
103
+ </td>
104
+ </tr>
105
+ </tbody>
106
+ </table>
107
+ </div>
108
+ </EscalatedLayout>
109
+ </template>