@consilioweb/payload-support 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/components/TicketConversation/components/AISummaryPanel.js +76 -0
  2. package/dist/components/TicketConversation/components/ActionPanels.js +118 -0
  3. package/dist/components/TicketConversation/components/ActivityLog.js +23 -0
  4. package/dist/components/TicketConversation/components/ClientBar.js +42 -0
  5. package/dist/components/TicketConversation/components/ClientHistory.js +137 -0
  6. package/dist/components/TicketConversation/components/CodeBlock.js +152 -0
  7. package/dist/components/TicketConversation/components/CodeBlockInserter.js +155 -0
  8. package/dist/components/TicketConversation/components/QuickActions.js +65 -0
  9. package/dist/components/TicketConversation/components/TicketHeader.js +92 -0
  10. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +133 -0
  11. package/dist/components/TicketConversation/config.js +41 -0
  12. package/dist/components/TicketConversation/constants.js +96 -0
  13. package/dist/components/TicketConversation/context.js +11 -0
  14. package/dist/components/TicketConversation/hooks/useAI.js +176 -0
  15. package/dist/components/TicketConversation/hooks/useMessageActions.js +135 -0
  16. package/dist/components/TicketConversation/hooks/useReply.js +187 -0
  17. package/dist/components/TicketConversation/hooks/useTicketActions.js +230 -0
  18. package/dist/components/TicketConversation/hooks/useTimeTracking.js +122 -0
  19. package/dist/components/TicketConversation/hooks/useTranslation.js +70 -0
  20. package/dist/components/TicketConversation/index.js +1123 -0
  21. package/dist/components/TicketConversation/locales/en.json +878 -0
  22. package/dist/components/TicketConversation/locales/fr.json +878 -0
  23. package/dist/components/TicketConversation/types.js +2 -0
  24. package/dist/components/TicketConversation/utils.js +25 -0
  25. package/dist/index.cjs +2 -2
  26. package/dist/index.js +2 -2
  27. package/dist/styles/BillingView.module.scss +311 -0
  28. package/dist/styles/ChatView.module.scss +438 -0
  29. package/dist/styles/CommandPalette.module.scss +160 -0
  30. package/dist/styles/CrmView.module.scss +554 -0
  31. package/dist/styles/EmailTracking.module.scss +238 -0
  32. package/dist/styles/ImportConversation.module.scss +267 -0
  33. package/dist/styles/Layout.module.scss +55 -0
  34. package/dist/styles/Logs.module.scss +164 -0
  35. package/dist/styles/NewTicket.module.scss +143 -0
  36. package/dist/styles/PendingEmails.module.scss +629 -0
  37. package/dist/styles/SupportDashboard.module.scss +649 -0
  38. package/dist/styles/TicketDetail.module.scss +1050 -0
  39. package/dist/styles/TicketInbox.module.scss +296 -0
  40. package/dist/styles/TicketingSettings.module.scss +358 -0
  41. package/dist/styles/TimeDashboard.module.scss +287 -0
  42. package/dist/styles/_tokens.scss +78 -0
  43. package/dist/styles/theme.css +633 -0
  44. package/dist/views/BillingView/client.js +204 -0
  45. package/dist/views/BillingView/index.js +29 -0
  46. package/dist/views/ChatView/client.js +252 -0
  47. package/dist/views/ChatView/index.js +29 -0
  48. package/dist/views/CrmView/client.js +232 -0
  49. package/dist/views/CrmView/index.js +29 -0
  50. package/dist/views/EmailTrackingView/client.js +154 -0
  51. package/dist/views/EmailTrackingView/index.js +29 -0
  52. package/dist/views/ImportConversationView/client.js +204 -0
  53. package/dist/views/ImportConversationView/index.js +29 -0
  54. package/dist/views/LogsView/client.js +148 -0
  55. package/dist/views/LogsView/index.js +27 -0
  56. package/dist/views/NewTicketView/client.js +224 -0
  57. package/dist/views/NewTicketView/index.js +27 -0
  58. package/dist/views/PendingEmailsView/client.js +172 -0
  59. package/dist/views/PendingEmailsView/index.js +29 -0
  60. package/dist/views/SupportDashboardView/client.js +296 -0
  61. package/dist/views/SupportDashboardView/index.js +29 -0
  62. package/dist/views/TicketDetailView/client.js +844 -0
  63. package/dist/views/TicketDetailView/index.js +29 -0
  64. package/dist/views/TicketInboxView/client.js +294 -0
  65. package/dist/views/TicketInboxView/index.js +27 -0
  66. package/dist/{views.css → views/TicketingSettingsView/TicketingSettings.module.scss} +138 -66
  67. package/dist/views/TicketingSettingsView/client.js +728 -0
  68. package/dist/views/TicketingSettingsView/index.js +29 -0
  69. package/dist/views/TimeDashboardView/client.js +164 -0
  70. package/dist/views/TimeDashboardView/index.js +29 -0
  71. package/dist/views/shared/AdminViewHeader.js +67 -0
  72. package/dist/views/shared/ErrorBoundary.js +50 -0
  73. package/dist/views/shared/Skeleton.js +72 -0
  74. package/dist/views/shared/adminTokens.js +32 -0
  75. package/dist/views/shared/config.js +41 -0
  76. package/dist/views/shared/index.js +6 -0
  77. package/package.json +8 -12
  78. package/src/collections/Tickets.ts +1 -1
  79. package/src/components/TicketConversation/index.tsx +21 -2
  80. package/src/plugin.ts +3 -2
  81. package/dist/views.cjs +0 -6172
  82. package/dist/views.d.cts +0 -30
  83. package/dist/views.d.ts +0 -30
  84. package/dist/views.js +0 -6153
  85. package/src/views.ts +0 -16
