@consilioweb/payload-support 5.0.0 → 6.0.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 (116) hide show
  1. package/README.md +249 -22
  2. package/dist/components/RichTextEditor/index.js +1 -1
  3. package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
  4. package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
  5. package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
  6. package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
  7. package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
  8. package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
  9. package/dist/components/TicketConversation/components/QuickActions.js +10 -8
  10. package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
  11. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
  12. package/dist/components/TicketConversation/index.d.ts +10 -0
  13. package/dist/components/TicketConversation/index.js +25 -7
  14. package/dist/index.cjs +306 -60
  15. package/dist/index.d.cts +107 -38
  16. package/dist/index.d.ts +107 -38
  17. package/dist/index.js +300 -59
  18. package/dist/styles/BillingView.module.scss +11 -11
  19. package/dist/styles/ChatView.module.scss +7 -7
  20. package/dist/styles/CommandPalette.module.scss +1 -1
  21. package/dist/styles/CrmView.module.scss +9 -9
  22. package/dist/styles/EmailTracking.module.scss +6 -6
  23. package/dist/styles/ImportConversation.module.scss +5 -5
  24. package/dist/styles/Logs.module.scss +5 -5
  25. package/dist/styles/NewTicket.module.scss +2 -2
  26. package/dist/styles/PendingEmails.module.scss +13 -13
  27. package/dist/styles/SupportDashboard.module.scss +24 -8
  28. package/dist/styles/TicketDetail.module.scss +20 -20
  29. package/dist/styles/TicketInbox.module.scss +6 -6
  30. package/dist/styles/TicketingSettings.module.scss +10 -10
  31. package/dist/styles/TimeDashboard.module.scss +6 -6
  32. package/dist/styles/_tokens.scss +3 -1
  33. package/dist/views/BillingView/client.js +15 -8
  34. package/dist/views/ChatView/client.js +2 -0
  35. package/dist/views/CrmView/client.js +2 -0
  36. package/dist/views/EmailTrackingView/client.js +23 -11
  37. package/dist/views/ImportConversationView/client.js +1 -0
  38. package/dist/views/LogsView/client.js +1 -1
  39. package/dist/views/NewTicketView/client.js +20 -13
  40. package/dist/views/PendingEmailsView/client.js +9 -3
  41. package/dist/views/SupportDashboardView/client.js +17 -19
  42. package/dist/views/TicketDetailView/client.js +23 -11
  43. package/dist/views/TicketInboxView/client.js +52 -26
  44. package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  45. package/dist/views/TicketingSettingsView/client.js +44 -26
  46. package/dist/views/TimeDashboardView/client.js +10 -7
  47. package/dist/views/shared/ErrorBoundary.d.ts +36 -1
  48. package/dist/views/shared/ErrorBoundary.js +59 -28
  49. package/dist/views/shared/adminTokens.d.ts +15 -5
  50. package/dist/views/shared/adminTokens.js +23 -7
  51. package/dist/views/shared/icons.d.ts +49 -0
  52. package/dist/views/shared/icons.js +92 -0
  53. package/dist/views/shared/locales/en.json +15 -1
  54. package/dist/views/shared/locales/fr.json +15 -1
  55. package/package.json +6 -2
  56. package/scripts/uninstall-data.mjs +178 -0
  57. package/scripts/uninstall.mjs +184 -0
  58. package/src/collections/Tickets.ts +7 -0
  59. package/src/collections/TimeEntries.ts +11 -3
  60. package/src/components/RichTextEditor/index.tsx +1 -1
  61. package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
  62. package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
  63. package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
  64. package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
  65. package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
  66. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
  67. package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
  68. package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
  69. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
  70. package/src/components/TicketConversation/index.tsx +41 -7
  71. package/src/endpoints/delete-account.ts +191 -47
  72. package/src/endpoints/export-data.ts +104 -4
  73. package/src/endpoints/purge-logs.ts +6 -15
  74. package/src/index.ts +2 -0
  75. package/src/plugin.ts +51 -1
  76. package/src/portal/LiveChat.tsx +1 -1
  77. package/src/portal/auth/profile/page.tsx +3 -2
  78. package/src/portal/icons.tsx +91 -0
  79. package/src/styles/BillingView.module.scss +11 -11
  80. package/src/styles/ChatView.module.scss +7 -7
  81. package/src/styles/CommandPalette.module.scss +1 -1
  82. package/src/styles/CrmView.module.scss +9 -9
  83. package/src/styles/EmailTracking.module.scss +6 -6
  84. package/src/styles/ImportConversation.module.scss +5 -5
  85. package/src/styles/Logs.module.scss +5 -5
  86. package/src/styles/NewTicket.module.scss +2 -2
  87. package/src/styles/PendingEmails.module.scss +13 -13
  88. package/src/styles/SupportDashboard.module.scss +24 -8
  89. package/src/styles/TicketDetail.module.scss +20 -20
  90. package/src/styles/TicketInbox.module.scss +6 -6
  91. package/src/styles/TicketingSettings.module.scss +10 -10
  92. package/src/styles/TimeDashboard.module.scss +6 -6
  93. package/src/styles/_tokens.scss +3 -1
  94. package/src/types.ts +20 -0
  95. package/src/utils/retention.ts +111 -0
  96. package/src/utils/slugs.ts +15 -0
  97. package/src/views/BillingView/client.tsx +17 -8
  98. package/src/views/ChatView/client.tsx +2 -0
  99. package/src/views/CrmView/client.tsx +2 -0
  100. package/src/views/EmailTrackingView/client.tsx +18 -7
  101. package/src/views/ImportConversationView/client.tsx +1 -0
  102. package/src/views/LogsView/client.tsx +1 -1
  103. package/src/views/NewTicketView/client.tsx +22 -13
  104. package/src/views/PendingEmailsView/client.tsx +9 -3
  105. package/src/views/SupportDashboardView/client.tsx +11 -5
  106. package/src/views/TicketDetailView/client.tsx +25 -11
  107. package/src/views/TicketInboxView/client.tsx +47 -7
  108. package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  109. package/src/views/TicketingSettingsView/client.tsx +201 -144
  110. package/src/views/TimeDashboardView/client.tsx +10 -7
  111. package/src/views/shared/ErrorBoundary.tsx +95 -31
  112. package/src/views/shared/adminTokens.ts +28 -11
  113. package/src/views/shared/icons.tsx +146 -0
  114. package/src/views/shared/locales/en.json +15 -1
  115. package/src/views/shared/locales/fr.json +15 -1
  116. package/src/types/lucide-react.d.ts +0 -42
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { useState, useCallback, useEffect } from 'react';
4
- import { Inbox, Paperclip, ChevronUp, ChevronDown, Plus, Link2, X, Search } from 'lucide-react';
4
+ import { Inbox, Paperclip, ChevronUp, ChevronDown, Plus, Link2, X, Search } from '../shared/icons.js';
5
5
  import { SkeletonDashboard } from '../shared/Skeleton.js';
