@consilioweb/payload-support 0.16.0 → 1.1.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 (199) hide show
  1. package/README.md +357 -435
  2. package/dist/components/TicketConversation/RewriteDropdown.cjs +98 -0
  3. package/dist/components/TicketConversation/RewriteDropdown.js +93 -0
  4. package/dist/components/TicketConversation/SkeletonText.cjs +21 -0
  5. package/dist/components/TicketConversation/SkeletonText.js +20 -0
  6. package/dist/components/TicketConversation/constants.cjs +27 -0
  7. package/dist/components/TicketConversation/constants.js +26 -1
  8. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +20 -0
  9. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.js +19 -0
  10. package/dist/components/TicketConversation/index.cjs +10 -147
  11. package/dist/components/TicketConversation/index.js +4 -141
  12. package/dist/index.cjs +1763 -677
  13. package/dist/index.d.cts +4 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.js +1760 -677
  16. package/dist/views/BillingView/client.cjs +40 -54
  17. package/dist/views/BillingView/client.js +40 -54
  18. package/dist/views/ChatView/client.cjs +4 -3
  19. package/dist/views/ChatView/client.js +4 -3
  20. package/dist/views/CrmView/client.cjs +7 -7
  21. package/dist/views/CrmView/client.js +7 -7
  22. package/dist/views/EmailTrackingView/client.cjs +6 -5
  23. package/dist/views/EmailTrackingView/client.js +6 -5
  24. package/dist/views/ImportConversationView/client.cjs +4 -4
  25. package/dist/views/ImportConversationView/client.js +4 -4
  26. package/dist/views/LogsView/client.cjs +3 -2
  27. package/dist/views/LogsView/client.js +3 -2
  28. package/dist/views/NewTicketView/client.cjs +1 -1
  29. package/dist/views/NewTicketView/client.js +1 -1
  30. package/dist/views/PendingEmailsView/client.cjs +7 -6
  31. package/dist/views/PendingEmailsView/client.js +7 -6
  32. package/dist/views/SupportDashboardView/client.cjs +19 -22
  33. package/dist/views/SupportDashboardView/client.js +19 -22
  34. package/dist/views/TicketDetailView/NextActionItem.cjs +40 -0
  35. package/dist/views/TicketDetailView/NextActionItem.js +34 -0
  36. package/dist/views/TicketDetailView/RewriteDropdown.cjs +80 -0
  37. package/dist/views/TicketDetailView/RewriteDropdown.js +78 -0
  38. package/dist/views/TicketDetailView/client.cjs +59 -190
  39. package/dist/views/TicketDetailView/client.js +52 -183
  40. package/dist/views/TicketDetailView/constants.cjs +20 -0
  41. package/dist/views/TicketDetailView/constants.js +16 -0
  42. package/dist/views/TicketDetailView/helpers.cjs +23 -0
  43. package/dist/views/TicketDetailView/helpers.js +20 -0
  44. package/dist/views/TicketDetailView/types.cjs +2 -0
  45. package/dist/views/TicketDetailView/types.js +1 -0
  46. package/dist/views/TicketInboxView/client.cjs +14 -10
  47. package/dist/views/TicketInboxView/client.js +14 -10
  48. package/dist/views/TicketingSettingsView/client.cjs +101 -96
  49. package/dist/views/TicketingSettingsView/client.js +92 -88
  50. package/dist/views/TimeDashboardView/client.cjs +2 -2
  51. package/dist/views/TimeDashboardView/client.js +2 -2
  52. package/dist/views/shared/StatusPill.cjs +23 -12
  53. package/dist/views/shared/StatusPill.js +23 -12
  54. package/dist/views/shared/dateLocale.cjs +5 -0
  55. package/dist/views/shared/dateLocale.js +3 -0
  56. package/package.json +28 -15
  57. package/src/__tests__/generateTrackingToken.test.ts +53 -0
  58. package/src/__tests__/integration/ai-agent.test.ts +46 -0
  59. package/src/__tests__/integration/auth.test.ts +43 -0
  60. package/src/__tests__/integration/automation-rules.test.ts +68 -0
  61. package/src/__tests__/integration/buildTestPayload.ts +52 -0
  62. package/src/__tests__/integration/channels.test.ts +52 -0
  63. package/src/__tests__/integration/core-behaviors.test.ts +58 -0
  64. package/src/__tests__/integration/dashboard-volume.test.ts +31 -0
  65. package/src/__tests__/integration/digest.test.ts +48 -0
  66. package/src/__tests__/integration/invoice-pdf.test.ts +30 -0
  67. package/src/__tests__/integration/isolation.test.ts +81 -0
  68. package/src/__tests__/integration/mentions-invoice.test.ts +50 -0
  69. package/src/__tests__/integration/nps.test.ts +37 -0
  70. package/src/__tests__/integration/per-team-sla.test.ts +44 -0
  71. package/src/__tests__/integration/push.test.ts +75 -0
  72. package/src/__tests__/integration/sanitization.test.ts +35 -0
  73. package/src/__tests__/integration/sla-pause.test.ts +39 -0
  74. package/src/__tests__/integration/smoke.test.ts +26 -0
  75. package/src/__tests__/integration/snooze.test.ts +68 -0
  76. package/src/__tests__/integration/teams.test.ts +38 -0
  77. package/src/__tests__/rateLimiter.test.ts +89 -0
  78. package/src/__tests__/sanitizeHtml.test.ts +165 -0
  79. package/src/__tests__/sla.test.ts +258 -0
  80. package/src/collections/AutomationRules.ts +66 -0
  81. package/src/collections/NotificationQueue.ts +30 -0
  82. package/src/collections/PushSubscription.ts +28 -0
  83. package/src/collections/SatisfactionSurveys.ts +11 -2
  84. package/src/collections/SlaPolicies.ts +10 -0
  85. package/src/collections/SupportClients.ts +58 -1
  86. package/src/collections/SupportTeam.ts +27 -0
  87. package/src/collections/TicketActivityLog.ts +9 -4
  88. package/src/collections/TicketMessages.ts +140 -31
  89. package/src/collections/Tickets.ts +31 -73
  90. package/src/collections/index.ts +4 -0
  91. package/src/components/TicketConversation/RewriteDropdown.tsx +86 -0
  92. package/src/components/TicketConversation/SkeletonText.tsx +23 -0
  93. package/src/components/TicketConversation/constants.ts +29 -0
  94. package/src/components/TicketConversation/hooks/useDocumentIdFromUrl.ts +21 -0
  95. package/src/components/TicketConversation/index.tsx +4 -152
  96. package/src/endpoints/admin-chat-stream.ts +3 -4
  97. package/src/endpoints/admin-chat.ts +11 -20
  98. package/src/endpoints/admin-stats.ts +77 -35
  99. package/src/endpoints/ai-agent.ts +36 -0
  100. package/src/endpoints/apply-macro.ts +8 -14
  101. package/src/endpoints/auth-2fa.ts +12 -12
  102. package/src/endpoints/auto-close.ts +6 -10
  103. package/src/endpoints/billing.ts +17 -4
  104. package/src/endpoints/bulk-action.ts +7 -12
  105. package/src/endpoints/channels.ts +89 -0
  106. package/src/endpoints/chat-stream.ts +3 -4
  107. package/src/endpoints/chat.ts +7 -12
  108. package/src/endpoints/chatbot.ts +2 -2
  109. package/src/endpoints/client-intelligence.ts +4 -4
  110. package/src/endpoints/delete-account.ts +9 -16
  111. package/src/endpoints/email-stats.ts +2 -2
  112. package/src/endpoints/escalate.ts +2 -2
  113. package/src/endpoints/export-csv.ts +2 -2
  114. package/src/endpoints/export-data.ts +5 -8
  115. package/src/endpoints/import-conversation.ts +6 -10
  116. package/src/endpoints/index.ts +22 -1
  117. package/src/endpoints/invite-collaborator.ts +30 -15
  118. package/src/endpoints/invoice.ts +127 -0
  119. package/src/endpoints/kb-search.ts +2 -2
  120. package/src/endpoints/login.ts +12 -4
  121. package/src/endpoints/merge-clients.ts +14 -19
  122. package/src/endpoints/merge-tickets.ts +11 -18
  123. package/src/endpoints/oauth-google.ts +86 -39
  124. package/src/endpoints/pending-emails-process.ts +15 -25
  125. package/src/endpoints/process-digests.ts +93 -0
  126. package/src/endpoints/process-scheduled.ts +6 -10
  127. package/src/endpoints/process-snooze.ts +78 -0
  128. package/src/endpoints/purge-logs.ts +2 -2
  129. package/src/endpoints/push.ts +49 -0
  130. package/src/endpoints/resend-notification.ts +3 -4
  131. package/src/endpoints/round-robin-config.ts +3 -4
  132. package/src/endpoints/satisfaction.ts +21 -14
  133. package/src/endpoints/search.ts +5 -8
  134. package/src/endpoints/seed-kb.ts +3 -4
  135. package/src/endpoints/send-reminder.ts +6 -9
  136. package/src/endpoints/settings.ts +5 -2
  137. package/src/endpoints/signature.ts +3 -4
  138. package/src/endpoints/sla-check.ts +2 -2
  139. package/src/endpoints/split-ticket.ts +7 -12
  140. package/src/endpoints/statuses.ts +2 -2
  141. package/src/endpoints/ticket-feedback.ts +4 -6
  142. package/src/endpoints/ticket-synthesis.ts +2 -2
  143. package/src/endpoints/track-open.ts +16 -28
  144. package/src/endpoints/transfer-ticket.ts +21 -9
  145. package/src/endpoints/user-prefs.ts +3 -4
  146. package/src/hooks/automationRules.ts +84 -0
  147. package/src/hooks/checkSLA.ts +63 -17
  148. package/src/plugin.ts +8 -0
  149. package/src/portal/LiveChat.tsx +2 -63
  150. package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +10 -0
  151. package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +14 -2
  152. package/src/portal/auth/tickets/detail/MessageActions.tsx +37 -18
  153. package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +10 -0
  154. package/src/portal/auth/tickets/detail/TransferAndInviteActions.tsx +3 -2
  155. package/src/portal/auth/tickets/detail/page.tsx +2 -2
  156. package/src/portal/helpers.ts +46 -0
  157. package/src/portal/login/page.tsx +3 -8
  158. package/src/portal/types.ts +15 -0
  159. package/src/types/lucide-react.d.ts +34 -0
  160. package/src/types/payload-config.d.ts +10 -0
  161. package/src/utils/adminNotification.ts +2 -2
  162. package/src/utils/aiAgent.ts +147 -0
  163. package/src/utils/auth.ts +5 -4
  164. package/src/utils/channels.ts +36 -0
  165. package/src/utils/db.ts +73 -0
  166. package/src/utils/emailTemplate.ts +8 -1
  167. package/src/utils/fireWebhooks.ts +4 -6
  168. package/src/utils/generateTicketSynthesis.ts +7 -12
  169. package/src/utils/invoicePdf.ts +88 -0
  170. package/src/utils/notificationQueue.ts +36 -0
  171. package/src/utils/push.ts +63 -0
  172. package/src/utils/readSettings.ts +22 -6
  173. package/src/utils/sanitizeHtml.ts +57 -0
  174. package/src/utils/slugs.ts +8 -0
  175. package/src/utils/teamAccess.ts +27 -0
  176. package/src/utils/ticketAccess.ts +53 -0
  177. package/src/utils/webhookDispatcher.ts +4 -6
  178. package/src/views/BillingView/client.tsx +49 -37
  179. package/src/views/ChatView/client.tsx +4 -3
  180. package/src/views/CrmView/client.tsx +7 -7
  181. package/src/views/EmailTrackingView/client.tsx +7 -6
  182. package/src/views/ImportConversationView/client.tsx +4 -4
  183. package/src/views/LogsView/client.tsx +3 -2
  184. package/src/views/NewTicketView/client.tsx +1 -1
  185. package/src/views/PendingEmailsView/client.tsx +7 -6
  186. package/src/views/SupportDashboardView/client.tsx +21 -21
  187. package/src/views/TicketDetailView/NextActionItem.tsx +30 -0
  188. package/src/views/TicketDetailView/RewriteDropdown.tsx +63 -0
  189. package/src/views/TicketDetailView/client.tsx +54 -179
  190. package/src/views/TicketDetailView/constants.ts +23 -0
  191. package/src/views/TicketDetailView/helpers.ts +17 -0
  192. package/src/views/TicketDetailView/types.ts +10 -0
  193. package/src/views/TicketInboxView/client.tsx +16 -10
  194. package/src/views/TicketingSettingsView/client.tsx +90 -87
  195. package/src/views/TimeDashboardView/client.tsx +2 -2
  196. package/src/views/shared/StatusPill.tsx +24 -12
  197. package/src/views/shared/dateLocale.ts +5 -0
  198. package/src/views/shared/locales/en.json +708 -0
  199. package/src/views/shared/locales/fr.json +708 -0
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
5
  var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
