@escalated-dev/escalated 0.3.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/package.json +9 -2
- package/src/components/BulkActionBar.vue +97 -0
- package/src/components/EscalatedLayout.vue +244 -219
- package/src/components/FollowButton.vue +63 -0
- package/src/components/KeyboardShortcutHelp.vue +123 -0
- package/src/components/MacroDropdown.vue +90 -0
- package/src/components/PinnedNotes.vue +87 -0
- package/src/components/PluginSlot.vue +62 -0
- package/src/components/PresenceIndicator.vue +103 -0
- package/src/components/QuickFilters.vue +49 -0
- package/src/components/ReplyThread.vue +60 -38
- package/src/components/SatisfactionRating.vue +128 -0
- package/src/components/TicketFilters.vue +69 -58
- package/src/components/TicketList.vue +193 -94
- package/src/components/TicketSidebar.vue +99 -77
- package/src/composables/useKeyboardShortcuts.js +46 -0
- package/src/composables/usePluginExtensions.js +67 -0
- package/src/defineEscalatedPlugin.js +50 -0
- package/src/index.js +16 -0
- package/src/pages/Admin/ApiTokens/Index.vue +217 -0
- package/src/pages/Admin/Macros/Index.vue +287 -0
- package/src/pages/Admin/Plugins/Index.vue +197 -0
- package/src/pages/Admin/Plugins.vue +129 -0
- package/src/pages/Admin/Reports.vue +101 -66
- package/src/pages/Admin/Tickets/Index.vue +39 -22
- package/src/pages/Admin/Tickets/Show.vue +156 -109
- package/src/pages/Agent/Dashboard.vue +186 -63
- package/src/pages/Agent/TicketIndex.vue +38 -21
- package/src/pages/Agent/TicketShow.vue +155 -108
- package/src/pages/Customer/Show.vue +63 -55
- package/src/pages/Guest/Show.vue +93 -86
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Pick your framework:
|
|
|
18
18
|
| **Laravel** | [escalated-dev/escalated-laravel](https://github.com/escalated-dev/escalated-laravel) | `composer require escalated-dev/escalated-laravel` |
|
|
19
19
|
| **Rails** | [escalated-dev/escalated-rails](https://github.com/escalated-dev/escalated-rails) | `gem "escalated"` |
|
|
20
20
|
| **Django** | [escalated-dev/escalated-django](https://github.com/escalated-dev/escalated-django) | `pip install escalated-django` |
|
|
21
|
+
| **AdonisJS** | [escalated-dev/escalated-adonis](https://github.com/escalated-dev/escalated-adonis) | `npm install @escalated-dev/escalated-adonis` |
|
|
21
22
|
|
|
22
23
|
Each backend repo has full setup instructions — install command, migrations, config, and frontend integration.
|
|
23
24
|
|
|
@@ -112,9 +113,17 @@ import BaseLayout from '@/layouts/BaseLayout.vue'
|
|
|
112
113
|
app.use(EscalatedPlugin, { layout: BaseLayout })
|
|
113
114
|
```
|
|
114
115
|
|
|
116
|
+
**AdonisJS** (Inertia + Vue 3):
|
|
117
|
+
```js
|
|
118
|
+
import { EscalatedPlugin } from '@escalated-dev/escalated'
|
|
119
|
+
import AppLayout from '@/layouts/AppLayout.vue'
|
|
120
|
+
|
|
121
|
+
app.use(EscalatedPlugin, { layout: AppLayout })
|
|
122
|
+
```
|
|
123
|
+
|
|
115
124
|
## What's in This Repo
|
|
116
125
|
|
|
117
|
-
All the Vue 3 + Inertia.js components that power the Escalated UI. These are identical across Laravel, Rails, and
|
|
126
|
+
All the Vue 3 + Inertia.js components that power the Escalated UI. These are identical across Laravel, Rails, Django, and AdonisJS — the backend framework renders them via Inertia.
|
|
118
127
|
|
|
119
128
|
### Pages
|
|
120
129
|
|
|
@@ -157,6 +166,20 @@ Reusable building blocks used across the pages above.
|
|
|
157
166
|
| `AttachmentList` | File attachment display with download links |
|
|
158
167
|
| `StatsCard` | Metric card with label, value, and trend |
|
|
159
168
|
| `EscalatedLayout` | Top-level layout with navigation (supports host layout injection) |
|
|
169
|
+
| `BulkActionBar` | Toolbar for batch operations on selected tickets |
|
|
170
|
+
| `QuickFilters` | One-click filter chips (My Tickets, Unassigned, Urgent, SLA Breaching) |
|
|
171
|
+
| `MacroDropdown` | Dropdown to apply multi-step macros to a ticket |
|
|
172
|
+
| `FollowButton` | Toggle button to follow/unfollow a ticket |
|
|
173
|
+
| `SatisfactionRating` | 1-5 star CSAT rating input with optional comment |
|
|
174
|
+
| `KeyboardShortcutHelp` | Modal overlay showing all available keyboard shortcuts |
|
|
175
|
+
| `PinnedNotes` | Display pinned internal notes at the top of the thread |
|
|
176
|
+
| `PresenceIndicator` | Real-time indicator showing who is viewing a ticket |
|
|
177
|
+
|
|
178
|
+
### Composables
|
|
179
|
+
|
|
180
|
+
| Composable | Description |
|
|
181
|
+
|------------|-------------|
|
|
182
|
+
| `useKeyboardShortcuts` | Registers and manages keyboard shortcuts for ticket actions |
|
|
160
183
|
|
|
161
184
|
### Plugin
|
|
162
185
|
|
|
@@ -185,6 +208,17 @@ import CustomerIndex from '@escalated-dev/escalated/pages/Customer/Index.vue'
|
|
|
185
208
|
|
|
186
209
|
Peer dependencies: `vue` ^3.3.0, `@inertiajs/vue3` ^1.0.0 || ^2.0.0
|
|
187
210
|
|
|
211
|
+
## Ecosystem
|
|
212
|
+
|
|
213
|
+
This is the shared frontend for the Escalated support ticket system. Backend packages available for every major framework:
|
|
214
|
+
|
|
215
|
+
- **[Escalated for Laravel](https://github.com/escalated-dev/escalated-laravel)** — Laravel Composer package
|
|
216
|
+
- **[Escalated for Rails](https://github.com/escalated-dev/escalated-rails)** — Ruby on Rails engine
|
|
217
|
+
- **[Escalated for Django](https://github.com/escalated-dev/escalated-django)** — Django reusable app
|
|
218
|
+
- **[Escalated for AdonisJS](https://github.com/escalated-dev/escalated-adonis)** — AdonisJS v6 package
|
|
219
|
+
- **[Escalated for Filament](https://github.com/escalated-dev/escalated-filament)** — Filament v3 admin panel plugin
|
|
220
|
+
- **[Shared Frontend](https://github.com/escalated-dev/escalated)** — Vue 3 + Inertia.js UI components (you are here)
|
|
221
|
+
|
|
188
222
|
## License
|
|
189
223
|
|
|
190
224
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escalated-dev/escalated",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Vue 3 + Inertia.js UI components for Escalated — the embeddable support ticket system",
|
|
5
5
|
"author": "Escalated Dev <hello@escalated.dev>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,8 +30,15 @@
|
|
|
30
30
|
"vue": "^3.3.0",
|
|
31
31
|
"@inertiajs/vue3": "^1.0.0 || ^2.0.0"
|
|
32
32
|
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "vitest run"
|
|
35
|
+
},
|
|
33
36
|
"devDependencies": {
|
|
34
37
|
"vue": "^3.5.0",
|
|
35
|
-
"@inertiajs/vue3": "^2.0.0"
|
|
38
|
+
"@inertiajs/vue3": "^2.0.0",
|
|
39
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
40
|
+
"@vue/test-utils": "^2.4.0",
|
|
41
|
+
"happy-dom": "^15.0.0",
|
|
42
|
+
"vitest": "^2.0.0"
|
|
36
43
|
}
|
|
37
44
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, inject } from 'vue';
|
|
3
|
+
import { router } from '@inertiajs/vue3';
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
selectedIds: { type: Array, required: true },
|
|
7
|
+
bulkRoute: { type: String, required: true },
|
|
8
|
+
agents: { type: Array, default: () => [] },
|
|
9
|
+
departments: { type: Array, default: () => [] },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits(['clear']);
|
|
13
|
+
const escDark = inject('esc-dark', computed(() => false));
|
|
14
|
+
const processing = ref(false);
|
|
15
|
+
|
|
16
|
+
function performAction(action, value) {
|
|
17
|
+
if (!props.selectedIds.length || processing.value) return;
|
|
18
|
+
processing.value = true;
|
|
19
|
+
router.post(props.bulkRoute, {
|
|
20
|
+
ticket_ids: props.selectedIds,
|
|
21
|
+
action,
|
|
22
|
+
value,
|
|
23
|
+
}, {
|
|
24
|
+
preserveScroll: true,
|
|
25
|
+
onSuccess: () => emit('clear'),
|
|
26
|
+
onFinish: () => { processing.value = false; },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const statusOptions = [
|
|
31
|
+
{ value: 'in_progress', label: 'In Progress' },
|
|
32
|
+
{ value: 'waiting_on_customer', label: 'Waiting on Customer' },
|
|
33
|
+
{ value: 'resolved', label: 'Resolved' },
|
|
34
|
+
{ value: 'closed', label: 'Closed' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const priorityOptions = [
|
|
38
|
+
{ value: 'low', label: 'Low' },
|
|
39
|
+
{ value: 'medium', label: 'Medium' },
|
|
40
|
+
{ value: 'high', label: 'High' },
|
|
41
|
+
{ value: 'urgent', label: 'Urgent' },
|
|
42
|
+
{ value: 'critical', label: 'Critical' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const selectClass = computed(() => escDark.value
|
|
46
|
+
? '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'
|
|
47
|
+
: 'rounded-md border border-gray-300 bg-white px-2 py-1.5 text-sm focus:border-blue-500 focus:outline-none'
|
|
48
|
+
);
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<Transition enter-active-class="transition-all duration-200 ease-out" enter-from-class="translate-y-full opacity-0" enter-to-class="translate-y-0 opacity-100"
|
|
53
|
+
leave-active-class="transition-all duration-150 ease-in" leave-from-class="translate-y-0 opacity-100" leave-to-class="translate-y-full opacity-0">
|
|
54
|
+
<div v-if="selectedIds.length > 0"
|
|
55
|
+
:class="['fixed bottom-0 left-0 right-0 z-50 border-t px-6 py-3',
|
|
56
|
+
escDark ? 'border-white/[0.06] bg-neutral-950/95 backdrop-blur-xl' : 'border-gray-200 bg-white/95 backdrop-blur-xl shadow-lg']">
|
|
57
|
+
<div class="mx-auto flex max-w-7xl items-center gap-4">
|
|
58
|
+
<span :class="['text-sm font-semibold', escDark ? 'text-white' : 'text-gray-900']">
|
|
59
|
+
{{ selectedIds.length }} ticket{{ selectedIds.length !== 1 ? 's' : '' }} selected
|
|
60
|
+
</span>
|
|
61
|
+
|
|
62
|
+
<select @change="performAction('status', $event.target.value); $event.target.value = ''" :class="selectClass" :disabled="processing">
|
|
63
|
+
<option value="">Status...</option>
|
|
64
|
+
<option v-for="o in statusOptions" :key="o.value" :value="o.value">{{ o.label }}</option>
|
|
65
|
+
</select>
|
|
66
|
+
|
|
67
|
+
<select @change="performAction('priority', $event.target.value); $event.target.value = ''" :class="selectClass" :disabled="processing">
|
|
68
|
+
<option value="">Priority...</option>
|
|
69
|
+
<option v-for="o in priorityOptions" :key="o.value" :value="o.value">{{ o.label }}</option>
|
|
70
|
+
</select>
|
|
71
|
+
|
|
72
|
+
<select v-if="agents.length" @change="performAction('assign', $event.target.value); $event.target.value = ''" :class="selectClass" :disabled="processing">
|
|
73
|
+
<option value="">Assign to...</option>
|
|
74
|
+
<option v-for="a in agents" :key="a.id" :value="a.id">{{ a.name }}</option>
|
|
75
|
+
</select>
|
|
76
|
+
|
|
77
|
+
<select v-if="departments.length" @change="performAction('department', $event.target.value); $event.target.value = ''" :class="selectClass" :disabled="processing">
|
|
78
|
+
<option value="">Department...</option>
|
|
79
|
+
<option v-for="d in departments" :key="d.id" :value="d.id">{{ d.name }}</option>
|
|
80
|
+
</select>
|
|
81
|
+
|
|
82
|
+
<button @click="performAction('delete', null)"
|
|
83
|
+
:class="['rounded-lg px-3 py-1.5 text-sm font-medium transition-colors',
|
|
84
|
+
escDark ? 'bg-rose-500/15 text-rose-400 ring-1 ring-rose-500/20 hover:bg-rose-500/25' : 'bg-red-50 text-red-600 ring-1 ring-red-200 hover:bg-red-100']"
|
|
85
|
+
:disabled="processing">
|
|
86
|
+
Delete
|
|
87
|
+
</button>
|
|
88
|
+
|
|
89
|
+
<button @click="emit('clear')"
|
|
90
|
+
:class="['ml-auto rounded-lg px-3 py-1.5 text-sm font-medium transition-colors',
|
|
91
|
+
escDark ? 'text-neutral-400 hover:bg-white/[0.04] hover:text-neutral-200' : 'text-gray-500 hover:bg-gray-100 hover:text-gray-700']">
|
|
92
|
+
Deselect All
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</Transition>
|
|
97
|
+
</template>
|