@consilioweb/payload-support 5.0.0 → 6.0.1
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 +249 -22
- package/dist/components/RichTextEditor/index.js +1 -1
- package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
- package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
- package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
- package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
- package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
- package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
- package/dist/components/TicketConversation/components/QuickActions.js +10 -8
- package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
- package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
- package/dist/components/TicketConversation/index.d.ts +10 -0
- package/dist/components/TicketConversation/index.js +25 -7
- package/dist/index.cjs +306 -60
- package/dist/index.d.cts +107 -38
- package/dist/index.d.ts +107 -38
- package/dist/index.js +300 -59
- package/dist/styles/BillingView.module.scss +11 -11
- package/dist/styles/ChatView.module.scss +7 -7
- package/dist/styles/CommandPalette.module.scss +1 -1
- package/dist/styles/CrmView.module.scss +9 -9
- package/dist/styles/EmailTracking.module.scss +6 -6
- package/dist/styles/ImportConversation.module.scss +5 -5
- package/dist/styles/Logs.module.scss +5 -5
- package/dist/styles/NewTicket.module.scss +2 -2
- package/dist/styles/PendingEmails.module.scss +13 -13
- package/dist/styles/SupportDashboard.module.scss +24 -8
- package/dist/styles/TicketDetail.module.scss +20 -20
- package/dist/styles/TicketInbox.module.scss +6 -6
- package/dist/styles/TicketingSettings.module.scss +10 -10
- package/dist/styles/TimeDashboard.module.scss +6 -6
- package/dist/styles/_tokens.scss +3 -1
- package/dist/utils/db.js +20 -0
- package/dist/utils/readSettings.js +150 -0
- package/dist/views/BillingView/client.js +15 -8
- package/dist/views/ChatView/client.js +2 -0
- package/dist/views/CrmView/client.js +2 -0
- package/dist/views/EmailTrackingView/client.js +23 -11
- package/dist/views/ImportConversationView/client.js +1 -0
- package/dist/views/LogsView/client.js +1 -1
- package/dist/views/NewTicketView/client.js +20 -13
- package/dist/views/PendingEmailsView/client.js +9 -3
- package/dist/views/SupportDashboardView/client.js +17 -19
- package/dist/views/TicketDetailView/client.js +23 -11
- package/dist/views/TicketInboxView/client.js +52 -26
- package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
- package/dist/views/TicketingSettingsView/client.js +44 -26
- package/dist/views/TimeDashboardView/client.js +10 -7
- package/dist/views/shared/ErrorBoundary.d.ts +36 -1
- package/dist/views/shared/ErrorBoundary.js +59 -28
- package/dist/views/shared/adminTokens.d.ts +15 -5
- package/dist/views/shared/adminTokens.js +23 -7
- package/dist/views/shared/icons.d.ts +49 -0
- package/dist/views/shared/icons.js +92 -0
- package/dist/views/shared/locales/en.json +15 -1
- package/dist/views/shared/locales/fr.json +15 -1
- package/package.json +9 -4
- package/scripts/copy-subpath-types.mjs +103 -0
- package/scripts/uninstall-data.mjs +178 -0
- package/scripts/uninstall.mjs +184 -0
- package/scripts/verify-dist-imports.mjs +84 -0
- package/src/collections/Tickets.ts +7 -0
- package/src/collections/TimeEntries.ts +11 -3
- package/src/components/RichTextEditor/index.tsx +1 -1
- package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
- package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
- package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
- package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
- package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
- package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
- package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
- package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
- package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
- package/src/components/TicketConversation/index.tsx +41 -7
- package/src/endpoints/delete-account.ts +191 -47
- package/src/endpoints/export-data.ts +104 -4
- package/src/endpoints/purge-logs.ts +6 -15
- package/src/index.ts +2 -0
- package/src/plugin.ts +51 -1
- package/src/portal/LiveChat.tsx +1 -1
- package/src/portal/auth/profile/page.tsx +3 -2
- package/src/portal/icons.tsx +91 -0
- package/src/styles/BillingView.module.scss +11 -11
- package/src/styles/ChatView.module.scss +7 -7
- package/src/styles/CommandPalette.module.scss +1 -1
- package/src/styles/CrmView.module.scss +9 -9
- package/src/styles/EmailTracking.module.scss +6 -6
- package/src/styles/ImportConversation.module.scss +5 -5
- package/src/styles/Logs.module.scss +5 -5
- package/src/styles/NewTicket.module.scss +2 -2
- package/src/styles/PendingEmails.module.scss +13 -13
- package/src/styles/SupportDashboard.module.scss +24 -8
- package/src/styles/TicketDetail.module.scss +20 -20
- package/src/styles/TicketInbox.module.scss +6 -6
- package/src/styles/TicketingSettings.module.scss +10 -10
- package/src/styles/TimeDashboard.module.scss +6 -6
- package/src/styles/_tokens.scss +3 -1
- package/src/types.ts +20 -0
- package/src/utils/retention.ts +111 -0
- package/src/utils/slugs.ts +15 -0
- package/src/views/BillingView/client.tsx +17 -8
- package/src/views/ChatView/client.tsx +2 -0
- package/src/views/CrmView/client.tsx +2 -0
- package/src/views/EmailTrackingView/client.tsx +18 -7
- package/src/views/ImportConversationView/client.tsx +1 -0
- package/src/views/LogsView/client.tsx +1 -1
- package/src/views/NewTicketView/client.tsx +22 -13
- package/src/views/PendingEmailsView/client.tsx +9 -3
- package/src/views/SupportDashboardView/client.tsx +11 -5
- package/src/views/TicketDetailView/client.tsx +25 -11
- package/src/views/TicketInboxView/client.tsx +47 -7
- package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
- package/src/views/TicketingSettingsView/client.tsx +201 -144
- package/src/views/TimeDashboardView/client.tsx +10 -7
- package/src/views/shared/ErrorBoundary.tsx +95 -31
- package/src/views/shared/adminTokens.ts +28 -11
- package/src/views/shared/icons.tsx +146 -0
- package/src/views/shared/locales/en.json +15 -1
- package/src/views/shared/locales/fr.json +15 -1
- package/src/types/lucide-react.d.ts +0 -42
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
|
3
|
+
import React, { useState, useEffect, useCallback, useId, useRef } from 'react'
|
|
4
4
|
import { useSearchParams } from 'next/navigation'
|
|
5
5
|
import Link from 'next/link'
|
|
6
6
|
import { RichTextEditor, type RichTextEditorHandle } from '../../components/RichTextEditor/index'
|
|
@@ -26,6 +26,13 @@ export const TicketDetailClient: React.FC = () => {
|
|
|
26
26
|
const threadEndRef = useRef<HTMLDivElement>(null)
|
|
27
27
|
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
28
28
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
29
|
+
// Generated ids so every control below carries the name of the text next to
|
|
30
|
+
// it. useId, never a literal: this view can be mounted more than once.
|
|
31
|
+
const manualTimeId = useId()
|
|
32
|
+
const billingTypeId = useId()
|
|
33
|
+
const flatAmountId = useId()
|
|
34
|
+
const billedAmountId = useId()
|
|
35
|
+
const paymentStatusId = useId()
|
|
29
36
|
|
|
30
37
|
const [ticket, setTicket] = useState<Record<string, unknown> | null>(null)
|
|
31
38
|
const [messages, setMessages] = useState<Message[]>([])
|
|
@@ -721,7 +728,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
721
728
|
const file = typeof att.file === 'object' ? att.file : null
|
|
722
729
|
if (!file) return null
|
|
723
730
|
return (file.mimeType || '').startsWith('image/')
|
|
724
|
-
? <a key={i} href={file.url || '#'} target="_blank" rel="noopener noreferrer"><img src={file.url || ''} alt=
|
|
731
|
+
? <a key={i} href={file.url || '#'} target="_blank" rel="noopener noreferrer"><img src={file.url || ''} alt={file.filename || t('detail.file')} className={s.attachmentImg} /></a>
|
|
725
732
|
: <a key={i} href={file.url || '#'} target="_blank" rel="noopener noreferrer" className={s.attachmentFile}>PJ {file.filename || 'Fichier'}</a>
|
|
726
733
|
})}
|
|
727
734
|
</div>
|
|
@@ -810,7 +817,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
810
817
|
/>
|
|
811
818
|
{/* #5 — File upload button */}
|
|
812
819
|
<button className={s.toolbarBtn} data-tooltip={t('detail.file')} aria-label={t('detail.file')} onClick={() => fileInputRef.current?.click()} style={{ fontSize: 11, fontWeight: 700, padding: '4px 10px', width: 'auto' }}>📎 {t('detail.file')}</button>
|
|
813
|
-
<input ref={fileInputRef} type="file" multiple className={s.hiddenFileInput} onChange={(e) => handleFileSelect(e.target.files)} />
|
|
820
|
+
<input ref={fileInputRef} type="file" multiple aria-label={t('detail.file')} className={s.hiddenFileInput} onChange={(e) => handleFileSelect(e.target.files)} />
|
|
814
821
|
{macros.length > 0 && (
|
|
815
822
|
<select
|
|
816
823
|
className={s.cannedSelect}
|
|
@@ -871,6 +878,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
871
878
|
<div className={s.composerFooter}>
|
|
872
879
|
<div className={s.composerOptions}>
|
|
873
880
|
<select
|
|
881
|
+
aria-label={t('detail.sendAs')}
|
|
874
882
|
value={sendAsClient ? 'client' : 'admin'}
|
|
875
883
|
onChange={(e) => {
|
|
876
884
|
const asClient = e.target.value === 'client'
|
|
@@ -1089,9 +1097,9 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1089
1097
|
</div>
|
|
1090
1098
|
{/* Manual time entry */}
|
|
1091
1099
|
<div style={{ display: 'flex', gap: 6, marginTop: 8, alignItems: 'center' }}>
|
|
1092
|
-
<input type="number" min="1" placeholder="min" style={{ width: 60, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--theme-elevation-200)', fontSize: 12, color: 'var(--theme-text)', background: 'var(--theme-elevation-0)' }} id=
|
|
1100
|
+
<input type="number" min="1" placeholder="min" aria-label={t('detail.manualMinutes')} style={{ width: 60, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--theme-elevation-200)', fontSize: 12, color: 'var(--theme-text)', background: 'var(--theme-elevation-0)' }} id={manualTimeId} />
|
|
1093
1101
|
<button className={`${s.timerBtn} ${s.timerBtnGhost}`} onClick={async () => {
|
|
1094
|
-
const input = document.getElementById(
|
|
1102
|
+
const input = document.getElementById(manualTimeId) as HTMLInputElement
|
|
1095
1103
|
const mins = Number(input?.value)
|
|
1096
1104
|
if (!mins || mins < 1 || !ticketId) return
|
|
1097
1105
|
try {
|
|
@@ -1102,7 +1110,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1102
1110
|
}} style={{ fontSize: 11 }}>{t('detail.addTime')}</button>
|
|
1103
1111
|
</div>
|
|
1104
1112
|
{/* Billing info */}
|
|
1105
|
-
<div style={{ marginTop: 8, fontSize: 11, color: 'var(--theme-elevation-
|
|
1113
|
+
<div style={{ marginTop: 8, fontSize: 11, color: 'var(--theme-elevation-650)' }}>
|
|
1106
1114
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0', alignItems: 'center' }}>
|
|
1107
1115
|
<span>{t('detail.billing.billable')}</span>
|
|
1108
1116
|
<button
|
|
@@ -1126,7 +1134,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1126
1134
|
{timeEntries.length > 0 && (
|
|
1127
1135
|
<div style={{ marginTop: 8, fontSize: 11 }}>
|
|
1128
1136
|
{timeEntries.slice(0, 6).map((e) => (
|
|
1129
|
-
<div key={e.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', color: 'var(--theme-elevation-
|
|
1137
|
+
<div key={e.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', color: 'var(--theme-elevation-650)' }}>
|
|
1130
1138
|
<span>{new Date(e.date).toLocaleDateString(DATE_LOCALE, { day: 'numeric', month: 'short' })}</span>
|
|
1131
1139
|
<span title={e.description} style={{ fontWeight: 600, cursor: e.description ? 'help' : 'default' }}>{e.duration}min</span>
|
|
1132
1140
|
</div>
|
|
@@ -1149,6 +1157,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1149
1157
|
{addingTag ? (
|
|
1150
1158
|
<input
|
|
1151
1159
|
className={s.tagInput}
|
|
1160
|
+
aria-label={t('detail.addTag')}
|
|
1152
1161
|
value={newTagValue}
|
|
1153
1162
|
onChange={(e) => setNewTagValue(e.target.value)}
|
|
1154
1163
|
onKeyDown={(e) => { if (e.key === 'Enter') handleAddTag(); if (e.key === 'Escape') { setAddingTag(false); setNewTagValue('') } }}
|
|
@@ -1167,8 +1176,9 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1167
1176
|
<div className={s.sideSectionTitle}>{t('detail.billing.title')}</div>
|
|
1168
1177
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1169
1178
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
1170
|
-
<
|
|
1179
|
+
<label style={{ fontSize: 12 }} htmlFor={billingTypeId}>{t('detail.billing.type')}</label>
|
|
1171
1180
|
<select
|
|
1181
|
+
id={billingTypeId}
|
|
1172
1182
|
value={(ticket as any)?.billingType || 'hourly'}
|
|
1173
1183
|
onChange={async (e) => {
|
|
1174
1184
|
try {
|
|
@@ -1188,9 +1198,10 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1188
1198
|
</div>
|
|
1189
1199
|
{(ticket as any)?.billingType === 'flat' && (
|
|
1190
1200
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
1191
|
-
<
|
|
1201
|
+
<label style={{ fontSize: 12 }} htmlFor={flatAmountId}>{t('detail.billing.flatAmount')}</label>
|
|
1192
1202
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
1193
1203
|
<input
|
|
1204
|
+
id={flatAmountId}
|
|
1194
1205
|
type="number"
|
|
1195
1206
|
defaultValue={(ticket as any)?.flatRateAmount ?? ''}
|
|
1196
1207
|
placeholder="0"
|
|
@@ -1212,9 +1223,10 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1212
1223
|
</div>
|
|
1213
1224
|
)}
|
|
1214
1225
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
1215
|
-
<
|
|
1226
|
+
<label style={{ fontSize: 12 }} htmlFor={billedAmountId}>{t('detail.billing.billedAmount')}</label>
|
|
1216
1227
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
1217
1228
|
<input
|
|
1229
|
+
id={billedAmountId}
|
|
1218
1230
|
type="number"
|
|
1219
1231
|
defaultValue={(ticket as any)?.billedAmount ?? ''}
|
|
1220
1232
|
placeholder="0"
|
|
@@ -1235,8 +1247,9 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1235
1247
|
</div>
|
|
1236
1248
|
</div>
|
|
1237
1249
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
1238
|
-
<
|
|
1250
|
+
<label style={{ fontSize: 12 }} htmlFor={paymentStatusId}>{t('detail.billing.payment')}</label>
|
|
1239
1251
|
<select
|
|
1252
|
+
id={paymentStatusId}
|
|
1240
1253
|
value={(ticket as any)?.paymentStatus || 'unpaid'}
|
|
1241
1254
|
onChange={async (e) => {
|
|
1242
1255
|
try {
|
|
@@ -1358,6 +1371,7 @@ const [clientTyping, setClientTyping] = useState(false)
|
|
|
1358
1371
|
<div className={s.splitPreview}>{splitModal.preview}</div>
|
|
1359
1372
|
<input
|
|
1360
1373
|
className={s.splitInput}
|
|
1374
|
+
aria-label={t('detail.split.subjectLabel')}
|
|
1361
1375
|
value={splitSubject}
|
|
1362
1376
|
onChange={(e) => setSplitSubject(e.target.value)}
|
|
1363
1377
|
onKeyDown={(e) => { if (e.key === 'Enter') handleSplitConfirm() }}
|
|
@@ -31,6 +31,9 @@ interface Ticket {
|
|
|
31
31
|
|
|
32
32
|
type Tab = 'all' | 'open' | 'waiting_client' | 'resolved' | 'sla_breach'
|
|
33
33
|
|
|
34
|
+
// Single id: this view is a full admin route, mounted once.
|
|
35
|
+
const INBOX_TABPANEL_ID = 'inbox-tabpanel'
|
|
36
|
+
|
|
34
37
|
const PRIORITY_COLORS: Record<string, string> = {
|
|
35
38
|
urgent: 'var(--theme-error-500)',
|
|
36
39
|
high: 'var(--theme-warning-500)',
|
|
@@ -239,6 +242,7 @@ export const TicketInboxClient: React.FC = () => {
|
|
|
239
242
|
<input
|
|
240
243
|
type="text"
|
|
241
244
|
className={s.searchInput}
|
|
245
|
+
aria-label={t('inbox.searchLabel')}
|
|
242
246
|
placeholder={t('inbox.searchPlaceholder')}
|
|
243
247
|
value={search}
|
|
244
248
|
onChange={(e) => setSearch(e.target.value)}
|
|
@@ -250,14 +254,48 @@ export const TicketInboxClient: React.FC = () => {
|
|
|
250
254
|
</div>
|
|
251
255
|
|
|
252
256
|
{/* Tabs */}
|
|
253
|
-
|
|
257
|
+
{/*
|
|
258
|
+
A tablist is ONE tab stop: the inactive tabs carry `tabIndex={-1}` and
|
|
259
|
+
the arrow keys move between them. That is the ARIA pattern, but the
|
|
260
|
+
roving tabIndex is only half of it — without this handler the four
|
|
261
|
+
inactive tabs are simply unreachable from the keyboard, which is worse
|
|
262
|
+
than the plain tab order it replaced.
|
|
263
|
+
*/}
|
|
264
|
+
<div
|
|
265
|
+
className={s.tabs}
|
|
266
|
+
role="tablist"
|
|
267
|
+
aria-label={t('inbox.tabsLabel')}
|
|
268
|
+
onKeyDown={(e) => {
|
|
269
|
+
const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End']
|
|
270
|
+
if (!keys.includes(e.key)) return
|
|
271
|
+
e.preventDefault()
|
|
272
|
+
const i = tabs.findIndex((tk) => tk.key === tab)
|
|
273
|
+
const next =
|
|
274
|
+
e.key === 'Home' ? 0
|
|
275
|
+
: e.key === 'End' ? tabs.length - 1
|
|
276
|
+
: e.key === 'ArrowLeft' ? (i - 1 + tabs.length) % tabs.length
|
|
277
|
+
: (i + 1) % tabs.length
|
|
278
|
+
const target = tabs[next]
|
|
279
|
+
if (!target) return
|
|
280
|
+
setTab(target.key)
|
|
281
|
+
setSelectedIdx(-1)
|
|
282
|
+
// Focus follows selection, as the pattern expects for an
|
|
283
|
+
// automatically-activated tablist.
|
|
284
|
+
document.getElementById(`inbox-tab-${target.key}`)?.focus()
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
254
287
|
{tabs.map((tk) => {
|
|
255
288
|
const isAlert = tk.alert && tk.count > 0
|
|
256
289
|
return (
|
|
257
290
|
<button
|
|
258
291
|
key={tk.key}
|
|
292
|
+
type="button"
|
|
259
293
|
role="tab"
|
|
294
|
+
id={`inbox-tab-${tk.key}`}
|
|
260
295
|
aria-selected={tab === tk.key}
|
|
296
|
+
aria-controls={INBOX_TABPANEL_ID}
|
|
297
|
+
// Roving tabindex: a tablist is one tab stop, arrows move inside.
|
|
298
|
+
tabIndex={tab === tk.key ? 0 : -1}
|
|
261
299
|
className={`${s.tab} ${tab === tk.key ? s.tabActive : ''} ${isAlert ? s.tabAlert : ''}`}
|
|
262
300
|
onClick={() => { setTab(tk.key); setSelectedIdx(-1) }}
|
|
263
301
|
>
|
|
@@ -273,17 +311,17 @@ export const TicketInboxClient: React.FC = () => {
|
|
|
273
311
|
{checkedIds.size > 0 ? (
|
|
274
312
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flex: 1 }}>
|
|
275
313
|
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--theme-text)' }}>{checkedIds.size > 1 ? t('inbox.selectedPlural', { count: String(checkedIds.size) }) : t('inbox.selected', { count: String(checkedIds.size) })}</span>
|
|
276
|
-
<button className={s.sortSelect} onClick={() => handleBulkAction('close')} disabled={bulkProcessing}>{t('inbox.closeAction')}</button>
|
|
277
|
-
<button className={s.sortSelect} onClick={() => handleBulkAction('reopen')} disabled={bulkProcessing}>{t('inbox.reopenAction')}</button>
|
|
278
|
-
<select className={s.sortSelect} value={bulkAction} onChange={(e) => { if (e.target.value) handleBulkAction(e.target.value); setBulkAction('') }}>
|
|
314
|
+
<button type="button" className={s.sortSelect} onClick={() => handleBulkAction('close')} disabled={bulkProcessing}>{t('inbox.closeAction')}</button>
|
|
315
|
+
<button type="button" className={s.sortSelect} onClick={() => handleBulkAction('reopen')} disabled={bulkProcessing}>{t('inbox.reopenAction')}</button>
|
|
316
|
+
<select className={s.sortSelect} aria-label={t('inbox.moreActions')} value={bulkAction} onChange={(e) => { if (e.target.value) handleBulkAction(e.target.value); setBulkAction('') }}>
|
|
279
317
|
<option value="">{t('inbox.moreActions')}</option>
|
|
280
318
|
<option value="set_priority">{t('inbox.changePriority')}</option>
|
|
281
319
|
<option value="delete">{t('inbox.deleteAction')}</option>
|
|
282
320
|
</select>
|
|
283
|
-
<button className={s.sortSelect} onClick={() => setCheckedIds(new Set())} style={{ marginLeft: 'auto' }}>{t('inbox.deselect')}</button>
|
|
321
|
+
<button type="button" className={s.sortSelect} onClick={() => setCheckedIds(new Set())} style={{ marginLeft: 'auto' }}>{t('inbox.deselect')}</button>
|
|
284
322
|
</div>
|
|
285
323
|
) : (
|
|
286
|
-
<select className={s.sortSelect} value={sort} onChange={(e) => setSort(e.target.value)}>
|
|
324
|
+
<select className={s.sortSelect} aria-label={t('inbox.sortLabel')} value={sort} onChange={(e) => setSort(e.target.value)}>
|
|
287
325
|
<option value="-updatedAt">{t('inbox.sort.newest')}</option>
|
|
288
326
|
<option value="updatedAt">{t('inbox.sort.oldest')}</option>
|
|
289
327
|
<option value="-createdAt">{t('inbox.sort.created')}</option>
|
|
@@ -292,7 +330,8 @@ export const TicketInboxClient: React.FC = () => {
|
|
|
292
330
|
)}
|
|
293
331
|
</div>
|
|
294
332
|
|
|
295
|
-
{/* List */}
|
|
333
|
+
{/* List — the panel the tablist above controls */}
|
|
334
|
+
<div role="tabpanel" id={INBOX_TABPANEL_ID} aria-labelledby={`inbox-tab-${tab}`}>
|
|
296
335
|
{loading ? (
|
|
297
336
|
<div className={s.loading}>{t('common.loading')}</div>
|
|
298
337
|
) : tickets.length === 0 ? (
|
|
@@ -361,6 +400,7 @@ export const TicketInboxClient: React.FC = () => {
|
|
|
361
400
|
})}
|
|
362
401
|
</div>
|
|
363
402
|
)}
|
|
403
|
+
</div>
|
|
364
404
|
|
|
365
405
|
{/* Keyboard hints */}
|
|
366
406
|
<div className={s.keyboardHints}>
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
|
|
71
71
|
.sectionDescription {
|
|
72
72
|
font-size: 0.8125rem;
|
|
73
|
-
color: var(--theme-elevation-
|
|
73
|
+
color: var(--theme-elevation-650);
|
|
74
74
|
margin: 0 0 1rem;
|
|
75
75
|
line-height: 1.5;
|
|
76
76
|
}
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
|
|
144
144
|
.featureDesc {
|
|
145
145
|
font-size: 0.75rem;
|
|
146
|
-
color: var(--theme-elevation-
|
|
146
|
+
color: var(--theme-elevation-650);
|
|
147
147
|
margin-top: 0.125rem;
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
|
|
167
167
|
.fieldDescription {
|
|
168
168
|
font-size: 0.75rem;
|
|
169
|
-
color: var(--theme-elevation-
|
|
169
|
+
color: var(--theme-elevation-650);
|
|
170
170
|
font-weight: 400;
|
|
171
171
|
margin-top: 0.125rem;
|
|
172
172
|
}
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
.inputReadonly {
|
|
195
195
|
composes: input;
|
|
196
196
|
background: var(--theme-elevation-50);
|
|
197
|
-
color: var(--theme-elevation-
|
|
197
|
+
color: var(--theme-elevation-650);
|
|
198
198
|
cursor: not-allowed;
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
|
|
231
231
|
.inlineDesc {
|
|
232
232
|
font-size: 0.75rem;
|
|
233
|
-
color: var(--theme-elevation-
|
|
233
|
+
color: var(--theme-elevation-650);
|
|
234
234
|
margin-top: 0.125rem;
|
|
235
235
|
}
|
|
236
236
|
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
|
|
245
245
|
.slaHint {
|
|
246
246
|
font-size: 0.75rem;
|
|
247
|
-
color: var(--theme-elevation-
|
|
247
|
+
color: var(--theme-elevation-650);
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
// ── API Key ───────────────────────────────────
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
.aiToggleDesc {
|
|
291
291
|
display: block;
|
|
292
292
|
font-size: 0.6875rem;
|
|
293
|
-
color: var(--theme-elevation-
|
|
293
|
+
color: var(--theme-elevation-650);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
// ── Signature ─────────────────────────────────
|