6
+ var dateLocale = require('../shared/dateLocale');
6
7
  var styles = require('../../styles/BillingView.module.scss');
7
8
 
8
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -168,41 +169,41 @@ const BillingClient = () => {
168
169
  const copyRecap = React.useCallback(() => {
169
170
  if (!data) return;
170
171
  const lines = [];
171
- lines.push(`PRE-FACTURATION \u2014 Du ${from} au ${to}`);
172
- lines.push(`Taux horaire : ${rate} EUR/h`);
172
+ lines.push(t("billing.recap.header", { from, to }));
173
+ lines.push(t("billing.recap.hourlyRate", { rate: String(rate) }));
173
174
  lines.push("=".repeat(50));
174
175
  for (const group of visibleGroups) {
175
176
  lines.push("");
176
- lines.push(`PROJET : ${group.project?.name || "Sans projet"}`);
177
- if (group.client?.company) lines.push(`Client : ${group.client.company}`);
177
+ lines.push(group.project?.name ? t("billing.recap.project", { name: group.project.name }) : t("billing.recap.project", { name: t("billing.recap.noProject") }));
178
+ if (group.client?.company) lines.push(t("billing.recap.client", { company: group.client.company }));
178
179
  lines.push("-".repeat(40));
179
180
  for (const ticket of group.tickets) {
180
- const flag = ticket.hasNoTimeEntries ? " [AUCUN TEMPS SAISI]" : "";
181
+ const flag = ticket.hasNoTimeEntries ? t("billing.recap.noTime") : "";
181
182
  lines.push(` ${ticket.ticketNumber} \u2014 ${ticket.subject}${flag}`);
182
183
  for (const entry of ticket.entries) {
183
184
  lines.push(` ${entry.date} | ${formatDuration(entry.duration)} | ${entry.description || "-"}`);
184
185
  }
185
186
  if (ticket.entries.length > 0) {
186
187
  const ticketAmount = ticket.billedAmount || Number(formatAmount(ticket.totalMinutes, rate));
187
- lines.push(` Sous-total : ${formatDuration(ticket.totalMinutes)} = ${ticketAmount.toFixed(2)} EUR${ticket.billedAmount ? " (forfait)" : ""}`);
188
+ lines.push(` ${t("billing.recap.subtotal")} : ${formatDuration(ticket.totalMinutes)} = ${ticketAmount.toFixed(2)}${t("billing.recap.eur")}${ticket.billedAmount ? t("billing.recap.forfait") : ""}`);
188
189
  }
189
190
  if (ticket.aiSummary) {
190
- lines.push(" Detail des actions :");
191
+ lines.push(` ${t("billing.recap.actionDetails")} :`);
191
192
  for (const detailLine of ticket.aiSummary.split("\n")) {
192
193
  lines.push(` ${detailLine}`);
193
194
  }
194
195
  }
195
196
  }
196
197
  const groupAmount = group.totalBilledAmount > 0 ? group.totalBilledAmount : Number(formatAmount(group.totalMinutes, rate));
197
- lines.push(` Total projet : ${formatDuration(group.totalMinutes)} = ${groupAmount.toFixed(2)} EUR`);
198
+ lines.push(` ${t("billing.recap.projectTotal")} : ${formatDuration(group.totalMinutes)} = ${groupAmount.toFixed(2)}${t("billing.recap.eur")}`);
198
199
  }
199
200
  lines.push("");
200
201
  lines.push("=".repeat(50));
201
- lines.push(`TOTAL GENERAL : ${formatDuration(data.grandTotalMinutes)} = ${formatAmount(data.grandTotalMinutes, rate)} EUR`);
202
+ lines.push(`${t("billing.recap.grandTotal")} : ${formatDuration(data.grandTotalMinutes)} = ${formatAmount(data.grandTotalMinutes, rate)}${t("billing.recap.eur")}`);
202
203
  navigator.clipboard.writeText(lines.join("\n"));
203
204
  setCopied(true);
204
205
  setTimeout(() => setCopied(false), 2e3);
205
- }, [data, visibleGroups, from, to, rate]);
206
+ }, [data, visibleGroups, from, to, rate, t]);
206
207
  const copyTicketSummary = React.useCallback((ticket) => {
207
208
  const lines = [`${ticket.ticketNumber} \u2014 ${ticket.subject}`];
208
209
  if (ticket.aiSummary) lines.push("", ticket.aiSummary);
@@ -258,24 +259,17 @@ const BillingClient = () => {
258
259
  ] }),