6
6
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
7
7
  import styles from '../../styles/PendingEmails.module.scss';
@@ -52,7 +52,7 @@ function TicketSearchModal({
52
52
  return /* @__PURE__ */ jsx("div", { className: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { className: styles.modal, onClick: (e) => e.stopPropagation(), children: [
53
53
  /* @__PURE__ */ jsxs("div", { className: styles.modalHeader, children: [
54
54
  /* @__PURE__ */ jsx("h3", { className: styles.modalTitle, children: "Rattacher a un ticket" }),
55
- /* @__PURE__ */ jsx("button", { onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
55
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Fermer", onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
56
56
  ] }),
57
57
  suggestions.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles.sectionBlock, children: [
58
58
  /* @__PURE__ */ jsx("div", { className: styles.sectionLabel, children: "Suggestions" }),
@@ -71,6 +71,7 @@ function TicketSearchModal({
71
71
  "input",
72
72
  {
73
73
  type: "text",
74
+ "aria-label": "Rechercher un ticket",
74
75
  placeholder: "Rechercher un ticket (TK-0042, sujet...)...",
75
76
  value: search,
76
77
  onChange: (e) => setSearch(e.target.value),
@@ -163,7 +164,7 @@ function ClientPickerModal({
163
164
  return /* @__PURE__ */ jsx("div", { className: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { className: styles.modal, onClick: (e) => e.stopPropagation(), children: [
164
165
  /* @__PURE__ */ jsxs("div", { className: styles.modalHeader, children: [
165
166
  /* @__PURE__ */ jsx("h3", { className: styles.modalTitle, children: "Choisir un client" }),
166
- /* @__PURE__ */ jsx("button", { onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
167
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Fermer", onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
167
168
  ] }),
168
169
  detectedClient && /* @__PURE__ */ jsxs("div", { className: styles.sectionBlock, children: [
169
170
  /* @__PURE__ */ jsx("div", { className: styles.sectionLabel, children: "Client detecte" }),
@@ -181,6 +182,7 @@ function ClientPickerModal({
181
182
  "input",
182
183
  {
183
184
  type: "text",
185
+ "aria-label": "Rechercher un client",
184
186
  placeholder: "Rechercher un client (nom, email, entreprise)...",
185
187
  value: search,
186
188
  onChange: (e) => setSearch(e.target.value),
@@ -205,6 +207,7 @@ function ClientPickerModal({
205
207
  "input",
206
208
  {
207
209
  type: "text",
210
+ "aria-label": "Prenom",
208
211
  placeholder: "Prenom *",
209
212
  value: newClient.firstName,
210
213
  onChange: (e) => setNewClient((p) => ({ ...p, firstName: e.target.value })),
@@ -215,6 +218,7 @@ function ClientPickerModal({
215
218
  "input",
216
219
  {
217
220
  type: "text",
221
+ "aria-label": "Nom",
218
222
  placeholder: "Nom",
219
223
  value: newClient.lastName,
220
224
  onChange: (e) => setNewClient((p) => ({ ...p, lastName: e.target.value })),
@@ -226,6 +230,7 @@ function ClientPickerModal({
226
230
  "input",
227
231
  {
228
232
  type: "email",
233
+ "aria-label": "Email",
229
234
  placeholder: "Email *",
230
235
  value: newClient.email,
231
236
  onChange: (e) => setNewClient((p) => ({ ...p, email: e.target.value })),
@@ -236,6 +241,7 @@ function ClientPickerModal({
236
241
  "input",
237
242
  {
238
243
  type: "text",
244
+ "aria-label": "Entreprise",
239
245
  placeholder: "Entreprise *",
240
246
  value: newClient.company,
241
247
  onChange: (e) => setNewClient((p) => ({ ...p, company: e.target.value })),
@@ -372,25 +372,23 @@ const SupportDashboardClient = () => {
372
372
  /* @__PURE__ */ jsx("th", { children: t("dashboard.tableHeaders.modified") }),
373
373
  /* @__PURE__ */ jsx("th", {})
374
374
  ] }) }),
375
- /* @__PURE__ */ jsx("tbody", { children: tickets.map((tk) => /* @__PURE__ */ jsxs(
376
- "tr",
377
- {
378
- onClick: () => {
379
- window.location.href = `/admin/support/ticket?id=${tk.id}`;
380
- },
381
- children: [
382
- /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: `${styles.statusDot} ${getStatusDotClass(tk.status)}` }) }),
383
- /* @__PURE__ */ jsxs("td", { className: styles.ticketNum, children: [
384
- "#",
385
- tk.ticketNumber
386
- ] }),
387
- /* @__PURE__ */ jsx("td", { className: styles.ticketSubject, children: tk.subject }),
388
- /* @__PURE__ */ jsx("td", { className: styles.ticketClient, children: getClientName(tk) }),
389
- /* @__PURE__ */ jsx("td", { className: styles.ticketTime, children: timeAgo(tk.updatedAt) }),
390
- /* @__PURE__ */ jsx("td", { className: styles.ticketArrow, children: "\u2192" })
391
- ]
392
- },
393
- tk.id
375
+ /* @__PURE__ */ jsx("tbody", { children: tickets.map((tk) => (
376
+ /*
377
+ * The row is not the control: a `<tr onClick>` is invisible to
378
+ * the keyboard, and turning it into role="button" would cost
379
+ * the table semantics. The link lives in the subject cell.
380
+ */
381
+ /* @__PURE__ */ jsxs("tr", { children: [
382
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: `${styles.statusDot} ${getStatusDotClass(tk.status)}` }) }),
383
+ /* @__PURE__ */ jsxs("td", { className: styles.ticketNum, children: [
384
+ "#",
385
+ tk.ticketNumber
386
+ ] }),
387
+ /* @__PURE__ */ jsx("td", { className: styles.ticketSubject, children: /* @__PURE__ */ jsx("a", { className: styles.ticketLink, href: `/admin/support/ticket?id=${tk.id}`, children: tk.subject }) }),
388
+ /* @__PURE__ */ jsx("td", { className: styles.ticketClient, children: getClientName(tk) }),
389
+ /* @__PURE__ */ jsx("td", { className: styles.ticketTime, children: timeAgo(tk.updatedAt) }),
390
+ /* @__PURE__ */ jsx("td", { className: styles.ticketArrow, children: "\u2192" })
391
+ ] }, tk.id)
394
392
  )) })
395
393
  ] })
396
394
  ] }),
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import React, { useRef, useState, useEffect, useCallback } from 'react';
3
+ import React, { useRef, useId, useState, useEffect, useCallback } from 'react';
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import Link from 'next/link';
6
6
  import { RichTextEditor } from '../../components/RichTextEditor/index.js';
@@ -25,6 +25,11 @@ const TicketDetailClient = () => {
25
25
  const threadEndRef = useRef(null);
26
26
  const dropdownRef = useRef(null);
27
27
  const fileInputRef = useRef(null);
28
+ const manualTimeId = useId();
29
+ const billingTypeId = useId();
30
+ const flatAmountId = useId();
31
+ const billedAmountId = useId();
32
+ const paymentStatusId = useId();
28
33
  const [ticket, setTicket] = useState(null);
29
34
  const [messages, setMessages] = useState([]);
30
35
  const [client, setClient] = useState(null);
@@ -755,7 +760,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
755
760
  Array.isArray(msg.attachments) && msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: s.attachments, children: msg.attachments.map((att, i) => {
756
761
  const file = typeof att.file === "object" ? att.file : null;
757
762
  if (!file) return null;
758
- return (file.mimeType || "").startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx("img", { src: file.url || "", alt: "", className: s.attachmentImg }) }, i) : /* @__PURE__ */ jsxs("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", className: s.attachmentFile, children: [
763
+ return (file.mimeType || "").startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx("img", { src: file.url || "", alt: file.filename || t("detail.file"), className: s.attachmentImg }) }, i) : /* @__PURE__ */ jsxs("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", className: s.attachmentFile, children: [
759
764
  "PJ ",
760
765
  file.filename || "Fichier"
761
766
  ] }, i);
@@ -846,7 +851,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
846
851
  "\u{1F4CE} ",
847
852
  t("detail.file")
848
853
  ] }),
849
- /* @__PURE__ */ jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: s.hiddenFileInput, onChange: (e) => handleFileSelect(e.target.files) }),
854
+ /* @__PURE__ */ jsx("input", { ref: fileInputRef, type: "file", multiple: true, "aria-label": t("detail.file"), className: s.hiddenFileInput, onChange: (e) => handleFileSelect(e.target.files) }),
850
855
  macros.length > 0 && /* @__PURE__ */ jsxs(
851
856
  "select",
852
857
  {
@@ -920,6 +925,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
920
925
  /* @__PURE__ */ jsxs(
921
926
  "select",
922
927
  {
928
+ "aria-label": t("detail.sendAs"),
923
929
  value: sendAsClient ? "client" : "admin",
924
930
  onChange: (e) => {
925
931
  const asClient = e.target.value === "client";
@@ -1132,9 +1138,9 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1132
1138
  ] }),
1133
1139
  /* @__PURE__ */ jsx("div", { className: s.timerBar, "aria-hidden": true, children: /* @__PURE__ */ jsx("div", { className: s.timerBarFill, style: { width: `${Math.min(100, (totalMin + Math.floor(timerSeconds / 60)) / 120 * 100)}%` } }) }),
1134
1140
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" }, children: [
1135
- /* @__PURE__ */ jsx("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: "manual-time-input" }),
1141
+ /* @__PURE__ */ jsx("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 }),
1136
1142
  /* @__PURE__ */ jsx("button", { className: `${s.timerBtn} ${s.timerBtnGhost}`, onClick: async () => {
1137
- const input = document.getElementById("manual-time-input");
1143
+ const input = document.getElementById(manualTimeId);
1138
1144
  const mins = Number(input?.value);
1139
1145
  if (!mins || mins < 1 || !ticketId) return;
1140
1146
  try {
@@ -1145,7 +1151,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1145
1151
  }
1146
1152
  }, style: { fontSize: 11 }, children: t("detail.addTime") })
1147
1153
  ] }),
1148
- /* @__PURE__ */ jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-500)" }, children: [
1154
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-650)" }, children: [
1149
1155
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0", alignItems: "center" }, children: [
1150
1156
  /* @__PURE__ */ jsx("span", { children: t("detail.billing.billable") }),
1151
1157
  /* @__PURE__ */ jsx(
@@ -1172,7 +1178,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1172
1178
  ] })
1173
1179
  ] })
1174
1180
  ] }),
1175
- timeEntries.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-500)" }, children: [
1181
+ timeEntries.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-650)" }, children: [
1176
1182
  /* @__PURE__ */ jsx("span", { children: new Date(e.date).toLocaleDateString(DATE_LOCALE, { day: "numeric", month: "short" }) }),
1177
1183
  /* @__PURE__ */ jsxs("span", { title: e.description, style: { fontWeight: 600, cursor: e.description ? "help" : "default" }, children: [
1178
1184
  e.duration,
@@ -1191,6 +1197,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1191
1197
  "input",
1192
1198
  {
1193
1199
  className: s.tagInput,
1200
+ "aria-label": t("detail.addTag"),
1194
1201
  value: newTagValue,
1195
1202
  onChange: (e) => setNewTagValue(e.target.value),
1196
1203
  onKeyDown: (e) => {
@@ -1211,10 +1218,11 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1211
1218
  /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.billing.title") }),
1212
1219
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1213
1220
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1214
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: t("detail.billing.type") }),
1221
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 12 }, htmlFor: billingTypeId, children: t("detail.billing.type") }),
1215
1222
  /* @__PURE__ */ jsxs(
1216
1223
  "select",
1217
1224
  {
1225
+ id: billingTypeId,
1218
1226
  value: ticket?.billingType || "hourly",
1219
1227
  onChange: async (e) => {
1220
1228
  try {
@@ -1237,11 +1245,12 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1237
1245
  )
1238
1246
  ] }),
1239
1247
  ticket?.billingType === "flat" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1240
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: t("detail.billing.flatAmount") }),
1248
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 12 }, htmlFor: flatAmountId, children: t("detail.billing.flatAmount") }),
1241
1249
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1242
1250
  /* @__PURE__ */ jsx(
1243
1251
  "input",
1244
1252
  {
1253
+ id: flatAmountId,
1245
1254
  type: "number",
1246
1255
  defaultValue: ticket?.flatRateAmount ?? "",
1247
1256
  placeholder: "0",
@@ -1265,11 +1274,12 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1265
1274
  ] })
1266
1275
  ] }),
