@escalated-dev/escalated 0.11.8 → 0.11.10

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 (52) hide show
  1. package/README.md +7 -0
  2. package/package.json +1 -1
  3. package/pages.json +1 -1
  4. package/src/components/AuditLogEntry.vue +15 -9
  5. package/src/components/ChatQueue.vue +1 -1
  6. package/src/components/EscalatedLayout.vue +224 -38
  7. package/src/components/PeriodSelector.vue +1 -1
  8. package/src/components/PermissionMatrix.vue +1 -1
  9. package/src/components/QuickFilters.vue +11 -5
  10. package/src/components/TicketList.vue +10 -7
  11. package/src/components/admin/newsletters/DeliveriesTable.vue +29 -27
  12. package/src/components/admin/newsletters/ListMemberTable.vue +30 -28
  13. package/src/pages/Admin/ApiTokens/Index.vue +1 -1
  14. package/src/pages/Admin/Automations/Form.vue +10 -2
  15. package/src/pages/Admin/Automations/Index.vue +1 -1
  16. package/src/pages/Admin/BusinessHours/Index.vue +1 -1
  17. package/src/pages/Admin/Capacity/Index.vue +1 -1
  18. package/src/pages/Admin/CustomFields/Index.vue +1 -1
  19. package/src/pages/Admin/CustomObjects/Index.vue +1 -1
  20. package/src/pages/Admin/CustomObjects/Records.vue +1 -1
  21. package/src/pages/Admin/Departments/Index.vue +1 -1
  22. package/src/pages/Admin/EscalationRules/Index.vue +1 -1
  23. package/src/pages/Admin/Import/Index.vue +1 -1
  24. package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +3 -3
  25. package/src/pages/Admin/Macros/Index.vue +1 -1
  26. package/src/pages/Admin/Newsletters/Index.vue +31 -29
  27. package/src/pages/Admin/Newsletters/Lists/Index.vue +22 -20
  28. package/src/pages/Admin/Reports/AgentMetrics.vue +1 -1
  29. package/src/pages/Admin/Reports/AgentRanking.vue +3 -3
  30. package/src/pages/Admin/Reports/Cohorts.vue +7 -5
  31. package/src/pages/Admin/Reports/Comparison.vue +3 -3
  32. package/src/pages/Admin/Reports/Dashboard.vue +7 -5
  33. package/src/pages/Admin/Reports/ResolutionTimes.vue +54 -46
  34. package/src/pages/Admin/Reports/ResponseTimes.vue +54 -46
  35. package/src/pages/Admin/Reports/SlaReport.vue +70 -68
  36. package/src/pages/Admin/Reports/SlaTrends.vue +2 -2
  37. package/src/pages/Admin/Reports.vue +1 -1
  38. package/src/pages/Admin/Roles/Index.vue +1 -1
  39. package/src/pages/Admin/Settings/DatabaseConnection.vue +3 -3
  40. package/src/pages/Admin/Settings/EmailSettings.vue +1 -1
  41. package/src/pages/Admin/Skills/Index.vue +1 -1
  42. package/src/pages/Admin/SlaPolicies/Index.vue +1 -1
  43. package/src/pages/Admin/Statuses/Index.vue +1 -1
  44. package/src/pages/Admin/Tags/Index.vue +1 -1
  45. package/src/pages/Admin/Users/Index.vue +1 -1
  46. package/src/pages/Admin/Webhooks/DeliveryLog.vue +1 -1
  47. package/src/pages/Admin/Webhooks/Index.vue +1 -1
  48. package/src/pages/Admin/Workflows/Index.vue +1 -1
  49. package/src/pages/Admin/Workflows/Logs.vue +1 -1
  50. package/src/pages/Agent/Dashboard.vue +1 -1
  51. package/src/plugin.js +19 -0
  52. package/src/utils/scrollAffordance.js +87 -0