259
260
  data && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
260
261
  data.ticketsWithoutTime > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.warningBanner, children: [
261
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
262
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: data.ticketsWithoutTime }),
263
- " ticket",
264
- data.ticketsWithoutTime > 1 ? "s" : "",
265
- " actif",
266
- data.ticketsWithoutTime > 1 ? "s" : "",
267
- " sans temps saisi sur la periode."
268
- ] }),
262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: data.ticketsWithoutTime > 1 ? t("billing.warningTicketsPlural", { count: String(data.ticketsWithoutTime) }) : t("billing.warningTickets", { count: String(data.ticketsWithoutTime) }) }),
269
263
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: styles__default.default.toggleLabel, children: [
270
264
  /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: hideEmpty, onChange: (e) => setHideEmpty(e.target.checked) }),
271
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Masquer ces tickets" })
265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("billing.hideEmpty") })
272
266
  ] })
273
267
  ] }),
274
268
  visibleGroups.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.empty, children: t("billing.empty") }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
275
269
  visibleGroups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.groupCard, children: [
276
270
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.groupHeader, children: [
277
271
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
278
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.groupName, children: group.project?.name || "Sans projet" }),
272
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.groupName, children: group.project?.name || t("billing.noProject") }),
279
273
  group.client?.company && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.groupClient, children: [
280
274
  "\u2014 ",
281
275
  group.client.company
@@ -286,7 +280,8 @@ const BillingClient = () => {
286
280
  group.totalBilledAmount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
287
281
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.groupAmountBilled, children: [
288
282
  group.totalBilledAmount.toFixed(2),
289
- " EUR facture"
283
+ " EUR ",
284
+ t("billing.groupAmountBilled")
290
285
  ] }),
291
286
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.groupAmountStrike, children: [
292
287
  formatAmount(group.totalMinutes, rate),
@@ -316,16 +311,16 @@ const BillingClient = () => {
316
311
  });
317
312
  },
318
313
  className: styles__default.default.checkbox,
319
- title: "Tout cocher/decocher"
314
+ title: t("billing.tableCheckboxTitle")
320
315
  }
321
316
  ) }),
322
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: "N\xB0 Ticket" }),
323
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.thLeft, children: "Sujet" }),
324
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: "Date" }),
325
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: "Duree" }),
326
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.thLeft, children: "Description" }),
327
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: "Montant" }),
328
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: "Facture" })
317
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: t("billing.tableHeaders.number") }),
318
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.thLeft, children: t("billing.tableHeaders.subject") }),
319
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: t("billing.tableHeaders.date") }),
320
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: t("billing.tableHeaders.duration") }),
321
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.thLeft, children: t("billing.tableHeaders.description") }),
322
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: t("billing.tableHeaders.amount") }),
323
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: styles__default.default.th, children: t("billing.tableHeaders.billed") })
329
324
  ] }) }),