1267
1276
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1268
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: t("detail.billing.billedAmount") }),
1277
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 12 }, htmlFor: billedAmountId, children: t("detail.billing.billedAmount") }),
1269
1278
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1270
1279
  /* @__PURE__ */ jsx(
1271
1280
  "input",
1272
1281
  {
1282
+ id: billedAmountId,
1273
1283
  type: "number",
1274
1284
  defaultValue: ticket?.billedAmount ?? "",
1275
1285
  placeholder: "0",
@@ -1293,10 +1303,11 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1293
1303
  ] })
1294
1304
  ] }),
1295
1305
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1296
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: t("detail.billing.payment") }),
1306
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 12 }, htmlFor: paymentStatusId, children: t("detail.billing.payment") }),
1297
1307
  /* @__PURE__ */ jsxs(
1298
1308
  "select",
1299
1309
  {
1310
+ id: paymentStatusId,
1300
1311
  value: ticket?.paymentStatus || "unpaid",
1301
1312
  onChange: async (e) => {
1302
1313
  try {
@@ -1382,6 +1393,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1382
1393
  "input",
1383
1394
  {
1384
1395
  className: s.splitInput,
1396
+ "aria-label": t("detail.split.subjectLabel"),
1385
1397
  value: splitSubject,
1386
1398
  onChange: (e) => setSplitSubject(e.target.value),
1387
1399
  onKeyDown: (e) => {
@@ -9,6 +9,7 @@ import { StatusPill } from '../shared/StatusPill.js';
9
9
  import { computeSlaState, formatSlaRemaining } from '../shared/sla.js';
10
10
  import s from '../../styles/TicketInbox.module.scss';
11
11
 
12
+ const INBOX_TABPANEL_ID = "inbox-tabpanel";
12
13
  const PRIORITY_COLORS = {
13
14
  urgent: "var(--theme-error-500)",
14
15
  high: "var(--theme-warning-500)",
@@ -213,6 +214,7 @@ const TicketInboxClient = () => {
213
214
  {
214
215
  type: "text",
215
216
  className: s.searchInput,
217
+ "aria-label": t("inbox.searchLabel"),
216
218
  placeholder: t("inbox.searchPlaceholder"),
217
219
  value: search,
218
220
  onChange: (e) => setSearch(e.target.value)
@@ -223,31 +225,55 @@ const TicketInboxClient = () => {
223
225
  /* @__PURE__ */ jsx(Link, { href: "/admin/support/new-ticket", className: s.newTicketBtn, children: t("inbox.newTicketBtn") })
224
226
  ] })
225
227
  ] }),
226
- /* @__PURE__ */ jsx("div", { className: s.tabs, role: "tablist", children: tabs.map((tk) => {
227
- const isAlert = tk.alert && tk.count > 0;
228
- return /* @__PURE__ */ jsxs(
229
- "button",
230
- {
231
- role: "tab",
232
- "aria-selected": tab === tk.key,
233
- className: `${s.tab} ${tab === tk.key ? s.tabActive : ""} ${isAlert ? s.tabAlert : ""}`,
234
- onClick: () => {
235
- setTab(tk.key);
236
- setSelectedIdx(-1);
237
- },
238
- children: [
239
- tk.label,
240
- /* @__PURE__ */ jsx("span", { className: `${s.tabCount} ${isAlert ? s.tabCountAlert : ""}`, children: tk.count })
241
- ]
228
+ /* @__PURE__ */ jsx(
229
+ "div",
230
+ {
231
+ className: s.tabs,
232
+ role: "tablist",
233
+ "aria-label": t("inbox.tabsLabel"),
234
+ onKeyDown: (e) => {
235
+ const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
236
+ if (!keys.includes(e.key)) return;
237
+ e.preventDefault();
238
+ const i = tabs.findIndex((tk) => tk.key === tab);
239
+ const next = e.key === "Home" ? 0 : e.key === "End" ? tabs.length - 1 : e.key === "ArrowLeft" ? (i - 1 + tabs.length) % tabs.length : (i + 1) % tabs.length;
240
+ const target = tabs[next];
241
+ if (!target) return;
242
+ setTab(target.key);
243
+ setSelectedIdx(-1);
244
+ document.getElementById(`inbox-tab-${target.key}`)?.focus();
242
245
  },
243
- tk.key
244
- );
245
- }) }),
246
+ children: tabs.map((tk) => {
247
+ const isAlert = tk.alert && tk.count > 0;
248
+ return /* @__PURE__ */ jsxs(
249
+ "button",
250
+ {
251
+ type: "button",
252
+ role: "tab",
253
+ id: `inbox-tab-${tk.key}`,
254
+ "aria-selected": tab === tk.key,
255
+ "aria-controls": INBOX_TABPANEL_ID,
256
+ tabIndex: tab === tk.key ? 0 : -1,
257
+ className: `${s.tab} ${tab === tk.key ? s.tabActive : ""} ${isAlert ? s.tabAlert : ""}`,
258
+ onClick: () => {
259
+ setTab(tk.key);
260
+ setSelectedIdx(-1);
261
+ },
262
+ children: [
263
+ tk.label,
264
+ /* @__PURE__ */ jsx("span", { className: `${s.tabCount} ${isAlert ? s.tabCountAlert : ""}`, children: tk.count })
265
+ ]
266
+ },
267
+ tk.key
268
+ );
269
+ })
270
+ }
271
+ ),
246
272
  /* @__PURE__ */ jsx("div", { className: s.sortRow, children: checkedIds.size > 0 ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", flex: 1 }, children: [
247
273
  /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "var(--theme-text)" }, children: checkedIds.size > 1 ? t("inbox.selectedPlural", { count: String(checkedIds.size) }) : t("inbox.selected", { count: String(checkedIds.size) }) }),
248
- /* @__PURE__ */ jsx("button", { className: s.sortSelect, onClick: () => handleBulkAction("close"), disabled: bulkProcessing, children: t("inbox.closeAction") }),
249
- /* @__PURE__ */ jsx("button", { className: s.sortSelect, onClick: () => handleBulkAction("reopen"), disabled: bulkProcessing, children: t("inbox.reopenAction") }),
250
- /* @__PURE__ */ jsxs("select", { className: s.sortSelect, value: bulkAction, onChange: (e) => {
274
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.sortSelect, onClick: () => handleBulkAction("close"), disabled: bulkProcessing, children: t("inbox.closeAction") }),
275
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.sortSelect, onClick: () => handleBulkAction("reopen"), disabled: bulkProcessing, children: t("inbox.reopenAction") }),
276
+ /* @__PURE__ */ jsxs("select", { className: s.sortSelect, "aria-label": t("inbox.moreActions"), value: bulkAction, onChange: (e) => {
251
277
  if (e.target.value) handleBulkAction(e.target.value);
252
278
  setBulkAction("");
253
279
  }, children: [
@@ -255,14 +281,14 @@ const TicketInboxClient = () => {
255
281
  /* @__PURE__ */ jsx("option", { value: "set_priority", children: t("inbox.changePriority") }),
256
282
  /* @__PURE__ */ jsx("option", { value: "delete", children: t("inbox.deleteAction") })
257
283
  ] }),
258
- /* @__PURE__ */ jsx("button", { className: s.sortSelect, onClick: () => setCheckedIds(/* @__PURE__ */ new Set()), style: { marginLeft: "auto" }, children: t("inbox.deselect") })
259
- ] }) : /* @__PURE__ */ jsxs("select", { className: s.sortSelect, value: sort, onChange: (e) => setSort(e.target.value), children: [
284
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.sortSelect, onClick: () => setCheckedIds(/* @__PURE__ */ new Set()), style: { marginLeft: "auto" }, children: t("inbox.deselect") })
285
+ ] }) : /* @__PURE__ */ jsxs("select", { className: s.sortSelect, "aria-label": t("inbox.sortLabel"), value: sort, onChange: (e) => setSort(e.target.value), children: [
260
286
  /* @__PURE__ */ jsx("option", { value: "-updatedAt", children: t("inbox.sort.newest") }),
261
287
  /* @__PURE__ */ jsx("option", { value: "updatedAt", children: t("inbox.sort.oldest") }),
262
288
  /* @__PURE__ */ jsx("option", { value: "-createdAt", children: t("inbox.sort.created") }),
263
289
  /* @__PURE__ */ jsx("option", { value: "priority", children: t("inbox.sort.priority") })
264
290
  ] }) }),