@@ -58,9 +58,9 @@ function toggleSort(col) {
58
58
 
59
59
  <template>
60
60
  <EscalatedLayout title="Resolution Time Analysis">
61
- <div class="mb-6 flex items-center justify-between">
61
+ <div class="mb-6 flex flex-wrap items-center justify-between gap-3">
62
62
  <PeriodSelector :model-value="periodDays" @update:model-value="changePeriod" />
63
- <div class="flex items-center gap-3">
63
+ <div class="flex flex-wrap items-center gap-3">
64
64
  <ReportExportButton report-name="resolution-times" :period-days="periodDays" />
65
65
  <Link
66
66
  :href="route('escalated.admin.reports')"
@@ -114,51 +114,59 @@ function toggleSort(col) {
114
114
  <div class="border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)] px-4 py-3">
115
115
  <h3 class="text-sm font-semibold text-[var(--esc-panel-text-secondary)]">Resolution Time by Agent</h3>
116
116
  </div>
117
- <table class="w-full">
118
- <thead>
119
- <tr class="border-b border-[var(--esc-panel-border)]">
120
- <th
121
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
117
+ <div class="esc-table-scroll">
118
+ <table class="w-full">
119
+ <thead>
120
+ <tr class="border-b border-[var(--esc-panel-border)]">
121
+ <th
122
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
123
+ >
124
+ Agent
125
+ </th>
126
+ <th
127
+ v-for="col in [
128
+ { key: 'avg', label: 'Avg' },
129
+ { key: 'median', label: 'Median' },
130
+ { key: 'p90', label: 'P90' },
131
+ ]"
132
+ :key="col.key"
133
+ class="cursor-pointer px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
134
+ @click="toggleSort(col.key)"
135
+ >
136
+ {{ col.label }}
137
+ <span v-if="sortColumn === col.key">{{
138
+ sortDir === 'asc' ? ' &#9650;' : ' &#9660;'
139
+ }}</span>
140
+ </th>
141
+ </tr>
142
+ </thead>
143
+ <tbody class="divide-y divide-[var(--esc-panel-border)]">
144
+ <tr
145
+ v-for="agent in sortedAgents"
146
+ :key="agent.agent_id"
147
+ class="hover:bg-[var(--esc-panel-hover)]"
122
148
  >
123
- Agent
124
- </th>
125
- <th
126
- v-for="col in [
127
- { key: 'avg', label: 'Avg' },
128
- { key: 'median', label: 'Median' },
129
- { key: 'p90', label: 'P90' },
130
- ]"
131
- :key="col.key"
132
- class="cursor-pointer px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
133
- @click="toggleSort(col.key)"
134
- >
135
- {{ col.label }}
136
- <span v-if="sortColumn === col.key">{{ sortDir === 'asc' ? ' &#9650;' : ' &#9660;' }}</span>
137
- </th>
138
- </tr>
139
- </thead>
140
- <tbody class="divide-y divide-[var(--esc-panel-border)]">
141
- <tr v-for="agent in sortedAgents" :key="agent.agent_id" class="hover:bg-[var(--esc-panel-hover)]">
142
- <td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
143
- {{ agent.agent_name }}
144
- </td>
145
- <td class="px-4 py-3 text-sm font-medium" :class="resColor(agent.avg)">
146
- {{ agent.avg ?? '—' }}h
147
- </td>
148
- <td class="px-4 py-3 text-sm font-medium" :class="resColor(agent.median)">
149
- {{ agent.median ?? '—' }}h
150
- </td>
151
- <td class="px-4 py-3 text-sm font-medium" :class="resColor(agent.p90)">
152
- {{ agent.p90 ?? '—' }}h
153
- </td>
154
- </tr>
155
- <tr v-if="!sortedAgents.length">
156
- <td colspan="4" class="px-4 py-8 text-center text-sm text-[var(--esc-panel-text-muted)]">
157
- No agent data for this period.
158
- </td>
159
- </tr>
160
- </tbody>
161
- </table>
149
+ <td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
150
+ {{ agent.agent_name }}
151
+ </td>
152
+ <td class="px-4 py-3 text-sm font-medium" :class="resColor(agent.avg)">
153
+ {{ agent.avg ?? '—' }}h
154
+ </td>
155
+ <td class="px-4 py-3 text-sm font-medium" :class="resColor(agent.median)">
156
+ {{ agent.median ?? '—' }}h
157
+ </td>
158
+ <td class="px-4 py-3 text-sm font-medium" :class="resColor(agent.p90)">
159
+ {{ agent.p90 ?? '—' }}h
160
+ </td>
161
+ </tr>
162
+ <tr v-if="!sortedAgents.length">
163
+ <td colspan="4" class="px-4 py-8 text-center text-sm text-[var(--esc-panel-text-muted)]">
164
+ No agent data for this period.
165
+ </td>
166
+ </tr>
167
+ </tbody>
168
+ </table>
169
+ </div>
162
170
  </div>
163
171
  </EscalatedLayout>
164
172
  </template>
