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