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