@@ -58,9 +58,9 @@ function toggleSort(col) {
58
58
 
59
59
  <template>
60
60
  <EscalatedLayout title="First Response Time Analysis">
61
- <div class="mb-6 flex items-center justify-between">
61
+ <div class="mb-6 flex flex-wrap items-center justify-between gap-3">
62
62
  <PeriodSelector :model-value="periodDays" @update:model-value="changePeriod" />
63
- <div class="flex items-center gap-3">
63
+ <div class="flex flex-wrap items-center gap-3">
64
64
  <ReportExportButton report-name="response-times" :period-days="periodDays" />
65
65
  <Link
66
66
  :href="route('escalated.admin.reports')"
@@ -114,51 +114,59 @@ function toggleSort(col) {
114
114
  <div class="border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)] px-4 py-3">
115
115
  <h3 class="text-sm font-semibold text-[var(--esc-panel-text-secondary)]">FRT by Agent</h3>
116
116
  </div>
117
- <table class="w-full">
118
- <thead>
119
- <tr class="border-b border-[var(--esc-panel-border)]">
120
- <th
121
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
117
+ <div class="esc-table-scroll">
118
+ <table class="w-full">
119
+ <thead>
120
+ <tr class="border-b border-[var(--esc-panel-border)]">
121
+ <th
122
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
123
+ >
124
+ Agent
125
+ </th>
126
+ <th
127
+ v-for="col in [
128
+ { key: 'avg', label: 'Avg' },
129
+ { key: 'median', label: 'Median' },
130
+ { key: 'p90', label: 'P90' },
131
+ ]"
132
+ :key="col.key"
133
+ class="cursor-pointer px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
134
+ @click="toggleSort(col.key)"
135
+ >
136
+ {{ col.label }}
137
+ <span v-if="sortColumn === col.key">{{
138
+ sortDir === 'asc' ? ' &#9650;' : ' &#9660;'
139
+ }}</span>
140
+ </th>
141
+ </tr>
142
+ </thead>
143
+ <tbody class="divide-y divide-[var(--esc-panel-border)]">
144
+ <tr
145
+ v-for="agent in sortedAgents"
146
+ :key="agent.agent_id"
147
+ class="hover:bg-[var(--esc-panel-hover)]"
122
148
  >
123
- Agent
124
- </th>
125
- <th
126
- v-for="col in [
127
- { key: 'avg', label: 'Avg' },
128
- { key: 'median', label: 'Median' },
129
- { key: 'p90', label: 'P90' },
130
- ]"
131
- :key="col.key"
132
- class="cursor-pointer px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
133
- @click="toggleSort(col.key)"
134
- >
135
- {{ col.label }}
136
- <span v-if="sortColumn === col.key">{{ sortDir === 'asc' ? ' &#9650;' : ' &#9660;' }}</span>
137
- </th>
138
- </tr>
139
- </thead>
140
- <tbody class="divide-y divide-[var(--esc-panel-border)]">
141
- <tr v-for="agent in sortedAgents" :key="agent.agent_id" class="hover:bg-[var(--esc-panel-hover)]">
142
- <td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
143
- {{ agent.agent_name }}
144
- </td>
145
- <td class="px-4 py-3 text-sm font-medium" :class="frtColor(agent.avg)">
146
- {{ agent.avg ?? '—' }}h
147
- </td>
148
- <td class="px-4 py-3 text-sm font-medium" :class="frtColor(agent.median)">
149
- {{ agent.median ?? '—' }}h
150
- </td>
151
- <td class="px-4 py-3 text-sm font-medium" :class="frtColor(agent.p90)">
152
- {{ agent.p90 ?? '—' }}h
153
- </td>
154
- </tr>
155
- <tr v-if="!sortedAgents.length">
156
- <td colspan="4" class="px-4 py-8 text-center text-sm text-[var(--esc-panel-text-muted)]">
157
- No agent data for this period.
158
- </td>
159
- </tr>
160
- </tbody>
161
- </table>
149
+ <td class="px-4 py-3 text-sm font-medium text-[var(--esc-panel-text-secondary)]">
150
+ {{ agent.agent_name }}
151
+ </td>
152
+ <td class="px-4 py-3 text-sm font-medium" :class="frtColor(agent.avg)">
153
+ {{ agent.avg ?? '—' }}h
154
+ </td>
155
+ <td class="px-4 py-3 text-sm font-medium" :class="frtColor(agent.median)">
156
+ {{ agent.median ?? '—' }}h
157
+ </td>
158
+ <td class="px-4 py-3 text-sm font-medium" :class="frtColor(agent.p90)">
159
+ {{ agent.p90 ?? '—' }}h
160
+ </td>
161
+ </tr>
162
+ <tr v-if="!sortedAgents.length">
163
+ <td colspan="4" class="px-4 py-8 text-center text-sm text-[var(--esc-panel-text-muted)]">
164
+ No agent data for this period.
165
+ </td>
166
+ </tr>
167
+ </tbody>
168
+ </table>
169
+ </div>
162
170
  </div>