330
325
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: group.tickets.map((ticket) => {
331
326
  const isBilled = billedTickets.has(ticket.id);
@@ -340,7 +335,7 @@ const BillingClient = () => {
340
335
  checked: isBilled,
341
336
  onChange: () => toggleBilled(ticket.id),
342
337
  className: styles__default.default.checkbox,
343
- title: isBilled ? "Marquer comme non facture" : "Marquer comme facture"
338
+ title: isBilled ? t("billing.markUnbilledTitle") : t("billing.markBilledTitle")
344
339
  }
345
340
  ) }),
346
341
  /* @__PURE__ */ jsxRuntime.jsxs("td", { className: `${styles__default.default.td} ${styles__default.default.bold} ${isBilled ? styles__default.default.strikethrough : ""}`, rowSpan, children: [
@@ -350,7 +345,7 @@ const BillingClient = () => {
350
345
  {
351
346
  className: styles__default.default.summaryBtn,
352
347
  onClick: () => toggleSummary(ticket.id),
353
- title: isExpanded ? "Masquer le detail" : "Afficher le detail IA",
348
+ title: isExpanded ? t("billing.summaryToggleHide") : t("billing.summaryToggleShow"),
354
349
  children: [
355
350
  isExpanded ? "\u25BC" : "\u25B6",
356
351
  " IA"
@@ -360,7 +355,7 @@ const BillingClient = () => {
360
355
  ] }),
361
356
  /* @__PURE__ */ jsxRuntime.jsxs("td", { className: `${styles__default.default.tdLeft} ${isBilled ? styles__default.default.strikethrough : ""}`, rowSpan, children: [
362
357
  ticket.subject,
363
- ticket.hasNoTimeEntries && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.noTimeBadge, title: "Aucun temps saisi sur la periode", children: "\u26A0 Aucun temps saisi" })
358
+ ticket.hasNoTimeEntries && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.noTimeBadge, title: t("billing.noTimeBadgeTitle"), children: t("billing.noTimeBadge") })
364
359
  ] })
365
360
  ] });
366
361
  const rows = [];
@@ -372,7 +367,7 @@ const BillingClient = () => {
372
367
  className: `${isBilled ? styles__default.default.tableRowBilled : styles__default.default.tableRowNoTime}`,
373
368
  children: [
374
369
  renderTicketHeaderCells(),
375
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: `${styles__default.default.td} ${styles__default.default.secondary}`, colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "Pas de saisie de temps. Verifier si du temps a ete oublie." }) })
370
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: `${styles__default.default.td} ${styles__default.default.secondary}`, colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: t("billing.noTimeRow") }) })
376
371
  ]
377
372
  },
378
373
  `${ticket.id}-empty`
@@ -413,18 +408,15 @@ const BillingClient = () => {
413
408
  rows.push(
414
409
  /* @__PURE__ */ jsxRuntime.jsx("tr", { className: styles__default.default.summaryRow, children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan: 8, className: styles__default.default.summaryCell, children: [
415
410
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.summaryHeader, children: [
416
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Synthese IA des actions" }),
411
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: t("billing.summaryTitle") }),
417
412
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.summaryActions, children: [
418
- ticket.aiSummaryGeneratedAt && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.summaryMeta, children: [
419
- "Genere le ",
420
- new Date(ticket.aiSummaryGeneratedAt).toLocaleString("fr-FR")
421
- ] }),
413
+ ticket.aiSummaryGeneratedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.summaryMeta, children: t("billing.summaryGeneratedAt", { date: new Date(ticket.aiSummaryGeneratedAt).toLocaleString(dateLocale.DATE_LOCALE) }) }),
422
414
  ticket.aiSummary && /* @__PURE__ */ jsxRuntime.jsx(
423
415
  "button",
424
416
  {
425
417
  className: styles__default.default.summaryAction,
426
418
  onClick: () => copyTicketSummary(ticket),
427
- children: "Copier"
419
+ children: t("billing.summaryCopy")
428
420
  }
429
421
  ),
430
422
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -433,12 +425,12 @@ const BillingClient = () => {
433
425
  className: styles__default.default.summaryAction,
434
426
  onClick: () => requestSynthesis(ticket.id, !!ticket.aiSummary),
435
427
  disabled: isRegenerating,
436
- children: isRegenerating ? "Generation..." : ticket.aiSummary ? "Regenerer" : "Generer"
428
+ children: isRegenerating ? t("billing.summaryGenerating") : ticket.aiSummary ? t("billing.summaryRegenerate") : t("billing.summaryGenerate")
437
429
  }
438
430
  )
439
431
  ] })
440
432
  ] }),
441
- ticket.aiSummaryStatus === "pending" && !ticket.aiSummary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.summaryEmpty, children: 'Generation en cours en arriere-plan. Cliquer sur "Generer" pour forcer.' }) : ticket.aiSummary ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: styles__default.default.summaryText, children: ticket.aiSummary }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.summaryEmpty, children: 'Pas de synthese disponible. La synthese est generee automatiquement quand le ticket passe en "resolu", ou manuellement via le bouton "Generer".' })
433
+ ticket.aiSummaryStatus === "pending" && !ticket.aiSummary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.summaryEmpty, children: t("billing.summaryPending") }) : ticket.aiSummary ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: styles__default.default.summaryText, children: ticket.aiSummary }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.summaryEmpty, children: t("billing.summaryEmpty") })
442
434
  ] }) }, `${ticket.id}-summary`)
443
435
  );
444
436
  }
@@ -449,21 +441,15 @@ const BillingClient = () => {
449
441
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.grandTotal, children: [
450
442
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
451
443
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.totalMeta, children: [
452
- totalTickets,
453
- " ticket",
454
- totalTickets > 1 ? "s" : "",
455
- " affiche",
456
- totalTickets > 1 ? "s" : "",
457
- billedTickets.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.totalChecked, children: [
458
- "(",
459
- allTicketIds.filter((id) => billedTickets.has(id)).length,
460
- " coche",
461
- allTicketIds.filter((id) => billedTickets.has(id)).length > 1 ? "s" : "",
462
- ")"
463
- ] })
444
+ totalTickets > 1 ? t("billing.ticketsDisplayedPlural", { count: String(totalTickets) }) : t("billing.ticketsDisplayed", { count: String(totalTickets) }),
445
+ billedTickets.size > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.totalChecked, children: (() => {
446
+ const checkedCount = allTicketIds.filter((id) => billedTickets.has(id)).length;
447
+ return checkedCount > 1 ? `(${t("billing.ticketsCheckedPlural", { count: String(checkedCount) })})` : `(${t("billing.ticketsChecked", { count: String(checkedCount) })})`;
448
+ })() })
464
449
  ] }),
