@consilioweb/payload-support 0.8.2 → 0.9.4

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 (50) hide show
  1. package/dist/components/RichTextEditor/index.cjs +233 -0
  2. package/dist/components/RichTextEditor/index.js +232 -0
  3. package/dist/components/TicketConversation/components/CodeBlock.cjs +24 -7
  4. package/dist/components/TicketConversation/components/CodeBlock.js +23 -9
  5. package/dist/index.cjs +19 -1
  6. package/dist/index.js +19 -1
  7. package/dist/views/BillingView/client.cjs +260 -103
  8. package/dist/views/BillingView/client.js +259 -103
  9. package/dist/views/ChatView/client.cjs +184 -137
  10. package/dist/views/ChatView/client.js +180 -137
  11. package/dist/views/CrmView/client.cjs +270 -122
  12. package/dist/views/CrmView/client.js +266 -122
  13. package/dist/views/EmailTrackingView/client.cjs +80 -69
  14. package/dist/views/EmailTrackingView/client.js +80 -70
  15. package/dist/views/ImportConversationView/client.cjs +127 -94
  16. package/dist/views/ImportConversationView/client.js +123 -94
  17. package/dist/views/LogsView/client.cjs +56 -58
  18. package/dist/views/LogsView/client.js +52 -58
  19. package/dist/views/NewTicketView/client.cjs +39 -55
  20. package/dist/views/NewTicketView/client.js +38 -55
  21. package/dist/views/PendingEmailsView/client.cjs +399 -102
  22. package/dist/views/PendingEmailsView/client.js +396 -103
  23. package/dist/views/SupportDashboardView/client.cjs +276 -137
  24. package/dist/views/SupportDashboardView/client.js +275 -137
  25. package/dist/views/TicketDetailView/client.cjs +487 -204
  26. package/dist/views/TicketDetailView/client.js +486 -204
  27. package/dist/views/TicketInboxView/client.cjs +62 -65
  28. package/dist/views/TicketInboxView/client.js +62 -66
  29. package/dist/views/TicketingSettingsView/client.cjs +10 -8
  30. package/dist/views/TicketingSettingsView/client.js +10 -8
  31. package/dist/views/TimeDashboardView/client.cjs +70 -59
  32. package/dist/views/TimeDashboardView/client.js +69 -59
  33. package/package.json +6 -2
  34. package/src/components/RichTextEditor/index.tsx +261 -0
  35. package/src/components/TicketConversation/components/CodeBlock.tsx +53 -14
  36. package/src/plugin.ts +2 -0
  37. package/src/utils/emailTemplate.ts +37 -0
  38. package/src/views/BillingView/client.tsx +362 -69
  39. package/src/views/ChatView/client.tsx +225 -140
  40. package/src/views/CrmView/client.tsx +447 -189
  41. package/src/views/EmailTrackingView/client.tsx +111 -71
  42. package/src/views/ImportConversationView/client.tsx +255 -70
  43. package/src/views/LogsView/client.tsx +85 -50
  44. package/src/views/NewTicketView/client.tsx +37 -53
  45. package/src/views/PendingEmailsView/client.tsx +512 -92
  46. package/src/views/SupportDashboardView/client.tsx +294 -134
  47. package/src/views/TicketDetailView/client.tsx +486 -213
  48. package/src/views/TicketInboxView/client.tsx +52 -61
  49. package/src/views/TicketingSettingsView/client.tsx +10 -9
  50. package/src/views/TimeDashboardView/client.tsx +184 -69
@@ -4,18 +4,23 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
5
  var navigation = require('next/navigation');
6
6
  var Link = require('next/link');
7
+ var index = require('../../components/RichTextEditor/index');
8
+ var CodeBlock = require('../../components/TicketConversation/components/CodeBlock');
9
+ var CodeBlockInserter = require('../../components/TicketConversation/components/CodeBlockInserter');
7
10
  var config = require('../shared/config');
8
11
  var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
12
+ var s = require('../../styles/TicketDetail.module.scss');
9
13
 
10
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
15
 
12
16
  var React__default = /*#__PURE__*/_interopDefault(React);
13
17
  var Link__default = /*#__PURE__*/_interopDefault(Link);
18
+ var s__default = /*#__PURE__*/_interopDefault(s);
14
19
 