265
- loading ? /* @__PURE__ */ jsx("div", { className: s.loading, children: t("common.loading") }) : tickets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: s.empty, children: [
291
+ /* @__PURE__ */ jsx("div", { role: "tabpanel", id: INBOX_TABPANEL_ID, "aria-labelledby": `inbox-tab-${tab}`, children: loading ? /* @__PURE__ */ jsx("div", { className: s.loading, children: t("common.loading") }) : tickets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: s.empty, children: [
266
292
  /* @__PURE__ */ jsx("div", { className: s.emptyIcon, children: "--" }),
267
293
  /* @__PURE__ */ jsx("div", { className: s.emptyText, children: t("inbox.empty") })
268
294
  ] }) : /* @__PURE__ */ jsx("div", { className: s.list, children: tickets.map((tk, idx) => {
@@ -333,7 +359,7 @@ const TicketInboxClient = () => {
333
359
  },
334
360
  tk.id
335
361
  );
336
- }) }),
362
+ }) }) }),
337
363
  /* @__PURE__ */ jsxs("div", { className: s.keyboardHints, children: [
338
364
  /* @__PURE__ */ jsxs("span", { children: [
339
365
  /* @__PURE__ */ jsx("kbd", { children: "\u2191" }),
@@ -70,7 +70,7 @@
70
70
 
71
71
  .sectionDescription {
72
72
  font-size: 0.8125rem;
73
- color: var(--theme-elevation-500);
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-500);
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-500);
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-500);
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-500);
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-500);
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-500);
293
+ color: var(--theme-elevation-650);
294
294
  }
295
295
 
296
296
  // ── Signature ─────────────────────────────────