465
450
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.totalAmount, children: [
466
- "Total : ",
451
+ t("billing.totalLabel"),
452
+ " : ",
467
453
  formatDuration(data.grandTotalMinutes),
468
454
  " =",
469
455
  " ",
@@ -2,6 +2,7 @@
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import React, { useState, useCallback } from 'react';
4
4
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
5
+ import { DATE_LOCALE } from '../shared/dateLocale.js';
5
6
  import styles from '../../styles/BillingView.module.scss';
6
7
 
7
8
  function formatDuration(minutes) {
@@ -162,41 +163,41 @@ const BillingClient = () => {
162
163
  const copyRecap = useCallback(() => {
163
164
  if (!data) return;
164
165
  const lines = [];
165
- lines.push(`PRE-FACTURATION \u2014 Du ${from} au ${to}`);
166
- lines.push(`Taux horaire : ${rate} EUR/h`);
166
+ lines.push(t("billing.recap.header", { from, to }));
167
+ lines.push(t("billing.recap.hourlyRate", { rate: String(rate) }));
167
168
  lines.push("=".repeat(50));
168
169
  for (const group of visibleGroups) {
169
170
  lines.push("");
170
- lines.push(`PROJET : ${group.project?.name || "Sans projet"}`);
171
- if (group.client?.company) lines.push(`Client : ${group.client.company}`);
171
+ lines.push(group.project?.name ? t("billing.recap.project", { name: group.project.name }) : t("billing.recap.project", { name: t("billing.recap.noProject") }));
172
+ if (group.client?.company) lines.push(t("billing.recap.client", { company: group.client.company }));
172
173
  lines.push("-".repeat(40));
173
174
  for (const ticket of group.tickets) {
174
- const flag = ticket.hasNoTimeEntries ? " [AUCUN TEMPS SAISI]" : "";
175
+ const flag = ticket.hasNoTimeEntries ? t("billing.recap.noTime") : "";
175
176
  lines.push(` ${ticket.ticketNumber} \u2014 ${ticket.subject}${flag}`);
176
177
  for (const entry of ticket.entries) {
177
178
  lines.push(` ${entry.date} | ${formatDuration(entry.duration)} | ${entry.description || "-"}`);
178
179
  }
179
180
  if (ticket.entries.length > 0) {
180
181
  const ticketAmount = ticket.billedAmount || Number(formatAmount(ticket.totalMinutes, rate));
181
- lines.push(` Sous-total : ${formatDuration(ticket.totalMinutes)} = ${ticketAmount.toFixed(2)} EUR${ticket.billedAmount ? " (forfait)" : ""}`);
182
+ lines.push(` ${t("billing.recap.subtotal")} : ${formatDuration(ticket.totalMinutes)} = ${ticketAmount.toFixed(2)}${t("billing.recap.eur")}${ticket.billedAmount ? t("billing.recap.forfait") : ""}`);
182
183
  }
183
184
  if (ticket.aiSummary) {
184
- lines.push(" Detail des actions :");
185
+ lines.push(` ${t("billing.recap.actionDetails")} :`);
185
186
  for (const detailLine of ticket.aiSummary.split("\n")) {
186
187
  lines.push(` ${detailLine}`);
187
188
  }
188
189
  }
189
190
  }
190
191
  const groupAmount = group.totalBilledAmount > 0 ? group.totalBilledAmount : Number(formatAmount(group.totalMinutes, rate));
191
- lines.push(` Total projet : ${formatDuration(group.totalMinutes)} = ${groupAmount.toFixed(2)} EUR`);
192
+ lines.push(` ${t("billing.recap.projectTotal")} : ${formatDuration(group.totalMinutes)} = ${groupAmount.toFixed(2)}${t("billing.recap.eur")}`);
192
193
  }
193
194
  lines.push("");
194
195
  lines.push("=".repeat(50));
195
- lines.push(`TOTAL GENERAL : ${formatDuration(data.grandTotalMinutes)} = ${formatAmount(data.grandTotalMinutes, rate)} EUR`);
196
+ lines.push(`${t("billing.recap.grandTotal")} : ${formatDuration(data.grandTotalMinutes)} = ${formatAmount(data.grandTotalMinutes, rate)}${t("billing.recap.eur")}`);
196
197
  navigator.clipboard.writeText(lines.join("\n"));
197
198
  setCopied(true);
198
199
  setTimeout(() => setCopied(false), 2e3);
199
- }, [data, visibleGroups, from, to, rate]);
200
+ }, [data, visibleGroups, from, to, rate, t]);
200
201
  const copyTicketSummary = useCallback((ticket) => {
201
202
  const lines = [`${ticket.ticketNumber} \u2014 ${ticket.subject}`];
202
203
  if (ticket.aiSummary) lines.push("", ticket.aiSummary);
@@ -252,24 +253,17 @@ const BillingClient = () => {
252
253
  ] }),
253
254
  data && /* @__PURE__ */ jsxs(Fragment, { children: [
254
255
  data.ticketsWithoutTime > 0 && /* @__PURE__ */ jsxs("div", { className: styles.warningBanner, children: [
255
- /* @__PURE__ */ jsxs("span", { children: [
256
- /* @__PURE__ */ jsx("strong", { children: data.ticketsWithoutTime }),
257
- " ticket",
258
- data.ticketsWithoutTime > 1 ? "s" : "",
259
- " actif",
260
- data.ticketsWithoutTime > 1 ? "s" : "",
261
- " sans temps saisi sur la periode."
262
- ] }),
256
+ /* @__PURE__ */ jsx("span", { children: data.ticketsWithoutTime > 1 ? t("billing.warningTicketsPlural", { count: String(data.ticketsWithoutTime) }) : t("billing.warningTickets", { count: String(data.ticketsWithoutTime) }) }),
263
257
  /* @__PURE__ */ jsxs("label", { className: styles.toggleLabel, children: [
264
258
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: hideEmpty, onChange: (e) => setHideEmpty(e.target.checked) }),
265
- /* @__PURE__ */ jsx("span", { children: "Masquer ces tickets" })
259
+ /* @__PURE__ */ jsx("span", { children: t("billing.hideEmpty") })
266
260
  ] })
267
261
  ] }),
268
262
  visibleGroups.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.empty, children: t("billing.empty") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
269
263
  visibleGroups.map((group, gi) => /* @__PURE__ */ jsxs("div", { className: styles.groupCard, children: [
270
264
  /* @__PURE__ */ jsxs("div", { className: styles.groupHeader, children: [
271
265
  /* @__PURE__ */ jsxs("div", { children: [
272
- /* @__PURE__ */ jsx("span", { className: styles.groupName, children: group.project?.name || "Sans projet" }),
266
+ /* @__PURE__ */ jsx("span", { className: styles.groupName, children: group.project?.name || t("billing.noProject") }),
273
267
  group.client?.company && /* @__PURE__ */ jsxs("span", { className: styles.groupClient, children: [
274
268
  "\u2014 ",
275
269
  group.client.company
@@ -280,7 +274,8 @@ const BillingClient = () => {
280
274
  group.totalBilledAmount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
281
275
  /* @__PURE__ */ jsxs("div", { className: styles.groupAmountBilled, children: [
282
276
  group.totalBilledAmount.toFixed(2),
283
- " EUR facture"
277
+ " EUR ",
278
+ t("billing.groupAmountBilled")
284
279
  ] }),
285
280
  /* @__PURE__ */ jsxs("div", { className: styles.groupAmountStrike, children: [
286
281
  formatAmount(group.totalMinutes, rate),
@@ -310,16 +305,16 @@ const BillingClient = () => {
310
305
  });
311
306
  },
312
307
  className: styles.checkbox,
313
- title: "Tout cocher/decocher"
308
+ title: t("billing.tableCheckboxTitle")
314
309
  }
315
310
  ) }),
316
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "N\xB0 Ticket" }),
317
- /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: "Sujet" }),
318
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Date" }),
319
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Duree" }),
320
- /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: "Description" }),
321
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Montant" }),
322
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Facture" })
311
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: t("billing.tableHeaders.number") }),
312
+ /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: t("billing.tableHeaders.subject") }),
313
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: t("billing.tableHeaders.date") }),
314
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: t("billing.tableHeaders.duration") }),
315
+ /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: t("billing.tableHeaders.description") }),
316
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: t("billing.tableHeaders.amount") }),
317
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: t("billing.tableHeaders.billed") })
323
318
  ] }) }),