15
- const STATUS = {
16
- open: { label: "Ouvert", bg: "#dbeafe", color: "#1e40af" },
17
- waiting_client: { label: "En attente", bg: "#fef3c7", color: "#92400e" },
18
- resolved: { label: "Resolu", bg: "#dcfce7", color: "#166534" }
20
+ const STATUS_STYLE = {
21
+ open: { bg: "#dbeafe", color: "#1e40af" },
22
+ waiting_client: { bg: "#fef3c7", color: "#92400e" },
23
+ resolved: { bg: "#dcfce7", color: "#166534" }
19
24
  };
20
25
  function timeAgo(d) {
21
26
  const date = new Date(d), now = /* @__PURE__ */ new Date();
@@ -33,11 +38,92 @@ function dateLabel(d) {
33
38
  if (date.toDateString() === y.toDateString()) return "Hier";
34
39
  return date.toLocaleDateString("fr-FR", { day: "numeric", month: "long" });
35
40
  }
41
+ const REWRITE_STYLES = [
42
+ { id: "auto", label: "\u270F\uFE0F Auto", desc: "Garde le ton actuel" },
43
+ { id: "tutoyer", label: "\u{1F44B} Tutoyer", desc: "Passe en tu" },
44
+ { id: "vouvoyer", label: "\u{1F3A9} Vouvoyer", desc: "Passe en vous" },
45
+ { id: "formel", label: "\u{1F4BC} Formel", desc: "Ton professionnel" },
46
+ { id: "amical", label: "\u{1F60A} Amical", desc: "Ton chaleureux" },
47
+ { id: "court", label: "\u26A1 Court", desc: "Version concise" }
48
+ ];
49
+ const RewriteDropdown = ({ disabled, loading, onSelect, toolbarBtnClass }) => {
50
+ const [open, setOpen] = React.useState(false);
51
+ const ref = React.useRef(null);
52
+ React.useEffect(() => {
53
+ if (!open) return;
54
+ const close = (e) => {
55
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
56
+ };
57
+ document.addEventListener("mousedown", close);
58
+ return () => document.removeEventListener("mousedown", close);
59
+ }, [open]);
60
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, style: { position: "relative", display: "inline-block" }, children: [
61
+ /* @__PURE__ */ jsxRuntime.jsxs(
62
+ "button",
63
+ {
64
+ type: "button",
65
+ className: toolbarBtnClass,
66
+ onClick: () => setOpen(!open),
67
+ disabled,
68
+ style: { fontSize: 11, fontWeight: 700, padding: "4px 10px", width: "auto", display: "flex", alignItems: "center", gap: 4 },
69
+ children: [
70
+ loading ? "..." : "\u270F\uFE0F Reformuler",
71
+ !loading && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, opacity: 0.6 }, children: "\u25BC" })
72
+ ]
73
+ }
74
+ ),
75
+ open && !disabled && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
76
+ position: "absolute",
77
+ bottom: "100%",
78
+ left: 0,
79
+ marginBottom: 4,
80
+ background: "#fff",
81
+ border: "1px solid #e5e7eb",
82
+ borderRadius: 8,
83
+ boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
84
+ zIndex: 100,
85
+ minWidth: 180,
86
+ overflow: "hidden"
87
+ }, children: REWRITE_STYLES.map((style) => /* @__PURE__ */ jsxRuntime.jsxs(
88
+ "button",
89
+ {
90
+ type: "button",
91
+ onClick: () => {
92
+ setOpen(false);
93
+ onSelect(style.id);
94
+ },
95
+ style: {
96
+ display: "flex",
97
+ flexDirection: "column",
98
+ width: "100%",
99
+ padding: "8px 12px",
100
+ border: "none",
101
+ background: "transparent",
102
+ cursor: "pointer",
103
+ textAlign: "left",
104
+ borderBottom: "1px solid #f3f4f6"
105
+ },
106
+ onMouseEnter: (e) => {
107
+ e.currentTarget.style.background = "#f9fafb";
108
+ },
109
+ onMouseLeave: (e) => {
110
+ e.currentTarget.style.background = "transparent";
111
+ },
112
+ children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, fontWeight: 600 }, children: style.label }),
114
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, color: "#9ca3af" }, children: style.desc })
115
+ ]
116
+ },
117
+ style.id
118
+ )) })
119
+ ] });
120
+ };
36
121
  const TicketDetailClient = () => {
37
122
  const { t } = useTranslation.useTranslation();
38
123
  const searchParams = navigation.useSearchParams();
39
124
  const ticketId = searchParams.get("id");
40
125
  const features = config.getFeatures();
126
+ const editorRef = React.useRef(null);
41
127
  const threadEndRef = React.useRef(null);
42
128
  const dropdownRef = React.useRef(null);
43
129
  const fileInputRef = React.useRef(null);
@@ -49,6 +135,7 @@ const TicketDetailClient = () => {
49
135
  const [cannedResponses, setCannedResponses] = React.useState([]);
50
136
  const [loading, setLoading] = React.useState(true);
51
137
  const [replyBody, setReplyBody] = React.useState("");
138
+ const [replyHtml, setReplyHtml] = React.useState("");
52
139
  const [isInternal, setIsInternal] = React.useState(false);
53
140
  const [notifyClient, setNotifyClient] = React.useState(true);
54
141
  const [sending, setSending] = React.useState(false);
@@ -59,6 +146,8 @@ const TicketDetailClient = () => {
59
146
  const [sentiment, setSentiment] = React.useState(null);
60
147
  const [statusUpdating, setStatusUpdating] = React.useState(false);
61
148
  const [showActivity, setShowActivity] = React.useState(false);
149
+ const [clientSummary, setClientSummary] = React.useState(null);
150
+ const [summaryLoading, setSummaryLoading] = React.useState(false);
62
151
  const [timerRunning, setTimerRunning] = React.useState(() => {
63
152
  if (typeof window === "undefined" || !ticketId) return false;
64
153
  return localStorage.getItem(`timer-run-${ticketId}`) === "1";
@@ -118,26 +207,41 @@ const TicketDetailClient = () => {
118
207
  }
119
208
  setLoading(false);
120
209
  }, [ticketId]);
210
+ const failCountRef = React.useRef({ messages: 0, typing: 0, presence: 0 });
211
+ const MAX_FAILS = 3;
121
212
  React.useEffect(() => {
122
213
  fetchAll();
123
214
  }, [fetchAll]);
215
+ React.useEffect(() => {
216
+ if (!ticket) return;
217
+ const clientId = typeof ticket.client === "object" ? ticket.client?.id : ticket.client;
218
+ if (!clientId) return;
219
+ setSummaryLoading(true);
220
+ fetch(`/api/support/client-intelligence?clientId=${clientId}`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((d) => {
221
+ if (d) setClientSummary(d);
222
+ }).catch(() => {
223
+ }).finally(() => setSummaryLoading(false));
224
+ }, [ticket?.id]);
124
225
  React.useEffect(() => {
125
226
  if (!ticketId || loading) return;
126
227
  const iv = setInterval(async () => {
228
+ if (failCountRef.current.messages >= MAX_FAILS) return;
127
229
  try {
128
230
  const [mr, tr] = await Promise.all([
129
231
  fetch(`/api/ticket-messages?where[ticket][equals]=${ticketId}&sort=createdAt&limit=200&depth=1`, { credentials: "include" }),
130
232
  fetch(`/api/tickets/${ticketId}?depth=0`, { credentials: "include" })
131
233
  ]);
132
- if (mr.ok) {
234
+ if (mr.ok && tr.ok) {
235
+ failCountRef.current.messages = 0;
133
236
  const d = await mr.json();
134
237
  setMessages(d.docs || []);
135
- }
136
- if (tr.ok) {
137
- const d = await tr.json();
138
- setTicket((p) => p ? { ...p, ...d } : d);
238
+ const td = await tr.json();
239
+ setTicket((p) => p ? { ...p, ...td } : td);
240
+ } else {
241
+ failCountRef.current.messages++;
139
242
  }
140
243
  } catch {
244
+ failCountRef.current.messages++;
141
245
  }
142
246
  }, 1e4);
143
247
  return () => clearInterval(iv);
@@ -150,33 +254,40 @@ const TicketDetailClient = () => {
150
254
  React.useEffect(() => {
151
255
  if (!ticketId) return;
152
256
  const iv = setInterval(async () => {
257
+ if (failCountRef.current.typing >= MAX_FAILS) return;
153
258
  try {
154
259
  const r = await fetch(`/api/support/typing?ticketId=${ticketId}`, { credentials: "include" });
155
260
  if (r.ok) {
261
+ failCountRef.current.typing = 0;
156
262
  const d = await r.json();
157
263
  setClientTyping(d.typing);
264
+ } else {
265
+ failCountRef.current.typing++;
158
266
  }
159
- } catch (err) {
160
- console.warn("[support] typing poll error:", err);
267
+ } catch {
268
+ failCountRef.current.typing++;
161
269
  }
162
- }, 2e3);
270
+ }, 3e3);
163
271
  return () => clearInterval(iv);
164
272
  }, [ticketId]);
165
273
  React.useEffect(() => {
166
274
  if (!features.ai || messages.length === 0) return;
167
- const last = [...messages].reverse().find((m) => m.authorType === "client" || m.authorType === "email");
168
- if (!last) return;
169
- fetch("/api/support/ai", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ action: "sentiment", text: last.body.slice(0, 500) }) }).then((r) => r.json()).then((d) => {
170
- const raw = (d.sentiment || "").toLowerCase();
275
+ const clientMsgs = messages.filter((m) => m.authorType === "client" || m.authorType === "email").slice(-3);
276
+ if (clientMsgs.length === 0) return;
277
+ const contextText = clientMsgs.map((m) => m.body).join("\n---\n").slice(0, 1e3);
278
+ fetch("/api/support/ai", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ action: "sentiment", text: contextText }) }).then((r) => r.json()).then((d) => {
279
+ const raw = (d.sentiment || "").toLowerCase().replace(/[^a-zéèêàùûîôëüöç]/g, "");
171
280
  const map = {
172
- "frustre": { emoji: ":(", label: "Frustre", color: "#dc2626" },
173
- "mecontent": { emoji: ":(", label: "Mecontent", color: "#ea580c" },
174
- "urgent": { emoji: "!", label: "Urgent", color: "#dc2626" },
175
- "neutre": { emoji: "-", label: "Neutre", color: "#6b7280" },
176
- "satisfait": { emoji: ":)", label: "Satisfait", color: "#16a34a" }
281
+ "frustr\xE9": { emoji: "\u{1F624}", label: "Frustr\xE9", color: "#dc2626" },
282
+ "frustre": { emoji: "\u{1F624}", label: "Frustr\xE9", color: "#dc2626" },
283
+ "m\xE9content": { emoji: "\u{1F620}", label: "M\xE9content", color: "#ea580c" },
284
+ "mecontent": { emoji: "\u{1F620}", label: "M\xE9content", color: "#ea580c" },
285
+ "urgent": { emoji: "\u{1F525}", label: "Urgent", color: "#dc2626" },
286
+ "neutre": { emoji: "\u{1F610}", label: "Neutre", color: "#6b7280" },
287
+ "satisfait": { emoji: "\u{1F60A}", label: "Satisfait", color: "#16a34a" }
177
288
  };
178
289
  const m = Object.keys(map).find((k) => raw.includes(k));
179
- setSentiment(m ? map[m] : { emoji: "-", label: "Neutre", color: "#6b7280" });
290
+ setSentiment(m ? map[m] : { emoji: "\u{1F610}", label: "Neutre", color: "#6b7280" });
180
291
  }).catch(() => {
181
292
  });
182
293
  }, [messages.length, features.ai]);
@@ -230,13 +341,18 @@ const TicketDetailClient = () => {
230
341
  React.useEffect(() => {
231
342
  if (!ticketId) return;
232
343
  const poll = setInterval(async () => {
344
+ if (failCountRef.current.presence >= MAX_FAILS) return;
233
345
  try {
234
346
  const r = await fetch(`/api/support/presence?ticketId=${ticketId}`, { credentials: "include" });
235
347
  if (r.ok) {
348
+ failCountRef.current.presence = 0;
236
349
  const d = await r.json();
237
350
  setOtherViewers(d.viewers || []);
351
+ } else {
352
+ failCountRef.current.presence++;
238
353
  }
239
354
  } catch {
355
+ failCountRef.current.presence++;
240
356
  }
241
357
  }, 5e3);
242
358
  return () => clearInterval(poll);
@@ -278,7 +394,7 @@ const TicketDetailClient = () => {
278
394
  return () => document.removeEventListener("keydown", handler);
279
395
  }, []);
280
396
  const handleSend = async () => {
281
- if (!replyBody.trim() || !ticketId) return;
397
+ if (!replyBody.trim() && !replyHtml.trim() || !ticketId) return;
282
398
  setSending(true);
283
399
  try {
284
400
  const uploadedLinks = [];
@@ -297,21 +413,23 @@ const TicketDetailClient = () => {
297
413
  }
298
414
  const finalBody = uploadedLinks.length > 0 ? `${replyBody.trim()}
299
415
 
300
- ${uploadedLinks.join("\n")}` : replyBody.trim();
416
+ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
417
+ const finalHtml = uploadedLinks.length > 0 ? `${replyHtml || replyBody.trim()}<br/><br/>${uploadedLinks.map((l) => l.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>')).join("<br/>")}` : replyHtml || void 0;
301
418
  const res = await fetch("/api/ticket-messages", {
302
419
  method: "POST",
303
420
  headers: { "Content-Type": "application/json" },
304
421
  credentials: "include",
305
- body: JSON.stringify({ ticket: Number(ticketId), body: finalBody, authorType: "admin", isInternal, skipNotification: isInternal || !notifyClient })
422
+ body: JSON.stringify({ ticket: Number(ticketId), body: finalBody, ...finalHtml ? { bodyHtml: finalHtml } : {}, authorType: "admin", isInternal, skipNotification: isInternal || !notifyClient })
306
423
  });
307
424
  if (res.ok) {
308
425
  setReplyBody("");
426
+ setReplyHtml("");
309
427
  setIsInternal(false);
310
428
  setPendingFiles([]);
429
+ editorRef.current?.clear();
311
430
  fetchAll();
312
431
  }
313
- } catch (err) {
314
- console.warn("[support] send reply error:", err);
432
+ } catch {
315
433
  } finally {
316
434
  setSending(false);
317
435
  }
@@ -322,8 +440,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
322
440
  try {
323
441
  await fetch(`/api/tickets/${ticketId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ status: v }) });
324
442
  fetchAll();
325
- } catch (err) {
326
- console.warn("[support] status change error:", err);
443
+ } catch {
327
444
  } finally {
328
445
  setStatusUpdating(false);
329
446
  }
@@ -377,27 +494,29 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
377
494
  const d = await r.json();
378
495
  if (d.reply) {
379
496
  setReplyBody(d.reply);
497
+ setReplyHtml(d.reply.replace(/\n/g, "<br/>"));
498
+ editorRef.current?.setContent(d.reply.replace(/\n/g, "<br/>"));
380
499
  }
381
500
  }
382
- } catch (err) {
383
- console.warn("[support] AI suggest error:", err);
501
+ } catch {
384
502
  } finally {
385
503
  setAiReplying(false);
386
504
  }
387
505
  };
388
- const handleAiRewrite = async () => {
506
+ const handleAiRewrite = async (style = "auto") => {
389
507
  if (!replyBody.trim()) return;
390
508
  setAiRewriting(true);
391
509
  try {
392
- const r = await fetch("/api/support/ai", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ action: "rewrite", text: replyBody }) });
510
+ const r = await fetch("/api/support/ai", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ action: "rewrite", text: replyBody, style }) });
393
511
  if (r.ok) {
394
512
  const d = await r.json();
395
513
  if (d.rewritten) {
396
514
  setReplyBody(d.rewritten);
515
+ setReplyHtml(d.rewritten.replace(/\n/g, "<br/>"));
516
+ editorRef.current?.setContent(d.rewritten.replace(/\n/g, "<br/>"));
397
517
  }
398
518
  }
399
- } catch (err) {
400
- console.warn("[support] AI rewrite error:", err);
519
+ } catch {
401
520
  } finally {
402
521
  setAiRewriting(false);
403
522
  }
@@ -409,8 +528,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
409
528
  setTimerSeconds(0);
410
529
  setTimerRunning(false);
411
530
  fetchAll();
412
- } catch (err) {
413
- console.warn("[support] timer save error:", err);
531
+ } catch {
414
532
  }
415
533
  };
416
534
  const handleApplyMacro = async (macroId) => {
@@ -426,8 +544,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
426
544
  if (r.ok) {
427
545
  fetchAll();
428
546
  }
429
- } catch (err) {
430
- console.warn("[support] apply macro error:", err);
547
+ } catch {
431
548
  } finally {
432
549
  setApplyingMacro(false);
433
550
  }
@@ -472,127 +589,128 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
472
589
  if (!ticketId) return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 60, textAlign: "center", color: "#94a3b8", fontSize: 14 }, children: t("detail.selectTicket") });
473
590
  if (loading) return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 60, textAlign: "center", color: "#94a3b8", fontSize: 14 }, children: t("common.loading") });
474
591
  if (!ticket) return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 60, textAlign: "center", color: "#94a3b8", fontSize: 14 }, children: t("detail.notFound") });
475
- const st = STATUS[ticket.status || "open"] || STATUS.open;
592
+ const st = STATUS_STYLE[ticket.status || "open"] || STATUS_STYLE.open;
476
593
  const totalMin = timeEntries.reduce((a, e) => a + (e.duration || 0), 0);
477
594
  const initials = client ? `${(client.firstName?.[0] || "").toUpperCase()}${(client.lastName?.[0] || "").toUpperCase()}` : "?";
478
- const S = {
479
- page: { padding: "16px 20px", maxWidth: 1200, margin: "0 auto" },
480
- topBar: { display: "flex", alignItems: "center", gap: 12, marginBottom: 12, padding: "8px 0", borderBottom: "1px solid var(--theme-elevation-200)" },
481
- backLink: { fontSize: 18, textDecoration: "none", color: "var(--theme-elevation-500)", padding: "4px 8px" },
482
- ticketNumber: { fontWeight: 700, fontSize: 14, color: "var(--theme-elevation-400)" },
483
- ticketSubject: { fontWeight: 600, fontSize: 15, color: "var(--theme-text)", flex: 1 },
484
- statusChip: { padding: "4px 10px", borderRadius: 6, border: "none", fontWeight: 600, fontSize: 12, cursor: "pointer" },
485
- layout: { display: "grid", gridTemplateColumns: "1fr 280px", gap: 20 },
486
- thread: { display: "flex", flexDirection: "column", gap: 12, maxHeight: "calc(100vh - 400px)", overflowY: "auto", padding: "8px 0" },
487
- message: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, border: "1px solid var(--theme-elevation-150)", position: "relative" },
488
- avatar: { width: 32, height: 32, borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", color: "#fff", fontSize: 11, fontWeight: 700, flexShrink: 0 },
489
- messageBody: { fontSize: 14, lineHeight: 1.6, color: "var(--theme-text)", whiteSpace: "pre-wrap" },
490
- composer: { marginTop: 12, border: "1px solid var(--theme-elevation-200)", borderRadius: 10, padding: 12 },
491
- composerInternal: { borderColor: "#fbbf24", background: "#fefce8" },
492
- textarea: { width: "100%", minHeight: 100, padding: 10, border: "none", outline: "none", resize: "vertical", fontSize: 14, fontFamily: "inherit", background: "transparent", color: "var(--theme-text)" },
493
- composerFooter: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 8 },
494
- sendBtn: { padding: "8px 16px", borderRadius: 6, border: "none", background: "#2563eb", color: "#fff", fontWeight: 600, fontSize: 13, cursor: "pointer" },
495
- sidebar: { display: "flex", flexDirection: "column", gap: 16 },
496
- sideSection: { padding: "12px 14px", borderRadius: 10, border: "1px solid var(--theme-elevation-150)", fontSize: 13 },
497
- sideSectionTitle: { fontSize: 12, fontWeight: 700, textTransform: "uppercase", color: "var(--theme-elevation-500)", marginBottom: 8, display: "flex", justifyContent: "space-between", alignItems: "center" },
498
- sideField: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "4px 0" },
499
- sideLabel: { fontSize: 12, color: "var(--theme-elevation-500)" },
500
- sideSelect: { padding: "2px 6px", borderRadius: 4, border: "1px solid var(--theme-elevation-200)", fontSize: 12, background: "var(--theme-elevation-0)", color: "var(--theme-text)" },
501
- sideValue: { fontSize: 12, fontWeight: 500, color: "var(--theme-text)" },
502
- badge: { padding: "2px 6px", borderRadius: 4, fontSize: 10, fontWeight: 600 },
503
- tagChip: { display: "inline-flex", alignItems: "center", gap: 4, padding: "2px 8px", borderRadius: 4, background: "var(--theme-elevation-100)", fontSize: 11, fontWeight: 500 },
504
- tagRemove: { background: "none", border: "none", cursor: "pointer", fontSize: 14, color: "var(--theme-elevation-400)", padding: 0 },
505
- tagInput: { padding: "2px 6px", borderRadius: 4, border: "1px solid var(--theme-elevation-200)", fontSize: 11, width: 80 },
506
- tagAddBtn: { padding: "2px 8px", borderRadius: 4, border: "1px dashed var(--theme-elevation-300)", background: "none", fontSize: 11, cursor: "pointer", color: "var(--theme-elevation-400)" },
507
- toolbarBtn: { padding: "4px 10px", borderRadius: 4, border: "1px solid var(--theme-elevation-200)", background: "var(--theme-elevation-0)", fontSize: 11, fontWeight: 700, cursor: "pointer", color: "var(--theme-text)" },
508
- dateSeparator: { textAlign: "center", padding: "8px 0", fontSize: 11, color: "var(--theme-elevation-400)" }
509
- };
510
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.page, children: [
511
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.topBar, children: [
512
- /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: "/admin/support/inbox", style: S.backLink, "aria-label": "Retour", children: "\u2190" }),
513
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, flex: 1 }, children: [
514
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.ticketNumber, children: ticket.ticketNumber }),
515
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.ticketSubject, children: ticket.subject })
595
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.page, children: [
596
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.topBar, children: [
597
+ /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: "/admin/support/inbox", className: s__default.default.backLink, "aria-label": "Retour \xE0 la bo\xEEte de r\xE9ception", children: "\u2190" }),
598
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.ticketMeta, children: [
599
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.ticketNumber, children: ticket.ticketNumber }),
600
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.ticketSubject, children: ticket.subject })
516
601
  ] }),
517
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
518
- /* @__PURE__ */ jsxRuntime.jsxs("select", { style: { ...S.statusChip, background: st.bg, color: st.color }, value: ticket.status || "open", onChange: (e) => handleStatusChange(e.target.value), disabled: statusUpdating, children: [
602
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.topBarRight, children: [
603
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.statusChip, style: { background: st.bg, color: st.color }, value: ticket.status || "open", onChange: (e) => handleStatusChange(e.target.value), disabled: statusUpdating, "aria-label": t("ticket.status.label"), children: [
519
604
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "open", children: t("detail.statusOpen") }),
520
605
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "waiting_client", children: t("detail.statusWaiting") }),
521
606
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "resolved", children: t("detail.statusResolved") })
522
607
  ] }),
523
- sentiment && features.ai && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...S.badge, background: `${sentiment.color}12`, color: sentiment.color }, children: [
608
+ sentiment && features.ai && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.sentimentBadge, style: { background: `${sentiment.color}12`, color: sentiment.color }, children: [
524
609
  sentiment.emoji,
525
610
  " ",
526
611
  sentiment.label
527
612
  ] }),
528
- /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: dropdownRef, style: { position: "relative" }, children: [
529
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setShowMenu(!showMenu), style: { background: "none", border: "none", cursor: "pointer", fontSize: 18, color: "var(--theme-elevation-500)" }, children: "\xB7\xB7\xB7" }),
530
- showMenu && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", right: 0, top: "100%", background: "var(--theme-elevation-0)", border: "1px solid var(--theme-elevation-200)", borderRadius: 8, boxShadow: "0 4px 12px rgba(0,0,0,0.1)", zIndex: 50, minWidth: 160 }, children: [
531
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { display: "block", width: "100%", padding: "8px 14px", border: "none", background: "none", cursor: "pointer", textAlign: "left", fontSize: 13, color: "var(--theme-text)" }, onClick: () => {
613
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.dropdown, ref: dropdownRef, children: [
614
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.moreBtn, onClick: () => setShowMenu(!showMenu), "aria-label": "Plus d'options", children: "\xB7\xB7\xB7" }),
615
+ showMenu && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.dropdownMenu, children: [
616
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.dropdownItem, onClick: () => {
532
617
  navigator.clipboard.writeText(window.location.href);
533
618
  setShowMenu(false);
534
619
  }, children: t("detail.copyLink") }),
535
- /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/tickets/${ticketId}`, style: { display: "block", padding: "8px 14px", fontSize: 13, color: "var(--theme-text)", textDecoration: "none" }, onClick: () => setShowMenu(false), children: t("detail.payloadView") }),
536
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/support/tickets/${ticketId}`, target: "_blank", rel: "noopener noreferrer", style: { display: "block", padding: "8px 14px", fontSize: 13, color: "var(--theme-text)", textDecoration: "none" }, onClick: () => setShowMenu(false), children: t("detail.clientView") })
620
+ /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/tickets/${ticketId}`, className: s__default.default.dropdownItem, onClick: () => setShowMenu(false), children: t("detail.payloadView") }),
621
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/support/tickets/${ticketId}`, target: "_blank", rel: "noopener noreferrer", className: s__default.default.dropdownItem, onClick: () => setShowMenu(false), children: t("detail.clientView") })
537
622
  ] })
538
623
  ] })
539
624
  ] })
540
625
  ] }),
541
- otherViewers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 14px", marginBottom: 12, borderRadius: 8, background: "#fef3c7", border: "1px solid #fde68a", fontSize: 13, fontWeight: 500, color: "#92400e" }, children: [
542
- otherViewers.map((v) => v.name).join(", "),
543
- " ",
544
- otherViewers.length === 1 ? "est aussi en train de voir ce ticket" : "sont aussi en train de voir ce ticket"
626
+ otherViewers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
627
+ display: "flex",
628
+ alignItems: "center",
629
+ gap: 8,
630
+ padding: "8px 14px",
631
+ marginBottom: 12,
632
+ borderRadius: 8,
633
+ background: "#fef3c7",
634
+ border: "1px solid #fde68a",
635
+ fontSize: 13,
636
+ fontWeight: 500,
637
+ color: "#92400e"
638
+ }, children: [
639
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 1L3 9h4l-1 6 6-8H8l1-6z", fill: "#92400e" }) }),
640
+ otherViewers.length === 1 ? t("detail.viewingAlso", { names: otherViewers.map((v) => v.name).join(", ") }) : t("detail.viewingAlsoPlural", { names: otherViewers.map((v) => v.name).join(", ") })
545
641
  ] }),
546
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.layout, children: [
547
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
548
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.thread, children: [
642
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.layout, children: [
643
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.conversationCol, children: [
644
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.thread, children: [
549
645
  messages.map((msg, idx) => {
550
646
  const prev = idx > 0 ? messages[idx - 1] : null;
551
647
  const showDate = msg.createdAt && (!prev?.createdAt || new Date(msg.createdAt).toDateString() !== new Date(prev.createdAt).toDateString());
552
648
  const isAdmin = msg.authorType === "admin";
553
649
  const isPendingDelete = undoToast?.msgId === msg.id;
554
650
  return /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
555
- showDate && /* @__PURE__ */ jsxRuntime.jsx("div", { style: S.dateSeparator, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: dateLabel(msg.createdAt) }) }),
556
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...S.message, ...msg.isInternal ? { borderColor: "#fbbf24", background: "#fefce8" } : {}, ...isPendingDelete ? { opacity: 0.3, pointerEvents: "none" } : {} }, children: [
557
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...S.avatar, backgroundColor: isAdmin ? "#2563eb" : msg.authorType === "email" ? "#ea580c" : "#7c3aed" }, children: isAdmin ? "CW" : initials }),
558
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
559
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 4 }, children: [
560
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, fontSize: 13 }, children: isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client" }),
561
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 11, color: "var(--theme-elevation-400)" }, children: timeAgo(msg.createdAt) }),
562
- msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...S.badge, background: "#fef3c7", color: "#92400e" }, children: "Interne" }),
563
- msg.isSolution && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...S.badge, background: "#dcfce7", color: "#166534" }, children: "Solution" })
651
+ showDate && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.dateSeparator, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.dateSeparatorText, children: dateLabel(msg.createdAt) }) }),
652
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${s__default.default.message} ${msg.isInternal ? s__default.default.messageInternal : ""}`, style: isPendingDelete ? { opacity: 0.3, pointerEvents: "none" } : void 0, children: [
653
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.avatar, style: { backgroundColor: isAdmin ? "#2563eb" : msg.authorType === "email" ? "#ea580c" : "#7c3aed" }, children: isAdmin ? "CW" : initials }),
654
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageContent, children: [
655
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageHeader, children: [
656
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageAuthor, children: isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client" }),
657
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageTime, children: timeAgo(msg.createdAt) }),
658
+ msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.badge, style: { background: "#fef3c7", color: "#92400e" }, children: "Interne" }),
659
+ msg.isSolution && /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.badge, style: { background: "#dcfce7", color: "#166534" }, children: "Solution" }),
660
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageMeta, children: isAdmin && !msg.isInternal && (() => {
661
+ const ext = msg;
662
+ if (ext.emailOpenedAt) {
663
+ const d = new Date(ext.emailOpenedAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
664
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#16a34a", cursor: "help" }, title: `Ouvert le ${d}`, children: [
665
+ "\u2713\u2713 Lu ",
666
+ d
667
+ ] });
668
+ }
669
+ if (ext.emailSentAt) {
670
+ const d = new Date(ext.emailSentAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
671
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#2563eb", cursor: "help" }, title: `Envoy\xE9 le ${d}`, children: [
672
+ "\u2713 Envoy\xE9 ",
673
+ d
674
+ ] });
675
+ }
676
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#94a3b8" }, children: "\u2713" });
677
+ })() })
564
678
  ] }),