163
171
  </EscalatedLayout>
164
172
  </template>
@@ -89,74 +89,76 @@ const resolutionBreaches = computed(() => {
89
89
  <div class="border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)] px-4 py-3">
90
90
  <h3 class="text-sm font-semibold text-[var(--esc-panel-text-secondary)]">Breach Details</h3>
91
91
  </div>
92
- <table class="w-full">
93
- <thead>
94
- <tr class="border-b border-[var(--esc-panel-border)]">
95
- <th
96
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
97
- >
98
- Ticket
99
- </th>
100
- <th
101
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
102
- >
103
- Subject
104
- </th>
105
- <th
106
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
107
- >
108
- SLA Policy
109
- </th>
110
- <th
111
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
112
- >
113
- Breach Type
114
- </th>
115
- <th
116
- class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
117
- >
118
- Created
119
- </th>
120
- </tr>
121
- </thead>
122
- <tbody class="divide-y divide-[var(--esc-panel-border)]">
123
- <tr v-for="ticket in breaches" :key="ticket.id" class="hover:bg-[var(--esc-panel-hover)]">
124
- <td class="px-4 py-3 text-sm font-mono text-[var(--esc-panel-accent)]">
125
- {{ ticket.reference }}
126
- </td>
127
- <td class="max-w-xs truncate px-4 py-3 text-sm text-[var(--esc-panel-text-secondary)]">
128
- {{ ticket.subject }}
129
- </td>
130
- <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
131
- {{ ticket.sla_policy?.name || '—' }}
132
- </td>
133
- <td class="px-4 py-3">
134
- <div class="flex gap-1.5">
135
- <span
136
- v-if="ticket.sla_first_response_breached"
137
- class="inline-flex rounded-full border border-red-500/20 bg-red-500/10 px-2 py-0.5 text-xs font-medium text-red-400"
138
- >
139
- Response
140
- </span>
141
- <span
142
- v-if="ticket.sla_resolution_breached"
143
- class="inline-flex rounded-full border border-red-500/20 bg-red-500/10 px-2 py-0.5 text-xs font-medium text-red-400"
144
- >
145
- Resolution
146
- </span>
147
- </div>
148
- </td>
149
- <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-muted)]">
150
- {{ ticket.created_at ? new Date(ticket.created_at).toLocaleDateString() : '—' }}
151
- </td>
152
- </tr>
153
- <tr v-if="!breaches?.length">
154
- <td colspan="5" class="px-4 py-8 text-center text-sm text-[var(--esc-panel-text-muted)]">
155
- No SLA breaches in this period.
156
- </td>
157
- </tr>
158
- </tbody>
159
- </table>
92
+ <div class="esc-table-scroll">
93
+ <table class="w-full">
94
+ <thead>
95
+ <tr class="border-b border-[var(--esc-panel-border)]">
96
+ <th
97
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
98
+ >
99
+ Ticket
100
+ </th>
101
+ <th
102
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
103
+ >
104
+ Subject
105
+ </th>
106
+ <th
107
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
108
+ >
109
+ SLA Policy
110
+ </th>
111
+ <th
112
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
113
+ >
114
+ Breach Type
115
+ </th>
116
+ <th
117
+ class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
118
+ >
119
+ Created
120
+ </th>
121
+ </tr>
122
+ </thead>
123
+ <tbody class="divide-y divide-[var(--esc-panel-border)]">
124
+ <tr v-for="ticket in breaches" :key="ticket.id" class="hover:bg-[var(--esc-panel-hover)]">
125
+ <td class="px-4 py-3 text-sm font-mono text-[var(--esc-panel-accent)]">
126
+ {{ ticket.reference }}
127
+ </td>
128
+ <td class="max-w-xs truncate px-4 py-3 text-sm text-[var(--esc-panel-text-secondary)]">
129
+ {{ ticket.subject }}
130
+ </td>
131
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
132
+ {{ ticket.sla_policy?.name || '—' }}
133
+ </td>
134
+ <td class="px-4 py-3">
135
+ <div class="flex gap-1.5">
136
+ <span
137
+ v-if="ticket.sla_first_response_breached"
138
+ class="inline-flex rounded-full border border-red-500/20 bg-red-500/10 px-2 py-0.5 text-xs font-medium text-red-400"
139
+ >
140
+ Response
141
+ </span>
142
+ <span
143
+ v-if="ticket.sla_resolution_breached"
144
+ class="inline-flex rounded-full border border-red-500/20 bg-red-500/10 px-2 py-0.5 text-xs font-medium text-red-400"
145
+ >
146
+ Resolution
147
+ </span>
148
+ </div>
149
+ </td>
150
+ <td class="px-4 py-3 text-sm text-[var(--esc-panel-text-muted)]">
151
+ {{ ticket.created_at ? new Date(ticket.created_at).toLocaleDateString() : '—' }}
152
+ </td>
153
+ </tr>
154
+ <tr v-if="!breaches?.length">
155
+ <td colspan="5" class="px-4 py-8 text-center text-sm text-[var(--esc-panel-text-muted)]">
156
+ No SLA breaches in this period.
157
+ </td>
158
+ </tr>
159
+ </tbody>
160
+ </table>
161
+ </div>
160
162
  </div>