324
319
  /* @__PURE__ */ jsx("tbody", { children: group.tickets.map((ticket) => {
325
320
  const isBilled = billedTickets.has(ticket.id);
@@ -334,7 +329,7 @@ const BillingClient = () => {
334
329
  checked: isBilled,
335
330
  onChange: () => toggleBilled(ticket.id),
336
331
  className: styles.checkbox,
337
- title: isBilled ? "Marquer comme non facture" : "Marquer comme facture"
332
+ title: isBilled ? t("billing.markUnbilledTitle") : t("billing.markBilledTitle")
338
333
  }
339
334
  ) }),
340
335
  /* @__PURE__ */ jsxs("td", { className: `${styles.td} ${styles.bold} ${isBilled ? styles.strikethrough : ""}`, rowSpan, children: [
@@ -344,7 +339,7 @@ const BillingClient = () => {
344
339
  {
345
340
  className: styles.summaryBtn,
346
341
  onClick: () => toggleSummary(ticket.id),
347
- title: isExpanded ? "Masquer le detail" : "Afficher le detail IA",
342
+ title: isExpanded ? t("billing.summaryToggleHide") : t("billing.summaryToggleShow"),
348
343
  children: [
349
344
  isExpanded ? "\u25BC" : "\u25B6",
350
345
  " IA"
@@ -354,7 +349,7 @@ const BillingClient = () => {
354
349
  ] }),
355
350
  /* @__PURE__ */ jsxs("td", { className: `${styles.tdLeft} ${isBilled ? styles.strikethrough : ""}`, rowSpan, children: [
356
351
  ticket.subject,
357
- ticket.hasNoTimeEntries && /* @__PURE__ */ jsx("span", { className: styles.noTimeBadge, title: "Aucun temps saisi sur la periode", children: "\u26A0 Aucun temps saisi" })
352
+ ticket.hasNoTimeEntries && /* @__PURE__ */ jsx("span", { className: styles.noTimeBadge, title: t("billing.noTimeBadgeTitle"), children: t("billing.noTimeBadge") })
358
353
  ] })
359
354
  ] });
360
355
  const rows = [];
@@ -366,7 +361,7 @@ const BillingClient = () => {
366
361
  className: `${isBilled ? styles.tableRowBilled : styles.tableRowNoTime}`,
367
362
  children: [
368
363
  renderTicketHeaderCells(),
369
- /* @__PURE__ */ jsx("td", { className: `${styles.td} ${styles.secondary}`, colSpan: 5, children: /* @__PURE__ */ jsx("em", { children: "Pas de saisie de temps. Verifier si du temps a ete oublie." }) })
364
+ /* @__PURE__ */ jsx("td", { className: `${styles.td} ${styles.secondary}`, colSpan: 5, children: /* @__PURE__ */ jsx("em", { children: t("billing.noTimeRow") }) })
370
365
  ]
371
366
  },
372
367
  `${ticket.id}-empty`
@@ -407,18 +402,15 @@ const BillingClient = () => {
407
402
  rows.push(
408
403
  /* @__PURE__ */ jsx("tr", { className: styles.summaryRow, children: /* @__PURE__ */ jsxs("td", { colSpan: 8, className: styles.summaryCell, children: [
409
404
  /* @__PURE__ */ jsxs("div", { className: styles.summaryHeader, children: [
410
- /* @__PURE__ */ jsx("strong", { children: "Synthese IA des actions" }),
405
+ /* @__PURE__ */ jsx("strong", { children: t("billing.summaryTitle") }),
411
406
  /* @__PURE__ */ jsxs("div", { className: styles.summaryActions, children: [
412
- ticket.aiSummaryGeneratedAt && /* @__PURE__ */ jsxs("span", { className: styles.summaryMeta, children: [
413
- "Genere le ",
414
- new Date(ticket.aiSummaryGeneratedAt).toLocaleString("fr-FR")
415
- ] }),
407
+ ticket.aiSummaryGeneratedAt && /* @__PURE__ */ jsx("span", { className: styles.summaryMeta, children: t("billing.summaryGeneratedAt", { date: new Date(ticket.aiSummaryGeneratedAt).toLocaleString(DATE_LOCALE) }) }),
416
408
  ticket.aiSummary && /* @__PURE__ */ jsx(
417
409
  "button",
418
410
  {
419
411
  className: styles.summaryAction,
420
412
  onClick: () => copyTicketSummary(ticket),
421
- children: "Copier"
413
+ children: t("billing.summaryCopy")
422
414
  }
423
415
  ),
424
416
  /* @__PURE__ */ jsx(
@@ -427,12 +419,12 @@ const BillingClient = () => {
427
419
  className: styles.summaryAction,
428
420
  onClick: () => requestSynthesis(ticket.id, !!ticket.aiSummary),
429
421
  disabled: isRegenerating,
430
- children: isRegenerating ? "Generation..." : ticket.aiSummary ? "Regenerer" : "Generer"
422
+ children: isRegenerating ? t("billing.summaryGenerating") : ticket.aiSummary ? t("billing.summaryRegenerate") : t("billing.summaryGenerate")
431
423
  }
432
424
  )
433
425
  ] })
434
426
  ] }),
435
- ticket.aiSummaryStatus === "pending" && !ticket.aiSummary ? /* @__PURE__ */ jsx("div", { className: styles.summaryEmpty, children: 'Generation en cours en arriere-plan. Cliquer sur "Generer" pour forcer.' }) : ticket.aiSummary ? /* @__PURE__ */ jsx("pre", { className: styles.summaryText, children: ticket.aiSummary }) : /* @__PURE__ */ jsx("div", { className: styles.summaryEmpty, children: 'Pas de synthese disponible. La synthese est generee automatiquement quand le ticket passe en "resolu", ou manuellement via le bouton "Generer".' })
427
+ ticket.aiSummaryStatus === "pending" && !ticket.aiSummary ? /* @__PURE__ */ jsx("div", { className: styles.summaryEmpty, children: t("billing.summaryPending") }) : ticket.aiSummary ? /* @__PURE__ */ jsx("pre", { className: styles.summaryText, children: ticket.aiSummary }) : /* @__PURE__ */ jsx("div", { className: styles.summaryEmpty, children: t("billing.summaryEmpty") })
436
428
  ] }) }, `${ticket.id}-summary`)
437
429
  );
438
430
  }
@@ -443,21 +435,15 @@ const BillingClient = () => {
443
435
  /* @__PURE__ */ jsxs("div", { className: styles.grandTotal, children: [
444
436
  /* @__PURE__ */ jsxs("div", { children: [
445
437
  /* @__PURE__ */ jsxs("div", { className: styles.totalMeta, children: [
446
- totalTickets,
447
- " ticket",
448
- totalTickets > 1 ? "s" : "",
449
- " affiche",
450
- totalTickets > 1 ? "s" : "",
451
- billedTickets.size > 0 && /* @__PURE__ */ jsxs("span", { className: styles.totalChecked, children: [
452
- "(",
453
- allTicketIds.filter((id) => billedTickets.has(id)).length,
454
- " coche",
455
- allTicketIds.filter((id) => billedTickets.has(id)).length > 1 ? "s" : "",
456
- ")"
457
- ] })
438
+ totalTickets > 1 ? t("billing.ticketsDisplayedPlural", { count: String(totalTickets) }) : t("billing.ticketsDisplayed", { count: String(totalTickets) }),
439
+ billedTickets.size > 0 && /* @__PURE__ */ jsx("span", { className: styles.totalChecked, children: (() => {
440
+ const checkedCount = allTicketIds.filter((id) => billedTickets.has(id)).length;
441
+ return checkedCount > 1 ? `(${t("billing.ticketsCheckedPlural", { count: String(checkedCount) })})` : `(${t("billing.ticketsChecked", { count: String(checkedCount) })})`;
442
+ })() })
458
443
  ] }),
459
444
  /* @__PURE__ */ jsxs("div", { className: styles.totalAmount, children: [
460
- "Total : ",
445
+ t("billing.totalLabel"),
446
+ " : ",
461
447
  formatDuration(data.grandTotalMinutes),
462
448
  " =",
463
449
  " ",
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var react = require('react');
5
5
  var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
6
+ var dateLocale = require('../shared/dateLocale');
6
7
  var styles = require('../../styles/ChatView.module.scss');
7
8
 
8
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -213,7 +214,7 @@ const ChatViewClient = () => {
213
214
  getClientCompany(s.client) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.sessionCompany, children: getClientCompany(s.client) }),
214
215
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.sessionPreview, children: s.lastMessage.startsWith("Note:") ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.sessionRating, children: s.lastMessage.match(/[★☆]+/)?.[0] || "\u2B50" }) : s.lastMessage }),
215
216
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.sessionMeta, children: [
216
- new Date(s.lastMessageAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
217
+ new Date(s.lastMessageAt).toLocaleString(dateLocale.DATE_LOCALE, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
217
218
  " \xB7 ",
218
219
  s.messageCount,
219
220
  " ",
@@ -240,12 +241,12 @@ const ChatViewClient = () => {
240
241
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.ratingMeta, children: [
241
242
  t("chat.clientReview"),
242
243
  " \xB7 ",
243
- new Date(msg.createdAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })
244
+ new Date(msg.createdAt).toLocaleString(dateLocale.DATE_LOCALE, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })
244
245
  ] })
245
246
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.bubbleSystem, children: msg.message }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles__default.default.bubble} ${msg.senderType === "agent" ? styles__default.default.bubbleAgent : styles__default.default.bubbleClient}`, children: [
246
247
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.bubbleSender, children: msg.senderType === "agent" ? msg.agent ? `${msg.agent.firstName || t("chat.agent")}` : t("chat.you") : t("chat.clientLabel") }),
247
248
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.bubbleBody, children: msg.message }),
248
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.bubbleTime, children: new Date(msg.createdAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }) })
249
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.bubbleTime, children: new Date(msg.createdAt).toLocaleTimeString(dateLocale.DATE_LOCALE, { hour: "2-digit", minute: "2-digit" }) })
249
250
  ] })
250
251
  },
251
252
  msg.id
@@ -2,6 +2,7 @@
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { useState, useRef, useCallback, useEffect } from 'react';
4
4
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
5
+ import { DATE_LOCALE } from '../shared/dateLocale.js';
5
6
  import styles from '../../styles/ChatView.module.scss';
6
7
 
7
8
  const ChatViewClient = () => {
@@ -208,7 +209,7 @@ const ChatViewClient = () => {
208
209
  getClientCompany(s.client) && /* @__PURE__ */ jsx("div", { className: styles.sessionCompany, children: getClientCompany(s.client) }),
209
210
  /* @__PURE__ */ jsx("div", { className: styles.sessionPreview, children: s.lastMessage.startsWith("Note:") ? /* @__PURE__ */ jsx("span", { className: styles.sessionRating, children: s.lastMessage.match(/[★☆]+/)?.[0] || "\u2B50" }) : s.lastMessage }),
210
211
  /* @__PURE__ */ jsxs("div", { className: styles.sessionMeta, children: [
211
- new Date(s.lastMessageAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
212
+ new Date(s.lastMessageAt).toLocaleString(DATE_LOCALE, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
212
213
  " \xB7 ",
213
214
  s.messageCount,
214
215
  " ",
@@ -235,12 +236,12 @@ const ChatViewClient = () => {
235
236
  /* @__PURE__ */ jsxs("div", { className: styles.ratingMeta, children: [
236
237
  t("chat.clientReview"),
237
238
  " \xB7 ",
238
- new Date(msg.createdAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })
239
+ new Date(msg.createdAt).toLocaleString(DATE_LOCALE, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })
239
240
  ] })
240
241
  ] }) : /* @__PURE__ */ jsx("div", { className: styles.bubbleSystem, children: msg.message }) : /* @__PURE__ */ jsxs("div", { className: `${styles.bubble} ${msg.senderType === "agent" ? styles.bubbleAgent : styles.bubbleClient}`, children: [
241
242
  /* @__PURE__ */ jsx("div", { className: styles.bubbleSender, children: msg.senderType === "agent" ? msg.agent ? `${msg.agent.firstName || t("chat.agent")}` : t("chat.you") : t("chat.clientLabel") }),
242
243
  /* @__PURE__ */ jsx("div", { className: styles.bubbleBody, children: msg.message }),
243
- /* @__PURE__ */ jsx("div", { className: styles.bubbleTime, children: new Date(msg.createdAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }) })
244
+ /* @__PURE__ */ jsx("div", { className: styles.bubbleTime, children: new Date(msg.createdAt).toLocaleTimeString(DATE_LOCALE, { hour: "2-digit", minute: "2-digit" }) })
244
245
  ] })
245
246
  },
246
247
  msg.id
@@ -36,14 +36,14 @@ function formatDuration(minutes) {
36
36
  if (m === 0) return `${h}h`;
37
37
  return `${h}h${m}m`;
38
38
  }
39
- function timeAgo(dateStr) {
39
+ function timeAgo(dateStr, tFn) {
40
40
  const diff = Date.now() - new Date(dateStr).getTime();
41
41
  const days = Math.floor(diff / 864e5);
42
- if (days === 0) return "Aujourd'hui";
43
- if (days === 1) return "Hier";
44
- if (days < 30) return `Il y a ${days}j`;
42
+ if (days === 0) return tFn("crm.timeAgo.today");
43
+ if (days === 1) return tFn("crm.timeAgo.yesterday");
44
+ if (days < 30) return tFn("crm.timeAgo.daysAgo", { count: days });
45
45
  const months = Math.floor(days / 30);
46
- return `Il y a ${months} mois`;
46
+ return tFn("crm.timeAgo.monthsAgo", { count: months });
47
47
  }
48
48
  function getStatColorClass(key, detail) {
49
49
  switch (key) {
@@ -336,7 +336,7 @@ Cette action est irr\xE9versible.`)) return;
336
336
  { key: "open", label: t("crm.stats.openTickets"), value: String(detail.stats.openTickets) },
337
337
  { key: "resolved", label: t("crm.stats.resolvedTickets"), value: String(detail.stats.resolvedTickets) },
338
338
  { key: "time", label: t("crm.stats.timeSpent"), value: formatDuration(detail.stats.totalTimeMinutes) },
339
- { key: "activity", label: t("crm.stats.lastActivity"), value: detail.stats.lastActivity ? timeAgo(detail.stats.lastActivity) : "-" }
339
+ { key: "activity", label: t("crm.stats.lastActivity"), value: detail.stats.lastActivity ? timeAgo(detail.stats.lastActivity, t) : "-" }
340
340
  ].map((stat) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.statCard, children: [
341
341
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.statLabel, children: stat.label }),
342
342
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${styles__default.default.statValue} ${getStatColorClass(stat.key, detail.stats)}`, children: stat.value })
@@ -446,7 +446,7 @@ Cette action est irr\xE9versible.`)) return;
446
446
  /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/admin/collections/tickets/${tk.id}`, className: styles__default.default.ticketLink, children: tk.ticketNumber }) }),
447
447
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.ticketSubject, children: tk.subject }),
448
448
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.ticketStatusCell, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${styles__default.default.badge} ${styles__default.default[statusBadgeClass[tk.status] || "badgeAccent"]}`, children: t(statusLabelKeys[tk.status] || "ticket.status.open") }) }),
449
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.ticketDate, children: timeAgo(tk.createdAt) })
449
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.ticketDate, children: timeAgo(tk.createdAt, t) })
450
450
  ] }, tk.id)) })