@@ -0,0 +1,1123 @@
1
+ "use client";
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
4
+ import { C, s } from './constants';
5
+ import { getDateLabel, formatMessageDate } from './utils';
6
+ import { CodeBlockRendererHtml, CodeBlockRenderer } from './components/CodeBlock';
7
+ import { CodeBlockInserter } from './components/CodeBlockInserter';
8
+ import { TicketHeader } from './components/TicketHeader';
9
+ import { ClientBar } from './components/ClientBar';
10
+ import { QuickActions } from './components/QuickActions';
11
+ import { AISummaryPanel } from './components/AISummaryPanel';
12
+ import { MergePanel, ExtMessagePanel, SnoozePanel } from './components/ActionPanels';
13
+ import { ActivityLog } from './components/ActivityLog';
14
+ import { ClientHistory } from './components/ClientHistory';
15
+ import { TimeTrackingPanel } from './components/TimeTrackingPanel';
16
+ import { useTimeTracking } from './hooks/useTimeTracking';
17
+ import { useMessageActions } from './hooks/useMessageActions';
18
+ import { useTicketActions } from './hooks/useTicketActions';
19
+ import { useReply } from './hooks/useReply';
20
+ import { useAI } from './hooks/useAI';
21
+ import { getFeatures } from './config';
22
+ import '../../styles/theme.css';
23
+
24
+ function useDocumentIdFromUrl() {
25
+ const [id, setId] = useState(void 0);
26
+ useEffect(() => {
27
+ const match = window.location.pathname.match(
28
+ /\/admin\/collections\/[^/]+\/([^/?#]+)/
29
+ );
30
+ if (match && match[1] !== "create") {
31
+ const raw = match[1];
32
+ const num = Number(raw);
33
+ setId(Number.isFinite(num) && String(num) === raw ? num : raw);
34
+ }
35
+ }, []);
36
+ return { id };
37
+ }
38
+ function SkeletonText({ lines = 3 }) {
39
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "8px", padding: "12px 0" }, children: Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ style: {
43
+ height: "14px",
44
+ borderRadius: "4px",
45
+ backgroundColor: "#e2e8f0",
46
+ width: i === lines - 1 ? "60%" : "100%",
47
+ animation: "pulse 1.5s ease-in-out infinite"
48
+ }
49
+ },
50
+ i
51
+ )) });
52
+ }
53
+ const layoutStyles = {
54
+ root: { padding: "12px 0" },
55
+ twoColumns: {
56
+ display: "grid",
57
+ gridTemplateColumns: "1fr 320px",
58
+ gap: "16px",
59
+ alignItems: "start"
60
+ },
61
+ mainColumn: { minWidth: 0 },
62
+ sideColumn: {
63
+ display: "flex",
64
+ flexDirection: "column",
65
+ gap: "12px",
66
+ position: "sticky",
67
+ top: "80px"
68
+ }
69
+ };
70
+ const TicketConversation = () => {
71
+ const { id } = useDocumentIdFromUrl();
72
+ const [features] = useState(() => getFeatures());
73
+ const [messages, setMessages] = useState([]);
74
+ const [timeEntries, setTimeEntries] = useState([]);
75
+ const [client, setClient] = useState(null);
76
+ const [cannedResponses, setCannedResponses] = useState([]);
77
+ const [activityLog, setActivityLog] = useState([]);
78
+ const [satisfaction, setSatisfaction] = useState(null);
79
+ const [loading, setLoading] = useState(true);
80
+ const [messagesCollapsed, setMessagesCollapsed] = useState(true);
81
+ const [searchQuery, setSearchQuery] = useState("");
82
+ const [copiedLink, setCopiedLink] = useState(null);
83
+ const prevMessageCountRef = useRef(0);
84
+ const [clientTyping, setClientTyping] = useState(false);
85
+ const [clientTypingName, setClientTypingName] = useState("");
86
+ const typingLastSent = useRef(0);
87
+ const [currentStatus, setCurrentStatus] = useState("");
88
+ const [ticketNumber, setTicketNumber] = useState("");
89
+ const [ticketSubject, setTicketSubject] = useState("");
90
+ const [ticketSource, setTicketSource] = useState("");
91
+ const [chatSession, setChatSession] = useState("");
92
+ const [clientTickets, setClientTickets] = useState([]);
93
+ const [clientProjects, setClientProjects] = useState([]);
94
+ const [clientNotes, setClientNotes] = useState("");
95
+ const [savingNotes, setSavingNotes] = useState(false);
96
+ const [notesSaved, setNotesSaved] = useState(false);
97
+ const [lastClientReadAt, setLastClientReadAt] = useState(null);
98
+ const fetchAll = useCallback(async () => {
99
+ if (!id) return;
100
+ try {
101
+ const [msgRes, timeRes, ticketRes, cannedRes, activityRes, csatRes] = await Promise.all([
102
+ fetch(`/api/ticket-messages?where[ticket][equals]=${id}&sort=createdAt&limit=200&depth=1`, { credentials: "include" }),
103
+ fetch(`/api/time-entries?where[ticket][equals]=${id}&sort=-date&limit=50&depth=0`, { credentials: "include" }),
104
+ fetch(`/api/tickets/${id}?depth=1`, { credentials: "include" }),
105
+ fetch(`/api/canned-responses?sort=sortOrder&limit=50&depth=0`, { credentials: "include" }),
106
+ fetch(`/api/ticket-activity-log?where[ticket][equals]=${id}&sort=-createdAt&limit=50&depth=0`, { credentials: "include" }),
107
+ fetch(`/api/satisfaction-surveys?where[ticket][equals]=${id}&limit=1&depth=0`, { credentials: "include" })
108
+ ]);
109
+ if (msgRes.ok) {
110
+ const d = await msgRes.json();
111
+ setMessages(d.docs || []);
112
+ }
113
+ if (timeRes.ok) {
114
+ const d = await timeRes.json();
115
+ setTimeEntries(d.docs || []);
116
+ }
117
+ let resolvedChatSession = "";
118
+ if (ticketRes.ok) {
119
+ const d = await ticketRes.json();
120
+ if (d.client && typeof d.client === "object") {
121
+ setClient(d.client);
122
+ }
123
+ setSnoozeUntil(d.snoozeUntil || null);
124
+ setLastClientReadAt(d.lastClientReadAt || null);
125
+ setCurrentStatus(d.status || "");
126
+ setTicketNumber(d.ticketNumber || "");
127
+ setTicketSubject(d.subject || "");
128
+ setTicketSource(d.source || "");
129
+ setChatSession(d.chatSession || "");
130
+ resolvedChatSession = d.chatSession || "";
131
+ const clientId = typeof d.client === "object" ? d.client?.id : d.client;
132
+ if (clientId) {
133
+ const [clientTicketsRes, projectsRes, clientDetailRes] = await Promise.all([
134
+ fetch(`/api/tickets?where[client][equals]=${clientId}&where[id][not_equals]=${id}&sort=-createdAt&limit=5&depth=0`, { credentials: "include" }),
135
+ fetch(`/api/projects?where[client][contains]=${clientId}&depth=0`, { credentials: "include" }),
136
+ fetch(`/api/support-clients/${clientId}?depth=0`, { credentials: "include" })
137
+ ]);
138
+ if (clientTicketsRes.ok) {
139
+ const ctData = await clientTicketsRes.json();
140
+ setClientTickets((ctData.docs || []).map((t) => ({
141
+ id: t.id,
142
+ ticketNumber: t.ticketNumber,
143
+ subject: t.subject,
144
+ status: t.status,
145
+ createdAt: t.createdAt
146
+ })));
147
+ }
148
+ if (projectsRes.ok) {
149
+ const pData = await projectsRes.json();
150
+ setClientProjects((pData.docs || []).map((p) => ({
151
+ id: p.id,
152
+ name: p.name,
153
+ status: p.status
154
+ })));
155
+ }
156
+ if (clientDetailRes.ok) {
157
+ const cdData = await clientDetailRes.json();
158
+ setClientNotes(cdData.notes || "");
159
+ }
160
+ }
161
+ }
162
+ if (cannedRes.ok) {
163
+ const d = await cannedRes.json();
164
+ setCannedResponses(d.docs || []);
165
+ }
166
+ if (activityRes.ok) {
167
+ const d = await activityRes.json();
168
+ setActivityLog(d.docs || []);
169
+ }
170
+ if (csatRes.ok) {
171
+ const d = await csatRes.json();
172
+ setSatisfaction(d.docs?.[0] || null);
173
+ }
174
+ if (resolvedChatSession) {
175
+ try {
176
+ const chatRes = await fetch(`/api/support/admin-chat?session=${encodeURIComponent(resolvedChatSession)}`, { credentials: "include" });
177
+ if (chatRes.ok) {
178
+ const chatData = await chatRes.json();
179
+ const chatMsgs = (chatData.messages || []).filter((cm) => cm.senderType !== "system").map((cm) => ({
180
+ id: `chat-${cm.id}`,
181
+ body: cm.message,
182
+ authorType: cm.senderType === "agent" ? "admin" : "client",
183
+ isInternal: false,
184
+ createdAt: cm.createdAt,
185
+ fromChat: true
186
+ }));
187
+ setMessages((prev) => {
188
+ const ticketMsgs = prev;
189
+ const merged = [...ticketMsgs];
190
+ for (const chatMsg of chatMsgs) {
191
+ const isDuplicate = ticketMsgs.some((tm) => {
192
+ if (tm.body !== chatMsg.body) return false;
193
+ const timeDiff = Math.abs(new Date(tm.createdAt).getTime() - new Date(chatMsg.createdAt).getTime());
194
+ return timeDiff < 5e3;
195
+ });
196
+ if (!isDuplicate) {
197
+ merged.push(chatMsg);
198
+ }
199
+ }
200
+ return merged.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
201
+ });
202
+ }
203
+ } catch (err) {
204
+ console.warn("[TicketConversation] Chat fetch error:", err);
205
+ }
206
+ }
207
+ } catch (err) {
208
+ console.warn("[TicketConversation] Fetch error:", err);
209
+ } finally {
210
+ setLoading(false);
211
+ }
212
+ }, [id]);
213
+ useEffect(() => {
214
+ fetchAll();
215
+ }, [fetchAll]);
216
+ useEffect(() => {
217
+ if (!id) return;
218
+ fetch(`/api/tickets/${id}`, {
219
+ method: "PATCH",
220
+ headers: { "Content-Type": "application/json" },
221
+ credentials: "include",
222
+ body: JSON.stringify({ lastAdminReadAt: (/* @__PURE__ */ new Date()).toISOString() })
223
+ }).catch(() => {
224
+ });
225
+ }, [id, messages.length]);
226
+ const typingFailCount = useRef(0);
227
+ useEffect(() => {
228
+ if (!id) return;
229
+ typingFailCount.current = 0;
230
+ const poll = async () => {
231
+ if (typingFailCount.current >= 3) return;
232
+ try {
233
+ const res = await fetch(`/api/support/typing?ticketId=${id}`, { credentials: "include" });
234
+ if (res.ok) {
235
+ typingFailCount.current = 0;
236
+ const data = await res.json();
237
+ setClientTyping(data.typing);
238
+ setClientTypingName(data.name || "");
239
+ } else {
240
+ typingFailCount.current++;
241
+ }
242
+ } catch {
243
+ typingFailCount.current++;
244
+ }
245
+ };
246
+ poll();
247
+ const interval = setInterval(poll, 3e3);
248
+ return () => clearInterval(interval);
249
+ }, [id]);
250
+ const sendAdminTyping = useCallback(() => {
251
+ if (!id) return;
252
+ const now = Date.now();
253
+ if (now - typingLastSent.current < 3e3) return;
254
+ typingLastSent.current = now;
255
+ fetch("/api/support/typing", {
256
+ method: "POST",
257
+ headers: { "Content-Type": "application/json" },
258
+ credentials: "include",
259
+ body: JSON.stringify({ ticketId: id })
260
+ }).catch(() => {
261
+ });
262
+ }, [id]);
263
+ const tt = useTimeTracking(id, fetchAll);
264
+ const { duration, setDuration, timeDescription, setTimeDescription, addingTime, timeSuccess, timerRunning, timerSeconds, setTimerSeconds, timerDescription, setTimerDescription, handleAddTime, handleTimerStart, handleTimerStop, handleTimerSave, handleTimerDiscard } = tt;
265
+ const ma = useMessageActions(id, client, fetchAll);
266
+ const { togglingAuthor, editingMsg, editBody, editHtml, setEditHtml, savingEdit, handleEditStart, handleEditSave, handleEditCancel, deletingMsg, handleDelete, resendingMsg, resendSuccess, handleResend, handleToggleAuthor, handleSplitMessage, setEditBody } = ma;
267
+ const ta = useTicketActions(id, fetchAll);
268
+ const { statusUpdating, handleStatusChange, showMerge, setShowMerge, mergeTarget, setMergeTarget, mergeTargetInfo, setMergeTargetInfo, mergeError, setMergeError, merging, handleMergeLookup, handleMerge, showExtMsg, setShowExtMsg, extMsgBody, setExtMsgBody, extMsgAuthor, setExtMsgAuthor, extMsgDate, setExtMsgDate, extMsgFiles, setExtMsgFiles, sendingExtMsg, handleExtFileChange, handleSendExtMsg, showSnooze, setShowSnooze, snoozeUntil, setSnoozeUntil, snoozeSaving, handleSnooze, showNextTicket, setShowNextTicket, nextTicketId, nextTicketInfo, handleNextTicket } = ta;
269
+ const replyEditorRef = useRef(null);
270
+ const rp = useReply(id, client, cannedResponses, ticketNumber, ticketSubject, fetchAll, handleNextTicket, replyEditorRef);
271
+ const { fileInputRef, replyBody, setReplyBody, replyHtml, setReplyHtml, replyFiles, setReplyFiles, isInternal, setIsInternal, notifyClient, setNotifyClient, sendAsClient, setSendAsClient, sending, showSchedule, setShowSchedule, scheduleDate, setScheduleDate, handleEditorFileUpload, handleCannedSelect, handleReplyFileChange, handleSendReply, handleScheduleReply } = rp;
272
+ const ai = useAI(messages, client, ticketSubject, replyBody, setReplyBody, setReplyHtml, replyEditorRef);
273
+ const { clientSentiment, aiReplying, handleAiSuggestReply, aiRewriting, handleAiRewrite, showAiSummary, setShowAiSummary, aiSummary, aiGenerating, handleAiGenerate, aiSaving, aiSaved, handleAiSave: aiSaveRaw } = ai;
274
+ const handleAiSave = () => aiSaveRaw(id, fetchAll);
275
+ const [pollExpired, setPollExpired] = useState(false);
276
+ useEffect(() => {
277
+ if (!id || loading || pollExpired) return;
278
+ const poll = async () => {
279
+ try {
280
+ const [msgRes, ticketRes, activityRes] = await Promise.all([
281
+ fetch(`/api/ticket-messages?where[ticket][equals]=${id}&sort=createdAt&limit=200&depth=1`, { credentials: "include" }),
282
+ fetch(`/api/tickets/${id}?depth=0`, { credentials: "include" }),
283
+ fetch(`/api/ticket-activity-log?where[ticket][equals]=${id}&sort=-createdAt&limit=50&depth=0`, { credentials: "include" })
284
+ ]);
285
+ if (msgRes.status === 401 || msgRes.status === 403) {
286
+ setPollExpired(true);
287
+ return;
288
+ }
289
+ if (msgRes.ok) {
290
+ const d = await msgRes.json();
291
+ setMessages(d.docs || []);
292
+ }
293
+ if (ticketRes.ok) {
294
+ const d = await ticketRes.json();
295
+ setCurrentStatus(d.status || "");
296
+ setSnoozeUntil(d.snoozeUntil || null);
297
+ setLastClientReadAt(d.lastClientReadAt || null);
298
+ }
299
+ if (activityRes.ok) {
300
+ const d = await activityRes.json();
301
+ setActivityLog(d.docs || []);
302
+ }
303
+ } catch {
304
+ }
305
+ };
306
+ const interval = setInterval(poll, 15e3);
307
+ return () => clearInterval(interval);
308
+ }, [id, loading, pollExpired]);
309
+ useEffect(() => {
310
+ const handleKeyDown = (e) => {
311
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter" && !e.shiftKey) {
312
+ e.preventDefault();
313
+ const sendBtn = document.querySelector('[data-action="send-reply"]');
314
+ if (sendBtn && !sendBtn.disabled) sendBtn.click();
315
+ }
316
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === "N" || e.key === "n")) {
317
+ e.preventDefault();
318
+ setIsInternal((prev) => !prev);
319
+ }
320
+ };
321
+ window.addEventListener("keydown", handleKeyDown);
322
+ return () => window.removeEventListener("keydown", handleKeyDown);
323
+ }, []);
324
+ const playNotificationSound = useCallback(() => {
325
+ try {
326
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
327
+ const osc = ctx.createOscillator();
328
+ const gain = ctx.createGain();
329
+ osc.connect(gain);
330
+ gain.connect(ctx.destination);
331
+ osc.frequency.value = 800;
332
+ osc.type = "sine";
333
+ gain.gain.value = 0.1;
334
+ osc.start();
335
+ gain.gain.exponentialRampToValueAtTime(1e-3, ctx.currentTime + 0.3);
336
+ osc.stop(ctx.currentTime + 0.3);
337
+ } catch {
338
+ }
339
+ }, []);
340
+ useEffect(() => {
341
+ const currentCount = messages.length;
342
+ if (prevMessageCountRef.current > 0 && currentCount > prevMessageCountRef.current) {
343
+ const lastMsg = messages[messages.length - 1];
344
+ if (lastMsg && lastMsg.authorType !== "admin") {
345
+ playNotificationSound();
346
+ }
347
+ }
348
+ prevMessageCountRef.current = currentCount;
349
+ }, [messages, playNotificationSound]);
350
+ const handleCopyLink = (type) => {
351
+ const url = type === "admin" ? `${window.location.origin}/admin/collections/tickets/${id}` : `${window.location.origin}/support/tickets/${id}`;
352
+ navigator.clipboard.writeText(url);
353
+ setCopiedLink(type);
354
+ setTimeout(() => setCopiedLink(null), 2e3);
355
+ };
356
+ if (!id) {
357
+ return /* @__PURE__ */ jsx("div", { style: { padding: "16px", color: "#666", fontStyle: "italic" }, children: "Enregistrez le ticket pour voir le tableau de bord." });
358
+ }
359
+ const totalMinutes = timeEntries.reduce((sum, e) => sum + (e.duration || 0), 0);
360
+ const statusTransitions = (() => {
361
+ switch (currentStatus) {
362
+ case "open":
363
+ return [
364
+ { status: "waiting_client", label: "Attente client", color: C.statusWaiting },
365
+ { status: "resolved", label: "R\xE9solu", color: C.statusResolved }
366
+ ];
367
+ case "waiting_client":
368
+ return [
369
+ { status: "open", label: "Ouvrir", color: C.statusOpen },
370
+ { status: "resolved", label: "R\xE9solu", color: C.statusResolved }
371
+ ];
372
+ case "resolved":
373
+ return [
374
+ { status: "open", label: "Rouvrir", color: C.statusOpen }
375
+ ];
376
+ default:
377
+ return [
378
+ { status: "open", label: "Ouvrir", color: C.statusOpen },
379
+ { status: "waiting_client", label: "Attente client", color: C.statusWaiting },
380
+ { status: "resolved", label: "R\xE9solu", color: C.statusResolved }
381
+ ];
382
+ }
383
+ })();
384
+ return /* @__PURE__ */ jsxs("div", { style: layoutStyles.root, children: [
385
+ /* @__PURE__ */ jsx(
386
+ TicketHeader,
387
+ {
388
+ ticketNumber,
389
+ currentStatus,
390
+ clientSentiment,
391
+ ticketSource,
392
+ chatSession,
393
+ snoozeUntil,
394
+ satisfaction,
395
+ copiedLink,
396
+ onCopyLink: handleCopyLink
397
+ }
398
+ ),
399
+ client && /* @__PURE__ */ jsx(ClientBar, { client }),
400
+ /* @__PURE__ */ jsxs("div", { style: layoutStyles.twoColumns, children: [
401
+ /* @__PURE__ */ jsxs("div", { style: layoutStyles.mainColumn, children: [
402
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "4px", display: "flex", alignItems: "center", gap: "10px" }, children: [
403
+ /* @__PURE__ */ jsxs("h3", { style: { fontSize: "14px", fontWeight: 600, margin: 0, display: "flex", alignItems: "center", gap: "8px" }, children: [
404
+ "Conversation ",
405
+ /* @__PURE__ */ jsx("span", { style: s.badge("#f1f5f9", "#475569"), children: messages.length })
406
+ ] }),
407
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
408
+ "input",
409
+ {
410
+ type: "text",
411
+ value: searchQuery,
412
+ onChange: (e) => setSearchQuery(e.target.value),
413
+ placeholder: "Rechercher...",
414
+ style: { ...s.input, width: "100%", fontSize: "12px", padding: "6px 10px" }
415
+ }
416
+ ) })
417
+ ] }),
418
+ loading ? /* @__PURE__ */ jsx(SkeletonText, { lines: 4 }) : messages.length === 0 ? /* @__PURE__ */ jsx("p", { style: { color: "#999", fontStyle: "italic", padding: "12px 0" }, children: "Aucun message." }) : /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "10px", marginBottom: "16px", paddingRight: "4px" }, children: (() => {
419
+ const filtered = messages.filter((msg) => !searchQuery.trim() || msg.body.toLowerCase().includes(searchQuery.toLowerCase()));
420
+ const isSearching = searchQuery.trim().length > 0;
421
+ const VISIBLE_COUNT = 3;
422
+ const showCollapse = !isSearching && messagesCollapsed && filtered.length > VISIBLE_COUNT;
423
+ const visibleMessages = showCollapse ? filtered.slice(-VISIBLE_COUNT) : filtered;
424
+ const hiddenCount = filtered.length - VISIBLE_COUNT;
425
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
426
+ showCollapse && hiddenCount > 0 && /* @__PURE__ */ jsxs(
427
+ "button",
428
+ {
429
+ onClick: () => setMessagesCollapsed(false),
430
+ style: {
431
+ background: "none",
432
+ border: `1px dashed ${C.border}`,
433
+ borderRadius: "6px",
434
+ padding: "8px",
435
+ cursor: "pointer",
436
+ color: C.textMuted,
437
+ fontSize: "12px",
438
+ fontWeight: 600,
439
+ textAlign: "center"
440
+ },
441
+ children: [
442
+ "Voir les ",
443
+ hiddenCount,
444
+ " message",
445
+ hiddenCount > 1 ? "s" : "",
446
+ " pr\xE9c\xE9dent",
447
+ hiddenCount > 1 ? "s" : ""
448
+ ]
449
+ }
450
+ ),
451
+ !messagesCollapsed && filtered.length > 1 && !isSearching && /* @__PURE__ */ jsx(
452
+ "button",
453
+ {
454
+ onClick: () => setMessagesCollapsed(true),
455
+ style: {
456
+ background: "none",
457
+ border: `1px dashed ${C.border}`,
458
+ borderRadius: "6px",
459
+ padding: "8px",
460
+ cursor: "pointer",
461
+ color: C.textMuted,
462
+ fontSize: "12px",
463
+ fontWeight: 600,
464
+ textAlign: "center"
465
+ },
466
+ children: "Masquer les anciens messages"
467
+ }
468
+ ),
469
+ visibleMessages.map((msg, msgIdx) => {
470
+ const borderColor = msg.isInternal ? C.internalBorder : msg.fromChat ? "#bae6fd" : msg.authorType === "admin" ? "#bfdbfe" : msg.authorType === "email" ? "#fed7aa" : C.clientBorder;
471
+ const bgColor = msg.isInternal ? C.internalBg : msg.fromChat ? "#f0f9ff" : msg.authorType === "admin" ? C.adminBg : msg.authorType === "email" ? C.emailBg : C.clientBg;
472
+ const prevVisMsg = msgIdx > 0 ? visibleMessages[msgIdx - 1] : null;
473
+ const showDateSep = msg.createdAt && (!prevVisMsg?.createdAt || new Date(msg.createdAt).toDateString() !== new Date(prevVisMsg.createdAt).toDateString());
474
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
475
+ showDateSep && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", padding: "4px 0" }, children: [
476
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, borderTop: `1px solid ${C.border}` } }),
477
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", fontWeight: 600, color: C.textMuted, whiteSpace: "nowrap" }, children: getDateLabel(msg.createdAt) }),
478
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, borderTop: `1px solid ${C.border}` } })
479
+ ] }),
480
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", alignItems: "flex-start" }, children: [
481
+ /* @__PURE__ */ jsx("div", { style: {
482
+ flexShrink: 0,
483
+ width: "28px",
484
+ height: "28px",
485
+ borderRadius: "50%",
486
+ display: "flex",
487
+ alignItems: "center",
488
+ justifyContent: "center",
489
+ fontSize: "10px",
490
+ fontWeight: 700,
491
+ color: "#fff",
492
+ marginTop: "2px",
493
+ backgroundColor: msg.authorType === "admin" ? C.blue : msg.authorType === "email" ? C.orange : "#94a3b8"
494
+ }, children: msg.authorType === "admin" ? "CW" : client ? `${(client.firstName?.[0] || "").toUpperCase()}${(client.lastName?.[0] || "").toUpperCase()}` || "?" : "?" }),
495
+ /* @__PURE__ */ jsxs(
496
+ "div",
497
+ {
498
+ style: {
499
+ flex: 1,
500
+ padding: "10px 14px",
501
+ borderRadius: "8px",
502
+ border: msg.isInternal ? `1px dashed ${C.internalBorder}` : `1px solid ${borderColor}`,
503
+ backgroundColor: bgColor
504
+ },
505
+ children: [
506
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px", fontSize: "12px", alignItems: "center" }, children: [
507
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600, display: "inline-flex", alignItems: "center", gap: "6px" }, children: [
508
+ msg.authorType === "email" ? /* @__PURE__ */ jsx("span", { style: s.badge(C.emailBg, C.orange), children: "Email" }) : /* @__PURE__ */ jsxs(
509
+ "select",
510
+ {
511
+ value: msg.authorType,
512
+ onChange: (e) => handleToggleAuthor(msg.id, e.target.value === "admin" ? "client" : "admin"),
513
+ disabled: togglingAuthor === msg.id,
514
+ style: {
515
+ fontSize: "12px",
516
+ fontWeight: 600,
517
+ padding: "2px 6px",
518
+ borderRadius: "4px",
519
+ border: `1px solid ${C.border}`,
520
+ cursor: "pointer",
521
+ backgroundColor: msg.authorType === "admin" ? "#eff6ff" : "#f9fafb",
522
+ color: "#374151",
523
+ opacity: togglingAuthor === msg.id ? 0.5 : 1
524
+ },
525
+ children: [
526
+ /* @__PURE__ */ jsx("option", { value: "admin", children: "Support" }),
527
+ /* @__PURE__ */ jsx("option", { value: "client", children: "Client" })
528
+ ]
529
+ }
530
+ ),
531
+ msg.fromChat && /* @__PURE__ */ jsx("span", { style: s.badge("#e0f2fe", "#0284c7"), children: "Chat" }),
532
+ msg.isInternal && /* @__PURE__ */ jsx("span", { style: s.badge("#fef3c7", "#92400e"), children: "Interne" }),
533
+ msg.scheduledAt && (() => {
534
+ const sched = msg;
535
+ const scheduledDate = new Date(sched.scheduledAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
536
+ const createdDate = new Date(msg.createdAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
537
+ return sched.scheduledSent ? /* @__PURE__ */ jsxs("span", { style: s.badge("#f0fdf4", "#16a34a"), children: [
538
+ "\u2713",
539
+ " Programm\xE9 le ",
540
+ createdDate,
541
+ " \u2014 r\xE9dig\xE9 le ",
542
+ scheduledDate !== createdDate ? scheduledDate : createdDate
543
+ ] }) : /* @__PURE__ */ jsxs("span", { style: s.badge("#f3e8ff", "#7c3aed"), children: [
544
+ "\u23F0",
545
+ " R\xE9dig\xE9 le ",
546
+ createdDate,
547
+ " \u2014 envoi programm\xE9 le ",
548
+ scheduledDate
549
+ ] });
550
+ })()
551
+ ] }),
552
+ /* @__PURE__ */ jsxs("span", { style: { color: C.textMuted, fontWeight: 500, fontSize: "11px", display: "inline-flex", alignItems: "center", gap: "6px" }, children: [
553
+ formatMessageDate(msg.createdAt),
554
+ msg.editedAt && /* @__PURE__ */ jsx("span", { style: { fontSize: "10px", color: "#6b7280", fontStyle: "italic" }, children: "(modifi\xE9)" }),
555
+ !msg.isInternal && !msg.deletedAt && /* @__PURE__ */ jsxs(
556
+ "button",
557
+ {
558
+ onClick: () => handleSplitMessage(msg.id, ticketSubject),
559
+ style: { background: "none", border: "none", cursor: "pointer", fontSize: "10px", color: "#6b7280", padding: 0 },
560
+ title: "Extraire en nouveau ticket",
561
+ children: [
562
+ "\u2197",
563
+ " Extraire"
564
+ ]
565
+ }
566
+ ),
567
+ msg.authorType === "admin" && !msg.isInternal && (() => {
568
+ const msgExt = msg;
569
+ const isRead = lastClientReadAt && msg.createdAt && new Date(msg.createdAt) < new Date(lastClientReadAt);
570
+ const sentAt = msgExt.emailSentAt;
571
+ const openedAt = msgExt.emailOpenedAt;
572
+ const sentTo = msgExt.emailSentTo;
573
+ if (openedAt) {
574
+ const openDate = new Date(openedAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", timeZone: "Europe/Paris" });
575
+ return /* @__PURE__ */ jsxs("span", { title: `Envoy\xE9 \xE0 ${sentTo || "?"} \u2014 Ouvert le ${openDate}`, style: { fontSize: "10px", color: "#16a34a", fontWeight: 600, cursor: "help" }, children: [
576
+ "\u2709 Ouvert ",
577
+ openDate
578
+ ] });
579
+ }
580
+ if (sentAt) {
581
+ const sentDate = new Date(sentAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", timeZone: "Europe/Paris" });
582
+ return /* @__PURE__ */ jsxs("span", { title: `Envoy\xE9 \xE0 ${sentTo || "?"} le ${sentDate}`, style: { fontSize: "10px", color: "#2563eb", fontWeight: 600, cursor: "help" }, children: [
583
+ "\u2709 Envoy\xE9 \xE0 ",
584
+ sentTo,
585
+ " \u2014 ",
586
+ sentDate
587
+ ] });
588
+ }
589
+ return /* @__PURE__ */ jsx("span", { style: { fontSize: "10px", color: isRead ? "#16a34a" : "#94a3b8", fontWeight: 600 }, children: isRead ? "\u2713\u2713 Lu" : "\u2713 Envoy\xE9" });
590
+ })()
591
+ ] })
592
+ ] }),
593
+ editingMsg === msg.id ? /* @__PURE__ */ jsxs("div", { style: { marginTop: "6px" }, children: [
594
+ /* @__PURE__ */ jsx(
595
+ "textarea",
596
+ {
597
+ value: editBody,
598
+ onChange: (e) => {
599
+ setEditBody(e.target.value);
600
+ setEditHtml(e.target.value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br />"));
601
+ },
602
+ rows: 4,
603
+ style: { ...s.input, width: "100%", resize: "vertical", fontSize: "13px" }
604
+ }
605
+ ),
606
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", marginTop: "8px" }, children: [
607
+ /* @__PURE__ */ jsx("button", { onClick: () => handleEditSave(msg.id), disabled: savingEdit || !editBody.trim() && !editHtml, style: { ...s.btn(C.blue, savingEdit), fontSize: "11px", padding: "5px 12px" }, children: savingEdit ? "..." : "Enregistrer" }),
608
+ /* @__PURE__ */ jsx("button", { onClick: handleEditCancel, style: { ...s.ghostBtn("#6b7280"), fontSize: "11px", padding: "5px 12px" }, children: "Annuler" })
609
+ ] })
610
+ ] }) : msg.deletedAt ? /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", color: "#94a3b8", fontStyle: "italic" }, children: "Ce message a \xE9t\xE9 supprim\xE9." }) : msg.bodyHtml ? /* @__PURE__ */ jsxs(Fragment, { children: [
611
+ /* @__PURE__ */ jsx(
612
+ "div",
613
+ {
614
+ className: "rte-display",
615
+ style: { fontSize: "13px", color: "#374151", lineHeight: 1.5 },
616
+ dangerouslySetInnerHTML: { __html: msg.bodyHtml }
617
+ }
618
+ ),
619
+ /* @__PURE__ */ jsx(CodeBlockRendererHtml, { html: msg.bodyHtml })
620
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
621
+ /* @__PURE__ */ jsx("div", { style: { whiteSpace: "pre-wrap", fontSize: "13px", color: "#374151", lineHeight: 1.5 }, children: searchQuery.trim() ? msg.body.split(new RegExp(`(${searchQuery.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi")).map(
622
+ (part, i) => part.toLowerCase() === searchQuery.toLowerCase() ? /* @__PURE__ */ jsx("mark", { style: { backgroundColor: "#fde68a", borderRadius: "2px", padding: "0 2px", fontWeight: 600 }, children: part }, i) : part
623
+ ) : msg.body }),
624
+ /* @__PURE__ */ jsx(CodeBlockRenderer, { text: msg.body })
625
+ ] }),
626
+ Array.isArray(msg.attachments) && msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: "8px", display: "flex", gap: "8px", flexWrap: "wrap" }, children: msg.attachments.map((att, i) => {
627
+ const file = typeof att.file === "object" ? att.file : null;
628
+ if (!file) return null;
629
+ const mime = (file.mimeType || file.filename || "").toLowerCase();
630
+ const isImage = mime.includes("image/") || /\.(png|jpg|jpeg|webp|gif|svg)$/i.test(file.filename || "");
631
+ const isGif = mime.includes("image/gif") || /\.gif$/i.test(file.filename || "");
632
+ const isVideo = mime.includes("video/") || /\.(mp4|webm|mov|avi)$/i.test(file.filename || "");
633
+ if (isImage) {
634
+ return /* @__PURE__ */ jsx("a", { href: file.url || "#", target: "_blank", rel: "noopener noreferrer", style: { display: "block" }, children: /* @__PURE__ */ jsx(
635
+ "img",
636
+ {
637
+ src: file.url || "",
638
+ alt: file.filename || "Image",
639
+ style: {
640
+ maxWidth: isGif ? 300 : 240,
641
+ maxHeight: 200,
642
+ borderRadius: "6px",
643
+ border: `1px solid ${C.border}`,
644
+ objectFit: "cover",
645
+ cursor: "pointer"
646
+ }
647
+ }
648
+ ) }, i);
649
+ }
650
+ if (isVideo) {
651
+ return /* @__PURE__ */ jsx(
652
+ "video",
653
+ {
654
+ src: file.url || "",
655
+ controls: true,
656
+ preload: "metadata",
657
+ style: {
658
+ maxWidth: 360,
659
+ maxHeight: 240,
660
+ borderRadius: "6px",
661
+ border: `1px solid ${C.border}`,
662
+ backgroundColor: "#000"
663
+ }
664
+ },
665
+ i
666
+ );
667
+ }
668
+ return /* @__PURE__ */ jsxs(
669
+ "a",
670
+ {
671
+ href: file.url || "#",
672
+ target: "_blank",
673
+ rel: "noopener noreferrer",
674
+ style: {
675
+ display: "inline-flex",
676
+ alignItems: "center",
677
+ gap: "4px",
678
+ padding: "4px 10px",
679
+ borderRadius: "4px",
680
+ border: `1px solid ${C.border}`,
681
+ fontSize: "11px",
682
+ fontWeight: 600,
683
+ color: "#374151",
684
+ textDecoration: "none",
685
+ backgroundColor: C.white
686
+ },
687
+ children: [
688
+ "\u{1F4CE}",
689
+ " ",
690
+ file.filename || "Fichier"
691
+ ]
692
+ },
693
+ i
694
+ );
695
+ }) }),
696
+ editingMsg !== msg.id && !msg.fromChat && /* @__PURE__ */ jsxs("div", { style: { marginTop: "6px", display: "flex", gap: "12px", alignItems: "center" }, children: [
697
+ /* @__PURE__ */ jsx(
698
+ "button",
699
+ {
700
+ type: "button",
701
+ onClick: () => handleEditStart(msg),
702
+ style: { border: "none", background: "none", cursor: "pointer", fontSize: "11px", color: C.textSecondary, padding: 0, fontWeight: 600, textDecoration: "underline" },
703
+ children: "Modifier"
704
+ }
705
+ ),
706
+ /* @__PURE__ */ jsx(
707
+ "button",
708
+ {
709
+ type: "button",
710
+ onClick: () => handleDelete(msg.id),
711
+ disabled: deletingMsg === msg.id,
712
+ style: { border: "none", background: "none", cursor: "pointer", fontSize: "11px", color: "#ef4444", padding: 0, fontWeight: 600, textDecoration: "underline", opacity: deletingMsg === msg.id ? 0.3 : 1 },
713
+ children: "Supprimer"
714
+ }
715
+ ),
716
+ msg.authorType === "admin" && !msg.isInternal && /* @__PURE__ */ jsx(
717
+ "button",
718
+ {
719
+ type: "button",
720
+ onClick: () => handleResend(msg.id),
721
+ disabled: resendingMsg === msg.id,
722
+ style: { border: "none", background: "none", cursor: "pointer", fontSize: "11px", color: "#2563eb", padding: 0, fontWeight: 600, textDecoration: "underline", opacity: resendingMsg === msg.id ? 0.3 : 1 },
723
+ children: resendingMsg === msg.id ? "Envoi..." : resendSuccess === msg.id ? "Envoy\xE9 !" : "Renvoyer email"
724
+ }
725
+ )
726
+ ] })
727
+ ]
728
+ }
729
+ )
730
+ ] })
731
+ ] }, msg.id);
732
+ })
733
+ ] });
734
+ })() }),
735
+ clientTyping && /* @__PURE__ */ jsxs("div", { style: {
736
+ display: "flex",
737
+ alignItems: "center",
738
+ gap: 8,
739
+ padding: "8px 12px",
740
+ fontSize: 12,
741
+ color: "#7c3aed",
742
+ fontWeight: 500
743
+ }, children: [
744
+ /* @__PURE__ */ jsxs("span", { style: { display: "flex", gap: 2 }, children: [
745
+ /* @__PURE__ */ jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", backgroundColor: "#7c3aed", animation: "bounce 1s infinite", animationDelay: "0ms" } }),
746
+ /* @__PURE__ */ jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", backgroundColor: "#7c3aed", animation: "bounce 1s infinite", animationDelay: "150ms" } }),
747
+ /* @__PURE__ */ jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", backgroundColor: "#7c3aed", animation: "bounce 1s infinite", animationDelay: "300ms" } })
748
+ ] }),
749
+ clientTypingName || "Client",
750
+ " est en train d'\xE9crire..."
751
+ ] }),
752
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
753
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "6px", alignItems: "center", overflowX: "auto", paddingBottom: "8px", marginBottom: "6px", flexWrap: "wrap" }, children: [
754
+ [
755
+ "Bien re\xE7u, je regarde \xE7a !",
756
+ "C'est corrig\xE9 !",
757
+ "Pouvez-vous pr\xE9ciser ?",
758
+ "Je reviens vers vous rapidement",
759
+ "Pouvez-vous m'envoyer une capture d'\xE9cran ?"
760
+ ].map((text) => /* @__PURE__ */ jsx(
761
+ "button",
762
+ {
763
+ type: "button",
764
+ onClick: () => {
765
+ setReplyBody(text);
766
+ const html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
767
+ setReplyHtml(html);
768
+ if (replyEditorRef.current?.setContent) {
769
+ replyEditorRef.current.setContent(html);
770
+ }
771
+ },
772
+ onMouseEnter: (e) => {
773
+ e.currentTarget.style.backgroundColor = "#f1f5f9";
774
+ },
775
+ onMouseLeave: (e) => {
776
+ e.currentTarget.style.backgroundColor = "white";
777
+ },
778
+ style: {
779
+ padding: "3px 10px",
780
+ borderRadius: "14px",
781
+ border: `1px solid ${C.border}`,
782
+ backgroundColor: "white",
783
+ fontSize: "11px",
784
+ fontWeight: 500,
785
+ color: "#475569",
786
+ cursor: "pointer",
787
+ whiteSpace: "nowrap"
788
+ },
789
+ children: text
790
+ },
791
+ text
792
+ )),
793
+ features.ai && /* @__PURE__ */ jsx(
794
+ "button",
795
+ {
796
+ onClick: handleAiSuggestReply,
797
+ disabled: aiReplying || messages.length === 0,
798
+ style: { ...s.outlineBtn("#7c3aed", aiReplying || messages.length === 0), fontSize: "11px", padding: "3px 10px", borderRadius: "14px" },
799
+ children: aiReplying ? "G\xE9n\xE9ration..." : "Suggestion IA"
800
+ }
801
+ ),
802
+ features.ai && /* @__PURE__ */ jsx(
803
+ "button",
804
+ {
805
+ onClick: handleAiRewrite,
806
+ disabled: aiRewriting || !replyBody.trim(),
807
+ style: { ...s.outlineBtn("#0891b2", aiRewriting || !replyBody.trim()), fontSize: "11px", padding: "3px 10px", borderRadius: "14px" },
808
+ children: aiRewriting ? "Reformulation..." : "Reformuler"
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsx(
812
+ CodeBlockInserter,
813
+ {
814
+ style: { ...s.outlineBtn("#059669", false), fontSize: "11px", padding: "3px 10px", borderRadius: "14px" },
815
+ onInsert: (block) => {
816
+ const nb = replyBody ? replyBody + block : block;
817
+ setReplyBody(nb);
818
+ setReplyHtml(nb.replace(/\n/g, "<br/>"));
819
+ replyEditorRef.current?.setContent(nb.replace(/\n/g, "<br/>"));
820
+ }
821
+ }
822
+ ),
823
+ features.canned && cannedResponses.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
824
+ /* @__PURE__ */ jsxs("select", { onChange: handleCannedSelect, style: { ...s.input, fontSize: "11px", padding: "3px 8px", fontWeight: 600 }, children: [
825
+ /* @__PURE__ */ jsx("option", { value: "", children: "R\xE9ponse rapide..." }),
826
+ cannedResponses.map((cr) => /* @__PURE__ */ jsx("option", { value: String(cr.id), children: cr.title }, cr.id))
827
+ ] }),
828
+ /* @__PURE__ */ jsx(
829
+ "span",
830
+ {
831
+ title: "Variables disponibles : {{client.firstName}}, {{client.lastName}}, {{client.company}}, {{client.email}}, {{ticket.number}}, {{ticket.subject}}, {{agent.name}}",
832
+ style: { cursor: "help", fontSize: "13px", color: C.textMuted },
833
+ children: "\u24D8"
834
+ }
835
+ )
836
+ ] })
837
+ ] }),
838
+ /* @__PURE__ */ jsx("div", { style: { border: `1px solid ${C.border}`, borderRadius: "8px", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
839
+ "textarea",
840
+ {
841
+ value: replyBody,
842
+ onChange: (e) => {
843
+ const text = e.target.value;
844
+ setReplyBody(text);
845
+ setReplyHtml(text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br />"));
846
+ sendAdminTyping();
847
+ },
848
+ placeholder: "\xC9crire une r\xE9ponse au client...",
849
+ style: {
850
+ width: "100%",
851
+ minHeight: "120px",
852
+ padding: "12px",
853
+ border: "none",
854
+ outline: "none",
855
+ fontSize: "14px",
856
+ lineHeight: 1.5,
857
+ resize: "vertical",
858
+ fontFamily: "inherit",
859
+ color: "#374151",
860
+ backgroundColor: "transparent",
861
+ boxSizing: "border-box"
862
+ }
863
+ }
864
+ ) }),
865
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "8px", display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" }, children: [
866
+ /* @__PURE__ */ jsx(
867
+ "input",
868
+ {
869
+ ref: fileInputRef,
870
+ type: "file",
871
+ multiple: true,
872
+ onChange: handleReplyFileChange,
873
+ style: { display: "none" },
874
+ accept: "image/*,.pdf,.doc,.docx,.txt,.zip"
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsx(
878
+ "button",
879
+ {
880
+ type: "button",
881
+ onClick: () => fileInputRef.current?.click(),
882
+ style: { ...s.ghostBtn("#6b7280"), fontSize: "12px", padding: "5px 10px" },
883
+ children: "+ Pi\xE8ce jointe"
884
+ }
885
+ ),
886
+ replyFiles.length > 0 && /* @__PURE__ */ jsx(Fragment, { children: replyFiles.map((file, i) => /* @__PURE__ */ jsxs("span", { style: { ...s.badge("#f1f5f9", "#374151"), display: "inline-flex", alignItems: "center", gap: "4px" }, children: [
887
+ "\u{1F4CE}",
888
+ " ",
889
+ file.name,
890
+ /* @__PURE__ */ jsx(
891
+ "button",
892
+ {
893
+ type: "button",
894
+ onClick: () => setReplyFiles((prev) => prev.filter((_, idx) => idx !== i)),
895
+ style: { border: "none", background: "none", color: "#ef4444", fontWeight: 700, cursor: "pointer", fontSize: "14px", lineHeight: 1 },
896
+ children: "\xD7"
897
+ }
898
+ )
899
+ ] }, i)) })
900
+ ] }),
901
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px", flexWrap: "wrap", marginTop: "8px" }, children: [
902
+ /* @__PURE__ */ jsxs(
903
+ "select",
904
+ {
905
+ value: sendAsClient ? "client" : "admin",
906
+ onChange: (e) => setSendAsClient(e.target.value === "client"),
907
+ style: { ...s.input, fontSize: "12px", padding: "6px 8px", fontWeight: 600 },
908
+ children: [
909
+ /* @__PURE__ */ jsx("option", { value: "admin", children: "En tant que : Support" }),
910
+ /* @__PURE__ */ jsx("option", { value: "client", children: "En tant que : Client" })
911
+ ]
912
+ }
913
+ ),
914
+ /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: "5px", fontSize: "12px", cursor: "pointer", fontWeight: 600 }, children: [
915
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: isInternal, onChange: (e) => {
916
+ setIsInternal(e.target.checked);
917
+ if (e.target.checked) setNotifyClient(false);
918
+ }, style: { width: "14px", height: "14px", accentColor: C.amber } }),
919
+ "Note interne"
920
+ ] }),
921
+ !isInternal && /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: "5px", fontSize: "12px", cursor: "pointer", fontWeight: 600 }, children: [
922
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: notifyClient, onChange: (e) => setNotifyClient(e.target.checked), style: { width: "14px", height: "14px", accentColor: "#16a34a" } }),
923
+ "Envoyer au client"
924
+ ] }),
925
+ /* @__PURE__ */ jsx("button", { "data-action": "send-reply", onClick: handleSendReply, disabled: sending || !replyBody.trim() && !replyHtml, style: { ...s.btn(isInternal ? C.amber : notifyClient ? "#16a34a" : C.blue, sending || !replyBody.trim() && !replyHtml), fontSize: "13px", padding: "8px 20px", marginLeft: "auto" }, children: sending ? "Envoi..." : isInternal ? "Ajouter note" : notifyClient ? "Envoyer + Notifier" : "Sauvegarder" }),
926
+ !isInternal && /* @__PURE__ */ jsx(
927
+ "button",
928
+ {
929
+ onClick: () => setShowSchedule(!showSchedule),
930
+ disabled: !replyBody.trim() && !replyHtml,
931
+ style: { ...s.outlineBtn("#7c3aed", !replyBody.trim() && !replyHtml), fontSize: "12px", padding: "8px 12px" },
932
+ title: "Programmer l'envoi \xE0 une date/heure pr\xE9cise",
933
+ children: "\u23F0"
934
+ }
935
+ )
936
+ ] }),
937
+ showSchedule && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", alignItems: "center", marginTop: "8px", padding: "10px 14px", borderRadius: "8px", backgroundColor: "#faf5ff", border: "1px solid #e9d5ff" }, children: [
938
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", fontWeight: 600, color: "#7c3aed" }, children: "Programmer pour :" }),
939
+ /* @__PURE__ */ jsx(
940
+ "input",
941
+ {
942
+ type: "datetime-local",
943
+ value: scheduleDate,
944
+ onChange: (e) => setScheduleDate(e.target.value),
945
+ min: (/* @__PURE__ */ new Date()).toISOString().slice(0, 16),
946
+ style: { ...s.input, fontSize: "12px", width: "auto" }
947
+ }
948
+ ),
949
+ /* @__PURE__ */ jsx(
950
+ "button",
951
+ {
952
+ onClick: handleScheduleReply,
953
+ disabled: sending || !scheduleDate || !replyBody.trim() && !replyHtml,
954
+ style: { ...s.btn("#7c3aed", sending || !scheduleDate || !replyBody.trim() && !replyHtml), fontSize: "12px", padding: "6px 14px" },
955
+ children: sending ? "..." : "\u23F0 Programmer"
956
+ }
957
+ ),
958
+ /* @__PURE__ */ jsx("button", { onClick: () => setShowSchedule(false), style: { background: "none", border: "none", cursor: "pointer", color: "#94a3b8", fontSize: "14px" }, children: "\u2715" })
959
+ ] }),
960
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "11px", color: C.textMuted, marginTop: "4px", textAlign: "right" }, children: [
961
+ "\u2318",
962
+ "Enter pour envoyer \xB7 ",
963
+ "\u2318",
964
+ "\u21E7",
965
+ "N note interne"
966
+ ] })
967
+ ] }),
968
+ /* @__PURE__ */ jsx("div", { style: { borderTop: `1px solid ${C.border}`, marginBottom: "16px" } }),
969
+ /* @__PURE__ */ jsx(
970
+ QuickActions,
971
+ {
972
+ statusTransitions,
973
+ statusUpdating,
974
+ onStatusChange: handleStatusChange,
975
+ snoozeUntil,
976
+ snoozeSaving,
977
+ onCancelSnooze: () => handleSnooze(null),
978
+ showMerge,
979
+ showExtMsg,
980
+ showSnooze,
981
+ onToggleMerge: () => {
982
+ setShowMerge(!showMerge);
983
+ setShowExtMsg(false);
984
+ setShowSnooze(false);
985
+ },
986
+ onToggleExtMsg: () => {
987
+ setShowExtMsg(!showExtMsg);
988
+ setShowMerge(false);
989
+ setShowSnooze(false);
990
+ },
991
+ onToggleSnooze: () => {
992
+ setShowSnooze(!showSnooze);
993
+ setShowMerge(false);
994
+ setShowExtMsg(false);
995
+ },
996
+ onNextTicket: handleNextTicket,
997
+ showNextTicket,
998
+ nextTicketId,
999
+ nextTicketInfo,
1000
+ onCloseNextTicket: () => setShowNextTicket(false)
1001
+ }
1002
+ ),
1003
+ features.ai && /* @__PURE__ */ jsx(
1004
+ AISummaryPanel,
1005
+ {
1006
+ showAiSummary,
1007
+ setShowAiSummary,
1008
+ aiSummary,
1009
+ aiGenerating,
1010
+ aiSaving,
1011
+ aiSaved,
1012
+ handleAiGenerate,
1013
+ handleAiSave
1014
+ }
1015
+ ),
1016
+ features.merge && showMerge && /* @__PURE__ */ jsx(
1017
+ MergePanel,
1018
+ {
1019
+ mergeTarget,
1020
+ setMergeTarget,
1021
+ mergeTargetInfo,
1022
+ setMergeTargetInfo,
1023
+ mergeError,
1024
+ setMergeError,
1025
+ merging,
1026
+ handleMergeLookup,
1027
+ handleMerge
1028
+ }
1029
+ ),
1030
+ features.externalMessages && showExtMsg && /* @__PURE__ */ jsx(
1031
+ ExtMessagePanel,
1032
+ {
1033
+ extMsgBody,
1034
+ setExtMsgBody,
1035
+ extMsgAuthor,
1036
+ setExtMsgAuthor,
1037
+ extMsgDate,
1038
+ setExtMsgDate,
1039
+ extMsgFiles,
1040
+ setExtMsgFiles,
1041
+ sendingExtMsg,
1042
+ handleSendExtMsg,
1043
+ handleExtFileChange
1044
+ }
1045
+ ),
1046
+ features.snooze && showSnooze && /* @__PURE__ */ jsx(SnoozePanel, { snoozeSaving, handleSnooze })
1047
+ ] }),
1048
+ /* @__PURE__ */ jsxs("div", { style: layoutStyles.sideColumn, children: [
1049
+ /* @__PURE__ */ jsx(
1050
+ TimeTrackingPanel,
1051
+ {
1052
+ timeEntries,
1053
+ totalMinutes,
1054
+ timerRunning,
1055
+ timerSeconds,
1056
+ setTimerSeconds,
1057
+ timerDescription,
1058
+ setTimerDescription,
1059
+ handleTimerStart,
1060
+ handleTimerStop,
1061
+ handleTimerSave,
1062
+ handleTimerDiscard,
1063
+ duration,
1064
+ setDuration,
1065
+ timeDescription,
1066
+ setTimeDescription,
1067
+ handleAddTime,
1068
+ addingTime,
1069
+ timeSuccess
1070
+ }
1071
+ ),
1072
+ features.clientHistory && client && /* @__PURE__ */ jsx(
1073
+ ClientHistory,
1074
+ {
1075
+ client,
1076
+ clientTickets,
1077
+ clientProjects,
1078
+ clientNotes,
1079
+ onNotesChange: (v) => {
1080
+ setClientNotes(v);
1081
+ setNotesSaved(false);
1082
+ },
1083
+ onNotesSave: async () => {
1084
+ if (!client) return;
1085
+ setSavingNotes(true);
1086
+ try {
1087
+ const res = await fetch(`/api/support-clients/${client.id}`, {
1088
+ method: "PATCH",
1089
+ headers: { "Content-Type": "application/json" },
1090
+ credentials: "include",
1091
+ body: JSON.stringify({ notes: clientNotes })
1092
+ });
1093
+ if (res.ok) {
1094
+ setNotesSaved(true);
1095
+ setTimeout(() => setNotesSaved(false), 3e3);
1096
+ }
1097
+ } catch {
1098
+ } finally {
1099
+ setSavingNotes(false);
1100
+ }
1101
+ },
1102
+ savingNotes,
1103
+ notesSaved
1104
+ }
1105
+ ),
1106
+ features.activityLog && /* @__PURE__ */ jsx(ActivityLog, { activityLog }),
1107
+ /* @__PURE__ */ jsx("div", { style: s.section, children: /* @__PURE__ */ jsx(
1108
+ "a",
1109
+ {
1110
+ href: "/api/support/export-csv",
1111
+ target: "_blank",
1112
+ rel: "noopener noreferrer",
1113
+ style: { ...s.ghostBtn("#6b7280"), fontSize: "12px", textDecoration: "none", display: "inline-block" },
1114
+ children: "Exporter tous les tickets (CSV)"
1115
+ }
1116
+ ) })
1117
+ ] })
1118
+ ] })
1119
+ ] });
1120
+ };
1121
+ var TicketConversation_default = TicketConversation;
1122
+
1123
+ export { TicketConversation_default as default };