161
163
  </EscalatedLayout>
162
164
  </template>
@@ -39,9 +39,9 @@ const riskCategories = computed(() => {
39
39
 
40
40
  <template>
41
41
  <EscalatedLayout title="SLA Breach Trends">
42
- <div class="mb-6 flex items-center justify-between">
42
+ <div class="mb-6 flex flex-wrap items-center justify-between gap-3">
43
43
  <PeriodSelector :model-value="periodDays" @update:model-value="changePeriod" />
44
- <div class="flex items-center gap-3">
44
+ <div class="flex flex-wrap items-center gap-3">
45
45
  <ReportExportButton report-name="sla-trends" :period-days="periodDays" />
46
46
  <Link
47
47
  :href="route('escalated.admin.reports')"
@@ -31,7 +31,7 @@ const { getPageComponents } = usePluginExtensions();
31
31
  <EscalatedLayout title="Reports">
32
32
  <!-- Sub-report navigation -->
33
33
  <div
34
- class="mb-6 flex items-center gap-3 rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)] p-3"
34
+ class="mb-6 flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)] p-3"
35
35
  >
36
36
  <span class="text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
37
37
  >Detailed Reports:</span
@@ -22,7 +22,7 @@ function destroy(role) {
22
22
  Add Role
23
23
  </Link>
24
24
  </div>
25
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
25
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
26
26
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
27
27
  <thead>
28
28
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -88,7 +88,7 @@ function formatCount(value) {
88
88
  table is not affected — it stays wherever your application keeps it.
89
89
  </p>
90
90
 
91
- <dl v-if="active" class="grid grid-cols-2 gap-x-6 gap-y-3 text-sm">
91
+ <dl v-if="active" class="grid grid-cols-[auto_minmax(0,1fr)] gap-x-6 gap-y-3 text-sm">
92
92
  <dt class="text-[var(--esc-panel-text-muted)]">Connection</dt>
93
93
  <dd class="font-medium text-[var(--esc-panel-text)]">
94
94
  {{ active.name }}
@@ -160,7 +160,7 @@ function formatCount(value) {
160
160
  {{ statusLabel(connection) }}
161
161
  </span>
162
162
  </span>
163
- <span class="mt-0.5 block text-xs text-[var(--esc-panel-text-muted)]">
163
+ <span class="mt-0.5 block break-all text-xs text-[var(--esc-panel-text-muted)]">
164
164
  {{ connection.driver ?? 'unknown driver' }} ·
165
165
  {{ connection.database ?? 'no database configured' }}
166
166
  <template v-if="connection.ticket_count !== null">
@@ -217,7 +217,7 @@ function formatCount(value) {
217
217
  <button
218
218
  type="submit"
219
219
  :disabled="form.processing || !isChanging"
220
- class="rounded-lg bg-[var(--esc-accent)] px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
220
+ class="rounded-lg bg-[var(--esc-panel-accent)] px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
221
221
  >
222
222
  {{ form.processing ? 'Saving…' : 'Use this connection' }}
223
223
  </button>
@@ -71,7 +71,7 @@ const dkimStatusClasses = {
71
71
  <!-- Address Table -->
72
72
  <div
73
73
  v-if="form.addresses.length"
74
- class="overflow-hidden rounded-lg border border-[var(--esc-panel-border)]"
74
+ class="esc-table-scroll rounded-lg border border-[var(--esc-panel-border)]"
75
75
  >
76
76
  <table class="w-full">
77
77
  <thead>
@@ -21,7 +21,7 @@ function destroy(id) {
21
21
  Add Skill
22
22
  </Link>
23
23
  </div>
24
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
24
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
25
25
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
26
26
  <thead>
27
27
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -21,7 +21,7 @@ function destroy(id) {
21
21
  Add Policy
22
22
  </Link>
23
23
  </div>
24
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
24
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
25
25
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
26
26
  <thead>
27
27
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -70,7 +70,7 @@ function destroy(id) {
70
70
  {{ categoryLabels[cat] || cat }}
71
71
  </h3>
72
72
  <div
73
- class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]"
73
+ class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]"
74
74
  >
75
75
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
76
76
  <thead>
@@ -85,7 +85,7 @@ function destroy(id) {
85
85
  Create
86
86
  </button>
87
87
  </form>
88
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
88
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
89
89
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
90
90
  <thead>
91
91
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -42,7 +42,7 @@ function isSelf(user) {
42
42
  </form>
43
43
  </div>
44
44
 
45
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
45
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
46
46
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
47
47
  <thead>
48
48
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -30,7 +30,7 @@ function togglePayload(id) {
30
30
  </a>
31
31
  </div>
32
32
 
33
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
33
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
34
34
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
35
35
  <thead>
36
36
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -27,7 +27,7 @@ function lastDeliveryStatus(webhook) {
27
27
  Add Webhook
28
28
  </Link>
29
29
  </div>
30
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
30
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
31
31
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
32
32
  <thead>
33
33
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -95,7 +95,7 @@ function triggerBadgeClass(trigger) {
95
95
  </div>
96
96
  </div>
97
97
 
98
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
98
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
99
99
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
100
100
  <thead>
101
101
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -114,7 +114,7 @@ function actionStatusIcon(status) {
114
114
  </div>
115
115
 
116
116
  <!-- Logs table -->
117
- <div class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
117
+ <div class="esc-table-scroll rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)]">
118
118
  <table class="min-w-full divide-y divide-[var(--esc-panel-border)]">
119
119
  <thead>
120
120
  <tr class="bg-[var(--esc-panel-hover)]">
@@ -144,7 +144,7 @@ const loadWidgetComponent = (plugin, component) => {
144
144
  </h2>
145
145
  <div
146
146
  :class="[
147
- 'overflow-hidden rounded-xl border',
147
+ 'esc-table-scroll rounded-xl border',
148
148
  escDark ? 'border-white/[0.06] bg-neutral-900/60' : 'border-gray-200 bg-white',
149
149
  ]"
150
150
  >
package/src/plugin.js CHANGED
@@ -159,6 +159,18 @@ const panelTokenMap = {
159
159
  active: '--esc-panel-active',
160
160
  };
161
161
 
162
+ // Optional panel hooks. The layout reads each through a CSS fallback to the
163
+ // token it used before the hook existed, so a host that leaves one unset sees
164
+ // no change. They are set only when given, and cleared otherwise, so a host
165
+ // stylesheet can also set them on :root.
166
+ const panelOptionalTokenMap = {
167
+ activeText: '--esc-panel-active-text', // active nav item text + icon (falls back to text)
168
+ headerBg: '--esc-panel-header-bg', // admin top bar / agent top nav (falls back to topbarBg / sidebarBg)
169
+ headerText: '--esc-panel-header-text', // page title and menu button in that bar (falls back to text)
170
+ logoTileBg: '--esc-panel-logo-tile-bg', // square behind the logo (falls back to borderInput)
171
+ logoTileFg: '--esc-panel-logo-tile-fg', // currentColor inside the logo tile (falls back to text)
172
+ };
173
+
162
174
  function applyPanelTheme(panelConfig) {
163
175
  const defaults = panelConfig.mode === 'light' ? panelLightDefaults : panelDarkDefaults;
164
176
  const overrides = {};
@@ -172,4 +184,11 @@ function applyPanelTheme(panelConfig) {
172
184
  for (const [key, cssVar] of Object.entries(panelTokenMap)) {
173
185
  style.setProperty(cssVar, merged[key]);
174
186
  }
187
+ for (const [key, cssVar] of Object.entries(panelOptionalTokenMap)) {
188
+ if (panelConfig[key] !== undefined && panelConfig[key] !== null) {
189
+ style.setProperty(cssVar, panelConfig[key]);
190
+ } else {
191
+ style.removeProperty(cssVar);
192
+ }
193
+ }
175
194
  }