565
- msg.bodyHtml ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: S.messageBody, dangerouslySetInnerHTML: { __html: msg.bodyHtml } }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: S.messageBody, children: msg.body }),
566
- Array.isArray(msg.attachments) && msg.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", marginTop: 8 }, children: msg.attachments.map((att, i) => {
679
+ msg.deletedAt ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.messageBody, style: { color: "#94a3b8", fontStyle: "italic" }, children: t("detail.messageDeleted") }) : msg.bodyHtml && CodeBlock.hasCodeBlocks(msg.bodyHtml.replace(/<[^>]+>/g, "")) ? /* @__PURE__ */ jsxRuntime.jsx(CodeBlock.CodeBlockRendererHtml, { html: msg.bodyHtml }) : msg.bodyHtml ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${s__default.default.messageBody} ${s__default.default.rteDisplay}`, dangerouslySetInnerHTML: { __html: msg.bodyHtml } }) : CodeBlock.hasCodeBlocks(msg.body) ? /* @__PURE__ */ jsxRuntime.jsx(CodeBlock.MessageWithCodeBlocks, { text: msg.body, style: { fontSize: "13px", lineHeight: 1.5 } }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.messageBody, children: msg.body }),
680
+ Array.isArray(msg.attachments) && msg.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.attachments, children: msg.attachments.map((att, i) => {
567
681
  const file = typeof att.file === "object" ? att.file : null;
568
682
  if (!file) return null;
569
- return (file.mimeType || "").startsWith("image/") ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: file.url || "", alt: "", style: { maxWidth: 200, maxHeight: 120, borderRadius: 6 } }) }, i) : /* @__PURE__ */ jsxRuntime.jsxs("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: [
683
+ return (file.mimeType || "").startsWith("image/") ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: file.url || "", alt: "", className: s__default.default.attachmentImg }) }, i) : /* @__PURE__ */ jsxRuntime.jsxs("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", className: s__default.default.attachmentFile, children: [
570
684
  "PJ ",
571
685
  file.filename || "Fichier"
572
686
  ] }, i);
573
687
  }) })
574
688
  ] }),
575
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", top: 8, right: 8, display: "flex", gap: 4, opacity: 0.6 }, children: [
576
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.toolbarBtn, color: "#dc2626", fontSize: 11 }, onClick: () => handleDeleteMessage(msg.id), children: t("common.delete") }),
577
- features.splitTicket && !msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.toolbarBtn, fontSize: 11 }, onClick: () => {
689
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageActions, children: [
690
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: `${s__default.default.actionIcon} ${s__default.default.danger}`, title: t("actions.deleteMessage"), "aria-label": t("actions.deleteMessage"), onClick: () => handleDeleteMessage(msg.id), style: { fontSize: 11, width: "auto", padding: "4px 8px" }, children: t("actions.deleteMessage") }),
691
+ features.splitTicket && !msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.actionIcon, title: t("actions.extractMessage"), "aria-label": t("actions.extractToNewTicket"), onClick: () => {
578
692
  setSplitModal({ messageId: msg.id, preview: msg.body.slice(0, 200) });
579
693
  setSplitSubject(`Split: ${ticket.subject}`);
580
- }, children: t("actions.extractMessage") }),
581
- isAdmin && !msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.toolbarBtn, fontSize: 11 }, onClick: () => fetch("/api/support/resend-notification", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ messageId: msg.id }) }), children: t("actions.resendEmail") })
694
+ }, style: { fontSize: 11, width: "auto", padding: "4px 8px" }, children: t("actions.extractMessage") }),
695
+ isAdmin && !msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.actionIcon, title: t("actions.resendEmail"), "aria-label": t("actions.resendEmail"), onClick: () => fetch("/api/support/resend-notification", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ messageId: msg.id }) }), style: { fontSize: 11, width: "auto", padding: "4px 8px" }, children: t("actions.resendEmail") })
582
696
  ] })
583
697
  ] })
584
698
  ] }, msg.id);
585
699
  }),
586
700
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: threadEndRef })
587
701
  ] }),
588
- clientTyping && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "8px 14px", fontSize: 13, color: "var(--theme-elevation-400)" }, children: [
589
- client?.firstName || "Client",
590
- " est en train d'ecrire..."
702
+ clientTyping && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.typing, children: [
703
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.typingDots, children: [
704
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
705
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
706
+ /* @__PURE__ */ jsxRuntime.jsx("span", {})
707
+ ] }),
708
+ t("detail.typing", { name: client?.firstName || "Client" })
591
709
  ] }),
592
710
  /* @__PURE__ */ jsxRuntime.jsxs(
593
711
  "div",
594
712
  {
595
- style: { ...S.composer, ...isInternal ? S.composerInternal : {}, ...composerDragOver ? { borderColor: "#2563eb", background: "#eff6ff" } : {} },
713
+ className: `${s__default.default.composer} ${isInternal ? s__default.default.composerInternal : ""} ${composerDragOver ? s__default.default.composerDragOver : ""}`,
596
714
  onDragOver: (e) => {
597
715
  e.preventDefault();
598
716
  setComposerDragOver(true);
@@ -600,30 +718,48 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
600
718
  onDragLeave: () => setComposerDragOver(false),
601
719
  onDrop: handleFileDrop,
602
720
  children: [
603
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginBottom: 8, flexWrap: "wrap" }, children: [
721
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.composerToolbar, children: [
604
722
  features.ai && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
605
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.toolbarBtn, onClick: handleAiSuggest, disabled: aiReplying || messages.length === 0, children: aiReplying ? "..." : t("detail.iaSuggestion") }),
606
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.toolbarBtn, onClick: handleAiRewrite, disabled: aiRewriting || !replyBody.trim(), children: aiRewriting ? "..." : t("detail.rewrite") })
723
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.toolbarBtn, "data-tooltip": t("detail.iaSuggestion"), "aria-label": t("detail.iaSuggestion"), onClick: handleAiSuggest, disabled: aiReplying || messages.length === 0, style: { fontSize: 11, fontWeight: 700, padding: "4px 10px", width: "auto" }, children: aiReplying ? "..." : `\u2728 ${t("detail.iaSuggestion")}` }),
724
+ /* @__PURE__ */ jsxRuntime.jsx(RewriteDropdown, { disabled: aiRewriting || !replyBody.trim(), loading: aiRewriting, onSelect: (style) => handleAiRewrite(style), toolbarBtnClass: s__default.default.toolbarBtn })
725
+ ] }),
726
+ /* @__PURE__ */ jsxRuntime.jsx(
727
+ CodeBlockInserter.CodeBlockInserter,
728
+ {
729
+ className: s__default.default.toolbarBtn,
730
+ onInsert: (block) => {
731
+ const nb = replyBody ? replyBody + block : block;
732
+ setReplyBody(nb);
733
+ setReplyHtml(nb.replace(/\n/g, "<br/>"));
734
+ editorRef.current?.setContent(nb.replace(/\n/g, "<br/>"));
735
+ }
736
+ }
737
+ ),
738
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: s__default.default.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" }, children: [
739
+ "\u{1F4CE} ",
740
+ t("detail.file")
607
741
  ] }),
608
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.toolbarBtn, onClick: () => fileInputRef.current?.click(), children: t("detail.file") }),
609
- /* @__PURE__ */ jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: "none" }, onChange: (e) => handleFileSelect(e.target.files) }),
742
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: s__default.default.hiddenFileInput, onChange: (e) => handleFileSelect(e.target.files) }),
610
743
  macros.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
611
744
  "select",
612
745
  {
613
- style: { ...S.sideSelect, marginLeft: 0 },
746
+ className: s__default.default.cannedSelect,
614
747
  onChange: (e) => {
615
748
  const id = Number(e.target.value);
616
749
  if (id) handleApplyMacro(id);
617
750
  e.target.value = "";
618
751
  },
619
752
  disabled: applyingMacro,
753
+ style: { marginLeft: 0 },
754
+ "aria-label": "Appliquer une macro",
620
755
  children: [
621
756
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: applyingMacro ? t("detail.applyingMacro") : t("detail.macros") }),
622
757
  macros.map((m) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: m.id, children: m.name }, m.id))
623
758
  ]
624
759
  }
625
760
  ),
626
- features.canned && cannedResponses.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("select", { style: S.sideSelect, onChange: (e) => {
761
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.toolbarDivider }),
762
+ features.canned && cannedResponses.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.cannedSelect, "aria-label": "R\xE9ponses types", onChange: (e) => {
627
763
  const cr = cannedResponses.find((c) => String(c.id) === e.target.value);
628
764
  if (cr) {
629
765
  let b = cr.body;
@@ -631,6 +767,8 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
631
767
  b = b.replace(/\{\{client\.firstName\}\}/g, client.firstName).replace(/\{\{client\.company\}\}/g, client.company);
632
768
  }
633
769
  setReplyBody(b);
770
+ setReplyHtml(b.replace(/\n/g, "<br/>"));
771
+ editorRef.current?.setContent(b.replace(/\n/g, "<br/>"));
634
772
  }
635
773
  e.target.value = "";
636
774
  }, children: [
@@ -638,24 +776,19 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
638
776
  cannedResponses.map((cr) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(cr.id), children: cr.title }, cr.id))
639
777
  ] })
640
778
  ] }),
641
- /* @__PURE__ */ jsxRuntime.jsx(
642
- "textarea",
643
- {
644
- style: S.textarea,
645
- value: replyBody,
646
- onChange: (e) => setReplyBody(e.target.value),
647
- placeholder: isInternal ? t("composer.placeholderInternal") : t("composer.placeholderReplyTo", { name: client?.firstName || "client" })
648
- }
649
- ),
650
- pendingFiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap", marginTop: 6 }, children: pendingFiles.map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, padding: "2px 8px", borderRadius: 4, background: "var(--theme-elevation-100)", fontSize: 11 }, children: [
779
+ /* @__PURE__ */ jsxRuntime.jsx(index.RichTextEditor, { ref: editorRef, onChange: (html, text) => {
780
+ setReplyHtml(html);
781
+ setReplyBody(text);
782
+ }, placeholder: isInternal ? t("composer.placeholderInternal") : t("composer.placeholderReplyTo", { name: client?.firstName || "client" }), minHeight: 100, borderColor: "transparent" }),
783
+ pendingFiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.uploadPreview, children: pendingFiles.map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.uploadPreviewItem, children: [
651
784
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
652
785
  "PJ ",
653
786
  f.name
654
787
  ] }),
655
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { background: "none", border: "none", cursor: "pointer", fontSize: 14, color: "var(--theme-elevation-400)" }, onClick: () => setPendingFiles((prev) => prev.filter((_, j) => j !== i)), children: "\xD7" })
788
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.uploadRemoveBtn, "aria-label": `Retirer ${f.name}`, onClick: () => setPendingFiles((prev) => prev.filter((_, j) => j !== i)), children: "\xD7" })
656
789
  ] }, i)) }),
657
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.composerFooter, children: [
658
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 12, alignItems: "center", fontSize: 12 }, children: [
790
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.composerFooter, children: [
791
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.composerOptions, children: [
659
792
  /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
660
793
  /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: isInternal, onChange: (e) => setIsInternal(e.target.checked) }),
661
794
  " ",
@@ -667,46 +800,46 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
667
800
  t("detail.notify")
668
801
  ] })
669
802
  ] }),
670
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.sendBtn, ...isInternal ? { background: "#d97706" } : {} }, onClick: handleSend, disabled: sending || !replyBody.trim(), "data-action": "send-reply", children: sending ? t("detail.sending") : isInternal ? t("detail.sendNote") : t("detail.sendReply") })
803
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: `${s__default.default.sendBtn} ${isInternal ? s__default.default.sendBtnInternal : ""}`, onClick: handleSend, disabled: sending || !replyBody.trim(), "data-action": "send-reply", children: sending ? t("detail.sending") : isInternal ? t("detail.sendNote") : t("detail.sendReply") })
671
804
  ] })
672
805
  ]
673
806
  }
674
807
  )
675
808
  ] }),
676
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sidebar, children: [
677
- client && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideSection, children: [
678
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 10, alignItems: "center", marginBottom: 8 }, children: [
679
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...S.avatar, backgroundColor: "#7c3aed", width: 36, height: 36 }, children: initials }),
680
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
681
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontWeight: 600, fontSize: 13 }, children: [
809
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sidebar, children: [
810
+ client && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
811
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientCard, children: [
812
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientAvatar, children: initials }),
813
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientInfo, children: [
814
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientName, children: [
682
815
  client.firstName,
683
816
  " ",
684
817
  client.lastName
685
818
  ] }),
686
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "var(--theme-elevation-500)" }, children: client.company }),
687
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${client.email}`, style: { fontSize: 11, color: "#2563eb" }, children: client.email })
819
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientCompany, children: client.company }),
820
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${client.email}`, className: s__default.default.clientEmail, children: client.email })
688
821
  ] })
689
822
  ] }),
690
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6 }, children: [
691
- /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/support-clients/${client.id}`, style: { padding: "4px 10px", borderRadius: 4, border: "1px solid var(--theme-elevation-200)", fontSize: 11, textDecoration: "none", color: "var(--theme-text)" }, children: t("client.clientSheet") }),
692
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { padding: "4px 10px", borderRadius: 4, border: "1px solid var(--theme-elevation-200)", fontSize: 11, background: "none", cursor: "pointer", color: "var(--theme-text)" }, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
823
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientActions, children: [
824
+ /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/support-clients/${client.id}`, className: s__default.default.smallBtn, children: t("client.clientSheet") }),
825
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
693
826
  ] })
694
827
  ] }),
695
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideSection, children: [
696
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: S.sideSectionTitle, children: t("detail.details") }),
697
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideField, children: [
698
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.sideLabel, children: t("detail.priority") }),
699
- /* @__PURE__ */ jsxRuntime.jsxs("select", { style: S.sideSelect, value: ticket.priority || "normal", onChange: (e) => handleFieldPatch("priority", e.target.value), children: [
828
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
829
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.details") }),
830
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
831
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.priority") }),
832
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.sideSelect, value: ticket.priority || "normal", onChange: (e) => handleFieldPatch("priority", e.target.value), "aria-label": t("detail.priority"), children: [
700
833
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "low", children: t("ticket.priority.low") }),
701
834
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "normal", children: t("ticket.priority.normal") }),
702
835
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "high", children: t("ticket.priority.high") }),
703
836
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "urgent", children: t("ticket.priority.urgent") })
704
837
  ] })
705
838
  ] }),
706
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideField, children: [
707
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.sideLabel, children: t("detail.category") }),
708
- /* @__PURE__ */ jsxRuntime.jsxs("select", { style: S.sideSelect, value: ticket.category || "", onChange: (e) => handleFieldPatch("category", e.target.value), children: [
709
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "--" }),
839
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
840
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.category") }),
841
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.sideSelect, value: ticket.category || "", onChange: (e) => handleFieldPatch("category", e.target.value), "aria-label": t("detail.category"), children: [
842
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "\u2014" }),
710
843
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "bug", children: t("ticket.category.bug") }),
711
844
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "content", children: t("ticket.category.content") }),
712
845
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "feature", children: t("ticket.category.feature") }),
@@ -714,26 +847,26 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
714
847
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "hosting", children: t("ticket.category.hosting") })
715
848
  ] })
716
849
  ] }),
717
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideField, children: [
718
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.sideLabel, children: t("detail.source") }),
719
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.sideValue, children: ticket.source || t("ticket.source.portal") })
850
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
851
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.source") }),
852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideValue, children: ticket.source || t("ticket.source.portal") })
720
853
  ] }),
721
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideField, children: [
722
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.sideLabel, children: t("detail.assigned") }),
723
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: S.sideValue, children: typeof ticket.assignedTo === "object" && ticket.assignedTo ? ticket.assignedTo.firstName || "Admin" : "--" })
854
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
855
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.assigned") }),
856
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideValue, children: typeof ticket.assignedTo === "object" && ticket.assignedTo ? ticket.assignedTo.firstName || "Admin" : "\u2014" })
724
857
  ] })
725
858
  ] }),
726
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideSection, children: [
727
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: S.sideSectionTitle, children: t("detail.tags") }),
728
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
729
- tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: S.tagChip, children: [
859
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
860
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.tags") }),
861
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.tagsWrap, children: [
862
+ tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.tagChip, children: [
730
863
  tag,
731
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.tagRemove, onClick: () => handleRemoveTag(tag), children: "\xD7" })
864
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.tagRemove, "aria-label": `Retirer le tag ${tag}`, onClick: () => handleRemoveTag(tag), children: "\xD7" })
732
865
  ] }, tag)),
733
866
  addingTag ? /* @__PURE__ */ jsxRuntime.jsx(
734
867
  "input",
735
868
  {
736
- style: S.tagInput,
869
+ className: s__default.default.tagInput,
737
870
  value: newTagValue,
738
871
  onChange: (e) => setNewTagValue(e.target.value),
739
872
  onKeyDown: (e) => {
@@ -747,35 +880,149 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
747
880
  placeholder: "Tag...",
748
881
  autoFocus: true
749
882
  }
750
- ) : /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.tagAddBtn, onClick: () => setAddingTag(true), children: "+ Tag" })
883
+ ) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.tagAddBtn, onClick: () => setAddingTag(true), "aria-label": "Ajouter un tag", children: "+ Tag" })
751
884
  ] })
752
885
  ] }),
753
- features.timeTracking && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideSection, children: [
754
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideSectionTitle, children: [
886
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
887
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: "Facturation" }),
888
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
889
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
890
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Type" }),
891
+ /* @__PURE__ */ jsxRuntime.jsxs(
892
+ "select",
893
+ {
894
+ value: ticket?.billingType || "hourly",
895
+ onChange: async (e) => {
896
+ try {
897
+ await fetch(`/api/tickets/${ticketId}`, {
898
+ method: "PATCH",
899
+ credentials: "include",
900
+ headers: { "Content-Type": "application/json" },
901
+ body: JSON.stringify({ billingType: e.target.value })
902
+ });
903
+ fetchAll();
904
+ } catch {
905
+ }
906
+ },
907
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
908
+ children: [
909
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "hourly", children: "Au temps" }),
910
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "flat", children: "Forfait" })
911
+ ]
912
+ }
913
+ )
914
+ ] }),
915
+ ticket?.billingType === "flat" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
916
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Montant forfait" }),
917
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
918
+ /* @__PURE__ */ jsxRuntime.jsx(
919
+ "input",
920
+ {
921
+ type: "number",
922
+ defaultValue: ticket?.flatRateAmount ?? "",
923
+ placeholder: "0",
924
+ onBlur: async (e) => {
925
+ const val = e.target.value ? Number(e.target.value) : null;
926
+ try {
927
+ await fetch(`/api/tickets/${ticketId}`, {
928
+ method: "PATCH",
929
+ credentials: "include",
930
+ headers: { "Content-Type": "application/json" },
931
+ body: JSON.stringify({ flatRateAmount: val })
932
+ });
933
+ fetchAll();
934
+ } catch {
935
+ }
936
+ },
937
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
938
+ }
939
+ ),
940
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
941
+ ] })
942
+ ] }),
943
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
944
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Montant factur\xE9" }),
945
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
946
+ /* @__PURE__ */ jsxRuntime.jsx(
947
+ "input",
948
+ {
949
+ type: "number",
950
+ defaultValue: ticket?.billedAmount ?? "",
951
+ placeholder: "0",
952
+ onBlur: async (e) => {
953
+ const val = e.target.value ? Number(e.target.value) : null;
954
+ try {
955
+ await fetch(`/api/tickets/${ticketId}`, {
956
+ method: "PATCH",
957
+ credentials: "include",
958
+ headers: { "Content-Type": "application/json" },
959
+ body: JSON.stringify({ billedAmount: val })
960
+ });
961
+ fetchAll();
962
+ } catch {
963
+ }
964
+ },
965
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
966
+ }
967
+ ),
968
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
969
+ ] })
970
+ ] }),
971
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
972
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Paiement" }),
973
+ /* @__PURE__ */ jsxRuntime.jsxs(
974
+ "select",
975
+ {
976
+ value: ticket?.paymentStatus || "unpaid",
977
+ onChange: async (e) => {
978
+ try {
979
+ await fetch(`/api/tickets/${ticketId}`, {
980
+ method: "PATCH",
981
+ credentials: "include",
982
+ headers: { "Content-Type": "application/json" },
983
+ body: JSON.stringify({ paymentStatus: e.target.value })
984
+ });
985
+ fetchAll();
986
+ } catch {
987
+ }
988
+ },
989
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
990
+ children: [
991
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "unpaid", children: "Non pay\xE9" }),
992
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "partial", children: "Partiel" }),
993
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "paid", children: "Pay\xE9" })
994
+ ]
995
+ }
996
+ )
997
+ ] })
998
+ ] })
999
+ ] }),
1000
+ features.timeTracking && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1001
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSectionTitle, children: [
755
1002
  t("detail.time"),
756
1003
  " ",
757
1004
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, fontSize: 13, color: "#d97706" }, children: totalMin > 0 ? `${Math.floor(totalMin / 60)}h${String(totalMin % 60).padStart(2, "0")} ${t("detail.total")}` : "0min" })
758
1005
  ] }),
759
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
760
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontFamily: "monospace", fontSize: 16, fontWeight: 700, color: timerRunning ? "#dc2626" : "var(--theme-text)" }, children: [
1006
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.timer, children: [
1007
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `${s__default.default.timerDisplay} ${timerRunning ? s__default.default.timerActive : ""}`, children: [
761
1008
  String(Math.floor(timerSeconds / 60)).padStart(2, "0"),
762
1009
  ":",
763
1010
  String(timerSeconds % 60).padStart(2, "0")
764
1011
  ] }),
765
- !timerRunning ? /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.toolbarBtn, color: "#dc2626", borderColor: "#dc2626" }, onClick: () => setTimerRunning(true), children: timerSeconds > 0 ? "Play" : "Go" }) : /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.toolbarBtn, onClick: () => setTimerRunning(false), children: "Pause" }),
766
- timerSeconds >= 60 && !timerRunning && /* @__PURE__ */ jsxRuntime.jsxs("button", { style: { ...S.toolbarBtn, color: "#16a34a", borderColor: "#16a34a" }, onClick: () => {
1012
+ !timerRunning ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: () => setTimerRunning(true), style: { color: "#dc2626", borderColor: "#dc2626" }, "aria-label": "D\xE9marrer le timer", children: timerSeconds > 0 ? "\u25B6" : "\u25B6 Go" }) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: () => setTimerRunning(false), "aria-label": "Mettre en pause le timer", children: "\u23F8" }),
1013
+ timerSeconds >= 60 && !timerRunning && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: s__default.default.timerBtn, onClick: () => {
767
1014
  handleTimerSave();
768
1015
  localStorage.removeItem(`timer-sec-${ticketId}`);
769
1016
  localStorage.removeItem(`timer-run-${ticketId}`);
770
- }, children: [
771
- "Save ",
1017
+ }, style: { color: "#16a34a", borderColor: "#16a34a" }, "aria-label": "Sauvegarder le temps", children: [
1018
+ "\u{1F4BE} ",
772
1019
  Math.round(timerSeconds / 60),
773
1020
  "m"
774
1021
  ] })
775
1022
  ] }),
776
1023
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" }, children: [
777
1024
  /* @__PURE__ */ jsxRuntime.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" }),
778
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.toolbarBtn, fontSize: 11 }, onClick: async () => {
1025
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: async () => {
779
1026
  const input = document.getElementById("manual-time-input");
780
1027
  const mins = Number(input?.value);
781
1028
  if (!mins || mins < 1 || !ticketId) return;
@@ -783,10 +1030,36 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
783
1030
  await fetch("/api/time-entries", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ ticket: Number(ticketId), duration: mins, date: (/* @__PURE__ */ new Date()).toISOString(), description: "Saisie manuelle" }) });
784
1031
  if (input) input.value = "";
785
1032
  fetchAll();
786
- } catch (err) {
787
- console.warn("[support] manual time entry error:", err);
1033
+ } catch {
788
1034
  }
789
- }, children: "+ Ajouter" })
1035
+ }, style: { fontSize: 11 }, children: "+ Ajouter" })
1036
+ ] }),
1037
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-500)" }, children: [
1038
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0", alignItems: "center" }, children: [
1039
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Facturable" }),
1040
+ /* @__PURE__ */ jsxRuntime.jsx(
1041
+ "button",
1042
+ {
1043
+ onClick: async () => {
1044
+ const newVal = ticket.billable === false ? true : false;
1045
+ try {
1046
+ await fetch(`/api/tickets/${ticketId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ billable: newVal }) });
1047
+ fetchAll();
1048
+ } catch {
1049
+ }
1050
+ },
1051
+ style: { fontWeight: 600, color: ticket.billable !== false ? "#16a34a" : "#dc2626", background: "none", border: "none", cursor: "pointer", fontSize: 11, textDecoration: "underline" },
1052
+ children: ticket.billable !== false ? "Oui" : "Non"
1053
+ }
1054
+ )
1055
+ ] }),
1056
+ totalMin > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0" }, children: [
1057
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Montant estim\xE9" }),
1058
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 700, color: "var(--theme-text)" }, children: [
1059
+ (totalMin / 60 * 60).toFixed(0),
1060
+ "\u20AC"
1061
+ ] })
1062
+ ] })
790
1063
  ] }),
