@escalated-dev/escalated 0.5.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.
- package/README.md +102 -0
- package/package.json +37 -6
- package/src/components/ActivityTimeline.stories.js +55 -0
- package/src/components/ActivityTimeline.vue +83 -66
- package/src/components/AdminDashboard.stories.js +1095 -0
- package/src/components/AgentLoadIndicator.stories.js +37 -0
- package/src/components/AgentLoadIndicator.vue +49 -0
- package/src/components/AgentPerformanceCard.vue +44 -0
- package/src/components/ArticleEditor.vue +82 -0
- package/src/components/AssigneeSelect.vue +37 -23
- package/src/components/AttachmentList.vue +53 -37
- package/src/components/AuditLogEntry.vue +95 -0
- package/src/components/BulkActionBar.vue +99 -30
- package/src/components/CategoryTree.vue +89 -0
- package/src/components/ChartWidget.vue +46 -0
- package/src/components/CollisionWarning.stories.js +30 -0
- package/src/components/CollisionWarning.vue +68 -0
- package/src/components/ConditionalFieldRules.vue +135 -0
- package/src/components/ContextPanel.vue +66 -0
- package/src/components/ContextPanelSection.vue +65 -0
- package/src/components/CustomFieldRenderer.vue +159 -0
- package/src/components/EscalatedLayout.vue +400 -69
- package/src/components/FileDropzone.vue +57 -41
- package/src/components/FollowButton.stories.js +30 -0
- package/src/components/FollowButton.vue +53 -28
- package/src/components/KeyboardShortcutHelp.vue +129 -39
- package/src/components/KnowledgePanel.vue +122 -0
- package/src/components/KpiCard.stories.js +32 -0
- package/src/components/KpiCard.vue +88 -0
- package/src/components/MacroDropdown.vue +95 -35
- package/src/components/PermissionMatrix.vue +107 -0
- package/src/components/PinnedNotes.vue +75 -33
- package/src/components/PresenceIndicator.vue +93 -27
- package/src/components/PriorityBadge.stories.js +33 -0
- package/src/components/PriorityBadge.vue +46 -39
- package/src/components/QuickFilters.vue +65 -49
- package/src/components/ReplyComposer.vue +273 -159
- package/src/components/ReplyThread.vue +78 -23
- package/src/components/SatisfactionRating.vue +98 -46
- package/src/components/ScheduleEditor.vue +104 -0
- package/src/components/SideConversation.vue +165 -0
- package/src/components/SideConversationList.vue +147 -0
- package/src/components/SkillTagManager.vue +79 -0
- package/src/components/SlaComplianceChart.vue +53 -0
- package/src/components/SlaTimer.stories.js +35 -0
- package/src/components/SlaTimer.vue +62 -49
- package/src/components/StatsCard.stories.js +24 -0
- package/src/components/StatsCard.vue +58 -46
- package/src/components/StatusBadge.stories.js +53 -0
- package/src/components/StatusBadge.vue +78 -46
- package/src/components/TagSelect.vue +84 -53
- package/src/components/TicketFilters.vue +225 -69
- package/src/components/TicketLinkPanel.vue +193 -0
- package/src/components/TicketList.vue +594 -193
- package/src/components/TicketMergeDialog.vue +157 -0
- package/src/components/TicketSidebar.vue +167 -99
- package/src/components/TicketTypeSelector.vue +21 -0
- package/src/components/TotpInput.stories.js +9 -0
- package/src/components/TotpInput.vue +81 -0
- package/src/components/TwoFactorSetup.vue +113 -0
- package/src/composables/useDebouncedSearch.js +38 -0
- package/src/composables/useI18n.js +80 -0
- package/src/composables/usePluginExtensions.js +95 -3
- package/src/composables/usePluginHooks.js +65 -0
- package/src/defineEscalatedPlugin.js +34 -8
- package/src/index.js +72 -35
- package/src/locales/de.json +442 -0
- package/src/locales/en.json +467 -0
- package/src/locales/es.json +442 -0
- package/src/locales/fr.json +442 -0
- package/src/locales/index.js +6 -0
- package/src/pages/Admin/ApiTokens/Index.vue +231 -67
- package/src/pages/Admin/AuditLog/Index.vue +154 -0
- package/src/pages/Admin/Automations/Form.vue +184 -0
- package/src/pages/Admin/Automations/Index.vue +129 -0
- package/src/pages/Admin/BusinessHours/Form.vue +172 -0
- package/src/pages/Admin/BusinessHours/Index.vue +113 -0
- package/src/pages/Admin/CannedResponses/Index.vue +127 -70
- package/src/pages/Admin/Capacity/Index.vue +134 -0
- package/src/pages/Admin/CustomFields/Form.vue +195 -0
- package/src/pages/Admin/CustomFields/Index.vue +134 -0
- package/src/pages/Admin/CustomObjects/Form.vue +202 -0
- package/src/pages/Admin/CustomObjects/Index.vue +127 -0
- package/src/pages/Admin/CustomObjects/Records.vue +228 -0
- package/src/pages/Admin/Departments/Form.vue +75 -51
- package/src/pages/Admin/Departments/Index.vue +109 -57
- package/src/pages/Admin/EscalationRules/Form.vue +157 -96
- package/src/pages/Admin/EscalationRules/Index.vue +178 -57
- package/src/pages/Admin/Import/Connect.vue +96 -0
- package/src/pages/Admin/Import/Index.vue +102 -0
- package/src/pages/Admin/Import/Mapping.vue +110 -0
- package/src/pages/Admin/Import/Progress.vue +153 -0
- package/src/pages/Admin/Import/Review.vue +76 -0
- package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -0
- package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -0
- package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -0
- package/src/pages/Admin/Macros/Index.vue +250 -89
- package/src/pages/Admin/Plugins/Index.vue +205 -75
- package/src/pages/Admin/Reports/AgentMetrics.vue +115 -0
- package/src/pages/Admin/Reports/CsatReport.vue +127 -0
- package/src/pages/Admin/Reports/Dashboard.vue +211 -0
- package/src/pages/Admin/Reports/SlaReport.vue +166 -0
- package/src/pages/Admin/Reports.vue +139 -34
- package/src/pages/Admin/Roles/Form.vue +71 -0
- package/src/pages/Admin/Roles/Index.vue +122 -0
- package/src/pages/Admin/Settings/CsatSettings.vue +165 -0
- package/src/pages/Admin/Settings/DataRetention.vue +182 -0
- package/src/pages/Admin/Settings/EmailSettings.vue +243 -0
- package/src/pages/Admin/Settings/Sandbox.vue +135 -0
- package/src/pages/Admin/Settings/SsoSettings.vue +218 -0
- package/src/pages/Admin/Settings/TwoFactor.vue +110 -0
- package/src/pages/Admin/Settings.vue +468 -260
- package/src/pages/Admin/Skills/Form.vue +53 -0
- package/src/pages/Admin/Skills/Index.vue +94 -0
- package/src/pages/Admin/SlaPolicies/Form.vue +118 -75
- package/src/pages/Admin/SlaPolicies/Index.vue +120 -59
- package/src/pages/Admin/Statuses/Form.vue +114 -0
- package/src/pages/Admin/Statuses/Index.vue +153 -0
- package/src/pages/Admin/Tags/Index.vue +191 -105
- package/src/pages/Admin/Tickets/Index.vue +91 -39
- package/src/pages/Admin/Tickets/Show.vue +101 -37
- package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -0
- package/src/pages/Admin/Webhooks/Form.vue +110 -0
- package/src/pages/Admin/Webhooks/Index.vue +136 -0
- package/src/pages/Agent/Dashboard.vue +311 -186
- package/src/pages/Agent/TicketIndex.vue +88 -38
- package/src/pages/Agent/TicketShow.vue +258 -51
- package/src/pages/Customer/Create.vue +36 -9
- package/src/pages/Customer/Index.vue +31 -24
- package/src/pages/Customer/KnowledgeBase/Article.vue +127 -0
- package/src/pages/Customer/KnowledgeBase/Index.vue +151 -0
- package/src/plugin.js +86 -1
- package/src/utils/constants.js +30 -0
- package/src/utils/formatting.js +35 -0
- package/src/utils/sanitizeHtml.js +64 -0
- package/src/pages/Admin/Plugins.vue +0 -129
package/README.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
Escalated
|
|
4
4
|
</h1>
|
|
5
5
|
|
|
6
|
+
[](https://github.com/escalated-dev/escalated/actions/workflows/run-tests.yml)
|
|
7
|
+
[](https://www.npmjs.com/package/@escalated-dev/escalated)
|
|
8
|
+
[](https://vuejs.org/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
6
11
|
Escalated is an embeddable support ticket system with SLA tracking, escalation rules, agent workflows, and a customer portal. This repo contains all the shared frontend assets (Vue 3 + Inertia.js) used across every supported backend framework.
|
|
7
12
|
|
|
8
13
|
👉 **Learn more, view demos, and compare Cloud vs Self-Hosted options at** **[https://escalated.dev](https://escalated.dev)**
|
|
@@ -19,6 +24,10 @@ Pick your framework:
|
|
|
19
24
|
| **Rails** | [escalated-dev/escalated-rails](https://github.com/escalated-dev/escalated-rails) | `gem "escalated"` |
|
|
20
25
|
| **Django** | [escalated-dev/escalated-django](https://github.com/escalated-dev/escalated-django) | `pip install escalated-django` |
|
|
21
26
|
| **AdonisJS** | [escalated-dev/escalated-adonis](https://github.com/escalated-dev/escalated-adonis) | `npm install @escalated-dev/escalated-adonis` |
|
|
27
|
+
| **WordPress** | [escalated-dev/escalated-wordpress](https://github.com/escalated-dev/escalated-wordpress) | [Download escalated.zip](https://github.com/escalated-dev/escalated-wordpress/releases/latest) |
|
|
28
|
+
| **Filament** | [escalated-dev/escalated-filament](https://github.com/escalated-dev/escalated-filament) | `composer require escalated-dev/escalated-filament` |
|
|
29
|
+
| **React Native** | [escalated-dev/escalated-react-native](https://github.com/escalated-dev/escalated-react-native) | `npm install @escalated-dev/escalated-react-native` |
|
|
30
|
+
| **Flutter** | [escalated-dev/escalated-flutter](https://github.com/escalated-dev/escalated-flutter) | See [pubspec.yaml setup](https://github.com/escalated-dev/escalated-flutter#quick-start) |
|
|
22
31
|
|
|
23
32
|
Each backend repo has full setup instructions — install command, migrations, config, and frontend integration.
|
|
24
33
|
|
|
@@ -125,6 +134,35 @@ app.use(EscalatedPlugin, { layout: AppLayout })
|
|
|
125
134
|
|
|
126
135
|
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.
|
|
127
136
|
|
|
137
|
+
## 📸 Screenshots
|
|
138
|
+
|
|
139
|
+
> Screenshots are auto-generated from Storybook via the [component-screenshots](.github/workflows/screenshots.yml) workflow.
|
|
140
|
+
|
|
141
|
+
<p align="center">
|
|
142
|
+
<strong>Admin Panel (Dark)</strong><br/>
|
|
143
|
+
<img src="docs/assets/escalated_admin_1.png" alt="Escalated Admin Panel — dark mode with sidebar navigation, KPI cards, stats, and ticket list" width="800" />
|
|
144
|
+
</p>
|
|
145
|
+
|
|
146
|
+
<p align="center">
|
|
147
|
+
<strong>Admin Panel (Light)</strong><br/>
|
|
148
|
+
<img src="docs/assets/escalated_admin_3.png" alt="Escalated Admin Panel — light mode with sidebar navigation, KPI cards, stats, and ticket list" width="800" />
|
|
149
|
+
</p>
|
|
150
|
+
|
|
151
|
+
<p align="center">
|
|
152
|
+
<strong>Ticket Queue</strong><br/>
|
|
153
|
+
<img src="docs/assets/escalated_ticket_list.png" alt="Escalated Ticket Queue — agent ticket list with filters, search, bulk actions, and SLA indicators" width="800" />
|
|
154
|
+
</p>
|
|
155
|
+
|
|
156
|
+
<p align="center">
|
|
157
|
+
<strong>Agent Panel</strong><br/>
|
|
158
|
+
<img src="docs/assets/escalated_admin_2.png" alt="Escalated Agent Panel — top navigation, stats, and assigned ticket queue" width="800" />
|
|
159
|
+
</p>
|
|
160
|
+
|
|
161
|
+
<p align="center">
|
|
162
|
+
<strong>Ticket Detail View</strong><br/>
|
|
163
|
+
<img src="docs/assets/escalated_ticket_view.png" alt="Escalated Ticket Detail View — conversation thread, reply composer, and ticket sidebar with SLA timer" width="800" />
|
|
164
|
+
</p>
|
|
165
|
+
|
|
128
166
|
### Pages
|
|
129
167
|
|
|
130
168
|
**Customer Portal** — Self-service ticket management
|
|
@@ -187,6 +225,70 @@ Reusable building blocks used across the pages above.
|
|
|
187
225
|
|--------|-------------|
|
|
188
226
|
| `EscalatedPlugin` | Vue plugin for layout injection and CSS theming |
|
|
189
227
|
|
|
228
|
+
## Plugin Development
|
|
229
|
+
|
|
230
|
+
Escalated supports framework-agnostic plugins built with the [Plugin SDK](https://github.com/escalated-dev/escalated-plugin-sdk). Plugins are written once in TypeScript and work across all Escalated backends.
|
|
231
|
+
|
|
232
|
+
### How the Frontend Plugin System Works
|
|
233
|
+
|
|
234
|
+
The frontend uses `defineEscalatedPlugin()` to register Vue components — custom admin pages, ticket sidebar widgets, or dashboard panels — that are mounted automatically when the plugin is active.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { defineEscalatedPlugin } from '@escalated-dev/escalated'
|
|
238
|
+
import MySettingsPage from './MySettingsPage.vue'
|
|
239
|
+
|
|
240
|
+
export default defineEscalatedPlugin({
|
|
241
|
+
name: 'my-plugin',
|
|
242
|
+
pages: {
|
|
243
|
+
'admin/my-plugin/settings': MySettingsPage,
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### How It Connects to the Backend
|
|
249
|
+
|
|
250
|
+
The backend uses `definePlugin()` from the [Plugin SDK](https://github.com/escalated-dev/escalated-plugin-sdk) to handle TypeScript business logic — subscribing to ticket lifecycle hooks, exposing API endpoints, and persisting data. The frontend and backend entries work together as a single npm package.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// backend entry (index.ts)
|
|
254
|
+
import { definePlugin } from '@escalated-dev/plugin-sdk'
|
|
255
|
+
|
|
256
|
+
export default definePlugin({
|
|
257
|
+
name: 'my-plugin',
|
|
258
|
+
version: '1.0.0',
|
|
259
|
+
actions: {
|
|
260
|
+
'ticket.created': async (event, ctx) => {
|
|
261
|
+
ctx.log.info('New ticket!', event)
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Quick Example: Both Entry Points
|
|
268
|
+
|
|
269
|
+
A published plugin package typically exports both:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
my-plugin/
|
|
273
|
+
index.ts ← backend: definePlugin() for TypeScript logic
|
|
274
|
+
frontend.ts ← frontend: defineEscalatedPlugin() for Vue components
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The backend framework (Laravel, Rails, Django, AdonisJS) loads `index.ts` via the [Plugin Runtime](https://github.com/escalated-dev/escalated-plugin-runtime). The Vue app imports `frontend.ts` and registers it with `app.use()`.
|
|
278
|
+
|
|
279
|
+
### Installing Plugins
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npm install @escalated-dev/plugin-slack
|
|
283
|
+
npm install @escalated-dev/plugin-jira
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Resources
|
|
287
|
+
|
|
288
|
+
- [Plugin SDK](https://github.com/escalated-dev/escalated-plugin-sdk) — TypeScript SDK for building plugins
|
|
289
|
+
- [Plugin Runtime](https://github.com/escalated-dev/escalated-plugin-runtime) — Runtime host for plugins
|
|
290
|
+
- [Plugin Development Guide](https://github.com/escalated-dev/escalated-docs) — Full documentation
|
|
291
|
+
|
|
190
292
|
## For Package Maintainers
|
|
191
293
|
|
|
192
294
|
If you're building a new backend integration, this package is available on npm:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escalated-dev/escalated",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -21,24 +21,55 @@
|
|
|
21
21
|
"exports": {
|
|
22
22
|
"./components/*": "./src/components/*",
|
|
23
23
|
"./pages/*": "./src/pages/*",
|
|
24
|
+
"./locales": "./src/locales/index.js",
|
|
25
|
+
"./locales/*": "./src/locales/*",
|
|
24
26
|
".": "./src/index.js"
|
|
25
27
|
},
|
|
26
28
|
"files": [
|
|
27
29
|
"src"
|
|
28
30
|
],
|
|
29
31
|
"peerDependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
+
"@inertiajs/vue3": "^1.0.0 || ^2.0.0",
|
|
33
|
+
"vue": "^3.3.0"
|
|
32
34
|
},
|
|
33
35
|
"scripts": {
|
|
34
|
-
"test": "vitest run"
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"lint": "eslint src/",
|
|
38
|
+
"lint:fix": "eslint src/ --fix",
|
|
39
|
+
"format": "prettier --write src/",
|
|
40
|
+
"format:check": "prettier --check src/",
|
|
41
|
+
"prepare": "husky",
|
|
42
|
+
"storybook": "storybook dev -p 6006",
|
|
43
|
+
"build-storybook": "storybook build",
|
|
44
|
+
"screenshot": "npx playwright test --config=playwright-screenshots.config.js"
|
|
45
|
+
},
|
|
46
|
+
"lint-staged": {
|
|
47
|
+
"*.{js,vue}": [
|
|
48
|
+
"eslint --fix",
|
|
49
|
+
"prettier --write"
|
|
50
|
+
]
|
|
35
51
|
},
|
|
36
52
|
"devDependencies": {
|
|
37
|
-
"
|
|
53
|
+
"@eslint/js": "^10.0.1",
|
|
38
54
|
"@inertiajs/vue3": "^2.0.0",
|
|
55
|
+
"@playwright/test": "^1.50.0",
|
|
56
|
+
"@storybook/addon-a11y": "^10.2.12",
|
|
57
|
+
"@storybook/addon-docs": "^10.2.12",
|
|
58
|
+
"@storybook/vue3-vite": "^10.2.12",
|
|
39
59
|
"@vitejs/plugin-vue": "^5.0.0",
|
|
40
60
|
"@vue/test-utils": "^2.4.0",
|
|
61
|
+
"autoprefixer": "^10.4.20",
|
|
62
|
+
"eslint": "^10.0.1",
|
|
63
|
+
"eslint-plugin-storybook": "^10.2.12",
|
|
64
|
+
"eslint-plugin-vue": "^10.8.0",
|
|
41
65
|
"happy-dom": "^15.0.0",
|
|
42
|
-
"
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"lint-staged": "^16.2.7",
|
|
68
|
+
"postcss": "^8.5.0",
|
|
69
|
+
"prettier": "^3.8.1",
|
|
70
|
+
"storybook": "^10.2.12",
|
|
71
|
+
"tailwindcss": "^3.4.0",
|
|
72
|
+
"vitest": "^2.0.0",
|
|
73
|
+
"vue": "^3.5.0"
|
|
43
74
|
}
|
|
44
75
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import ActivityTimeline from './ActivityTimeline.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/ActivityTimeline',
|
|
5
|
+
component: ActivityTimeline,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const sampleActivities = [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
type: 'status_changed',
|
|
12
|
+
causer: { name: 'Alice Chen' },
|
|
13
|
+
properties: { from: 'open', to: 'in_progress' },
|
|
14
|
+
created_at: new Date(Date.now() - 300000).toISOString(),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 2,
|
|
18
|
+
type: 'assigned',
|
|
19
|
+
causer: { name: 'Bob Smith' },
|
|
20
|
+
properties: { agent_name: 'Alice Chen' },
|
|
21
|
+
created_at: new Date(Date.now() - 600000).toISOString(),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 3,
|
|
25
|
+
type: 'priority_changed',
|
|
26
|
+
causer: { name: 'Alice Chen' },
|
|
27
|
+
properties: { from: 'medium', to: 'high' },
|
|
28
|
+
created_at: new Date(Date.now() - 1200000).toISOString(),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 4,
|
|
32
|
+
type: 'tag_added',
|
|
33
|
+
causer: { name: 'Alice Chen' },
|
|
34
|
+
properties: { tag: 'billing' },
|
|
35
|
+
created_at: new Date(Date.now() - 1800000).toISOString(),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 5,
|
|
39
|
+
type: 'replied',
|
|
40
|
+
causer: { name: 'Alice Chen' },
|
|
41
|
+
properties: {},
|
|
42
|
+
created_at: new Date(Date.now() - 2400000).toISOString(),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 6,
|
|
46
|
+
type: 'sla_breached',
|
|
47
|
+
causer: null,
|
|
48
|
+
properties: {},
|
|
49
|
+
created_at: new Date(Date.now() - 3600000).toISOString(),
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export const Default = { args: { activities: sampleActivities } };
|
|
54
|
+
export const Empty = { args: { activities: [] } };
|
|
55
|
+
export const SingleItem = { args: { activities: [sampleActivities[0]] } };
|
|
@@ -1,66 +1,83 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { computed, inject } from 'vue';
|
|
3
|
-
|
|
4
|
-
defineProps({
|
|
5
|
-
activities: { type: Array, required: true },
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const dark = inject(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (activity.type === '
|
|
40
|
-
return `${who}
|
|
41
|
-
}
|
|
42
|
-
if (activity.type === '
|
|
43
|
-
return `${who}
|
|
44
|
-
}
|
|
45
|
-
if (
|
|
46
|
-
return `${who} ${label}
|
|
47
|
-
}
|
|
48
|
-
if (activity.type === '
|
|
49
|
-
return `${who} ${label}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, inject } from 'vue';
|
|
3
|
+
|
|
4
|
+
defineProps({
|
|
5
|
+
activities: { type: Array, required: true },
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const dark = inject(
|
|
9
|
+
'esc-dark',
|
|
10
|
+
computed(() => false),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const typeLabels = {
|
|
14
|
+
status_changed: 'changed status',
|
|
15
|
+
assigned: 'assigned ticket',
|
|
16
|
+
unassigned: 'unassigned ticket',
|
|
17
|
+
priority_changed: 'changed priority',
|
|
18
|
+
tag_added: 'added tag',
|
|
19
|
+
tag_removed: 'removed tag',
|
|
20
|
+
escalated: 'escalated ticket',
|
|
21
|
+
sla_breached: 'SLA breached',
|
|
22
|
+
replied: 'replied',
|
|
23
|
+
note_added: 'added note',
|
|
24
|
+
department_changed: 'changed department',
|
|
25
|
+
reopened: 'reopened ticket',
|
|
26
|
+
resolved: 'resolved ticket',
|
|
27
|
+
closed: 'closed ticket',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function formatDate(date) {
|
|
31
|
+
return new Date(date).toLocaleString();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function describeActivity(activity) {
|
|
35
|
+
const label = typeLabels[activity.type] || activity.type;
|
|
36
|
+
const who = activity.causer?.name || 'System';
|
|
37
|
+
const props = activity.properties || {};
|
|
38
|
+
|
|
39
|
+
if (activity.type === 'status_changed' && props.to) {
|
|
40
|
+
return `${who} ${label} to ${props.to}`;
|
|
41
|
+
}
|
|
42
|
+
if (activity.type === 'assigned' && props.agent_name) {
|
|
43
|
+
return `${who} assigned to ${props.agent_name}`;
|
|
44
|
+
}
|
|
45
|
+
if (activity.type === 'priority_changed' && props.to) {
|
|
46
|
+
return `${who} ${label} to ${props.to}`;
|
|
47
|
+
}
|
|
48
|
+
if ((activity.type === 'tag_added' || activity.type === 'tag_removed') && props.tag) {
|
|
49
|
+
return `${who} ${label} "${props.tag}"`;
|
|
50
|
+
}
|
|
51
|
+
if (activity.type === 'department_changed' && props.department) {
|
|
52
|
+
return `${who} ${label} to ${props.department}`;
|
|
53
|
+
}
|
|
54
|
+
return `${who} ${label}`;
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div class="space-y-3" role="feed">
|
|
60
|
+
<div v-for="activity in activities" :key="activity.id" role="article" class="flex gap-3 text-sm">
|
|
61
|
+
<div
|
|
62
|
+
:class="[
|
|
63
|
+
'mt-1 h-2 w-2 flex-shrink-0 rounded-full',
|
|
64
|
+
dark ? 'bg-[var(--esc-panel-text-muted)]' : 'bg-gray-400',
|
|
65
|
+
]"
|
|
66
|
+
></div>
|
|
67
|
+
<div class="flex-1">
|
|
68
|
+
<p :class="dark ? 'text-[var(--esc-panel-text-secondary)]' : 'text-gray-700'">
|
|
69
|
+
{{ describeActivity(activity) }}
|
|
70
|
+
</p>
|
|
71
|
+
<p :class="['text-xs', dark ? 'text-[var(--esc-panel-text-muted)]' : 'text-gray-400']">
|
|
72
|
+
{{ formatDate(activity.created_at) }}
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div
|
|
77
|
+
v-if="!activities?.length"
|
|
78
|
+
:class="['py-4 text-center text-sm', dark ? 'text-[var(--esc-panel-text-muted)]' : 'text-gray-500']"
|
|
79
|
+
>
|
|
80
|
+
No activity yet.
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|