451
451
  ] })
452
452
  ] })
@@ -31,14 +31,14 @@ function formatDuration(minutes) {
31
31
  if (m === 0) return `${h}h`;
32
32
  return `${h}h${m}m`;
33
33
  }
34
- function timeAgo(dateStr) {
34
+ function timeAgo(dateStr, tFn) {
35
35
  const diff = Date.now() - new Date(dateStr).getTime();
36
36
  const days = Math.floor(diff / 864e5);
37
- if (days === 0) return "Aujourd'hui";
38
- if (days === 1) return "Hier";
39
- if (days < 30) return `Il y a ${days}j`;
37
+ if (days === 0) return tFn("crm.timeAgo.today");
38
+ if (days === 1) return tFn("crm.timeAgo.yesterday");
39
+ if (days < 30) return tFn("crm.timeAgo.daysAgo", { count: days });
40
40
  const months = Math.floor(days / 30);
41
- return `Il y a ${months} mois`;
41
+ return tFn("crm.timeAgo.monthsAgo", { count: months });
42
42
  }
43
43
  function getStatColorClass(key, detail) {
44
44
  switch (key) {
@@ -331,7 +331,7 @@ Cette action est irr\xE9versible.`)) return;
331
331
  { key: "open", label: t("crm.stats.openTickets"), value: String(detail.stats.openTickets) },
332
332
  { key: "resolved", label: t("crm.stats.resolvedTickets"), value: String(detail.stats.resolvedTickets) },
333
333
  { key: "time", label: t("crm.stats.timeSpent"), value: formatDuration(detail.stats.totalTimeMinutes) },
334
- { key: "activity", label: t("crm.stats.lastActivity"), value: detail.stats.lastActivity ? timeAgo(detail.stats.lastActivity) : "-" }
334
+ { key: "activity", label: t("crm.stats.lastActivity"), value: detail.stats.lastActivity ? timeAgo(detail.stats.lastActivity, t) : "-" }
335
335
  ].map((stat) => /* @__PURE__ */ jsxs("div", { className: styles.statCard, children: [
336
336
  /* @__PURE__ */ jsx("div", { className: styles.statLabel, children: stat.label }),
337
337
  /* @__PURE__ */ jsx("div", { className: `${styles.statValue} ${getStatColorClass(stat.key, detail.stats)}`, children: stat.value })
@@ -441,7 +441,7 @@ Cette action est irr\xE9versible.`)) return;
441
441
  /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("a", { href: `/admin/collections/tickets/${tk.id}`, className: styles.ticketLink, children: tk.ticketNumber }) }),
442
442
  /* @__PURE__ */ jsx("td", { className: styles.ticketSubject, children: tk.subject }),
443
443
  /* @__PURE__ */ jsx("td", { className: styles.ticketStatusCell, children: /* @__PURE__ */ jsx("span", { className: `${styles.badge} ${styles[statusBadgeClass[tk.status] || "badgeAccent"]}`, children: t(statusLabelKeys[tk.status] || "ticket.status.open") }) }),
444
- /* @__PURE__ */ jsx("td", { className: styles.ticketDate, children: timeAgo(tk.createdAt) })
444
+ /* @__PURE__ */ jsx("td", { className: styles.ticketDate, children: timeAgo(tk.createdAt, t) })
445
445
  ] }, tk.id)) })
446
446
  ] })
447
447
  ] })