791
1064
  timeEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-500)" }, children: [
792
1065
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(e.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) }),
@@ -796,52 +1069,62 @@ ${uploadedLinks.join("\n")}` : replyBody.trim();
796
1069
  ] })
797
1070
  ] }, e.id)) })
798
1071
  ] }),
799
- features.activityLog && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.sideSection, children: [
800
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: S.sideSectionTitle, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => setShowActivity(!showActivity), style: { background: "none", border: "none", cursor: "pointer", fontSize: 12, fontWeight: 700, textTransform: "uppercase", color: "var(--theme-elevation-500)" }, children: [
801
- "Activite ",
802
- showActivity ? "&#9662;" : "&#9656;"
1072
+ features.activityLog && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { className: s__default.default.collapseBtn, onClick: () => setShowActivity(!showActivity), "aria-label": showActivity ? "Masquer le journal" : "Afficher le journal", children: [
1074
+ "Activit\xE9 ",
1075
+ showActivity ? "\u25BE" : "\u25B8"
803
1076
  ] }) }),
804
- showActivity && activityLog.slice(0, 8).map((a) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, alignItems: "flex-start", padding: "4px 0" }, children: [
805
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 6, height: 6, borderRadius: "50%", marginTop: 4, flexShrink: 0, backgroundColor: a.actorType === "admin" ? "#2563eb" : a.actorType === "system" ? "#6b7280" : "#16a34a" } }),
806
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
807
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "var(--theme-text)" }, children: (a.detail || a.action).slice(0, 60) }),
808
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-400)" }, children: timeAgo(a.createdAt) })
1077
+ showActivity && activityLog.slice(0, 8).map((a) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.activityItem, children: [
1078
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityDot, style: { backgroundColor: a.actorType === "admin" ? "#2563eb" : a.actorType === "system" ? "#6b7280" : "#16a34a" } }),
1079
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.activityContent, children: [
1080
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityText, children: (a.detail || a.action).slice(0, 60) }),
1081
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityTime, children: timeAgo(a.createdAt) })
809
1082
  ] })
810
1083
  ] }, a.id))
811
- ] })
1084
+ ] }),
1085
+ clientSummary && clientSummary.summary && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, style: { background: "linear-gradient(135deg, rgba(37,99,235,0.03) 0%, rgba(139,92,246,0.03) 100%)" }, children: [
1086
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSectionTitle, style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1087
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: "\u{1F9E0}" }),
1088
+ " R\xE9sum\xE9 client"
1089
+ ] }),
1090
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 8px", fontSize: 11, lineHeight: 1.6, color: "#374151" }, children: clientSummary.summary }),
1091
+ clientSummary.recurringTopics && clientSummary.recurringTopics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 6 }, children: clientSummary.recurringTopics.slice(0, 3).map((tp, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { padding: "2px 8px", borderRadius: 10, background: "rgba(37,99,235,0.08)", color: "#2563eb", fontSize: 10, fontWeight: 600 }, children: tp.topic }, i)) }),
1092
+ clientSummary.keyFacts && clientSummary.keyFacts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: clientSummary.keyFacts.slice(0, 3).map((f, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { padding: "2px 8px", borderRadius: 10, background: "rgba(22,163,74,0.08)", color: "#16a34a", fontSize: 10, fontWeight: 600 }, children: f }, i)) })
1093
+ ] }),
1094
+ summaryLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSection, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#94a3b8", textAlign: "center", padding: 8 }, children: "\u{1F9E0} Chargement r\xE9sum\xE9..." }) })
812
1095
  ] })
813
1096
  ] }),
814
- undoToast && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", padding: "10px 20px", borderRadius: 8, background: "#1e293b", color: "#fff", fontSize: 13, display: "flex", gap: 12, alignItems: "center", zIndex: 100, boxShadow: "0 4px 12px rgba(0,0,0,0.2)" }, role: "alert", children: [
815
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("detail.messageDeleted") }),
816
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleUndoDelete, style: { background: "none", border: "1px solid rgba(255,255,255,0.3)", borderRadius: 4, padding: "4px 10px", color: "#fff", cursor: "pointer", fontSize: 12, fontWeight: 600 }, children: t("detail.undo") })
1097
+ undoToast && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.undoToast, role: "alert", children: [
1098
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Message supprim\xE9" }),
1099
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.undoBtn, onClick: handleUndoDelete, children: "Annuler" })
817
1100
  ] }),
818
- splitModal && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 100 }, onClick: (e) => {
1101
+ splitModal && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.splitOverlay, onClick: (e) => {
819
1102
  if (e.target === e.currentTarget) {
820
1103
  setSplitModal(null);
821
1104
  setSplitSubject("");
822
1105
  }
823
- }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: "var(--theme-elevation-0)", borderRadius: 12, padding: 24, maxWidth: 480, width: "100%", boxShadow: "0 8px 30px rgba(0,0,0,0.15)" }, role: "dialog", children: [
824
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { margin: "0 0 12px", fontSize: 16, fontWeight: 700 }, children: t("detail.extractTitle") }),
825
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 10, background: "var(--theme-elevation-50)", borderRadius: 6, fontSize: 13, color: "var(--theme-elevation-500)", marginBottom: 12 }, children: splitModal.preview }),
1106
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.splitModal, role: "dialog", "aria-label": "Extraire dans un nouveau ticket", children: [
1107
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: s__default.default.splitTitle, children: "Extraire dans un nouveau ticket" }),
1108
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.splitPreview, children: splitModal.preview }),
826
1109
  /* @__PURE__ */ jsxRuntime.jsx(
827
1110
  "input",
828
1111
  {
829
- style: { width: "100%", padding: "8px 12px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 13, marginBottom: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" },
1112
+ className: s__default.default.splitInput,
830
1113
  value: splitSubject,
831
1114
  onChange: (e) => setSplitSubject(e.target.value),
832
1115
  onKeyDown: (e) => {
833
1116
  if (e.key === "Enter") handleSplitConfirm();
834
1117
  },
835
- placeholder: t("detail.extractSubjectPlaceholder"),
1118
+ placeholder: "Sujet du nouveau ticket...",
836
1119
  autoFocus: true
837
1120
  }
838
1121
  ),
839
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
840
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { padding: "6px 14px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", background: "none", cursor: "pointer", fontSize: 13, color: "var(--theme-text)" }, onClick: () => {
1122
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.splitActions, children: [
1123
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.splitCancelBtn, onClick: () => {
841
1124
  setSplitModal(null);
842
1125
  setSplitSubject("");
843
- }, children: t("common.cancel") }),
844
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: { ...S.sendBtn }, onClick: handleSplitConfirm, disabled: !splitSubject.trim(), children: t("ticket.createTicket") })
1126
+ }, children: "Annuler" }),
1127
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.splitConfirmBtn, onClick: handleSplitConfirm, disabled: !splitSubject.trim(), children: "Cr\xE9er le ticket" })
845
1128
  ] })
846
1129
  ] }) })
847
1130
  ] });