@consilioweb/payload-support 0.6.0 → 0.6.2

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/{styles/theme.css → components/TicketConversation.css} +13 -326
  2. package/dist/components/TicketConversation.js +3004 -0
  3. package/dist/index.cjs +11 -11
  4. package/dist/index.js +11 -11
  5. package/dist/views/BillingView/client.cjs +209 -0
  6. package/dist/views/BillingView/index.cjs +34 -0
  7. package/dist/views/ChatView/client.cjs +253 -0
  8. package/dist/views/ChatView/index.cjs +34 -0
  9. package/dist/views/CrmView/client.cjs +233 -0
  10. package/dist/views/CrmView/index.cjs +34 -0
  11. package/dist/views/EmailTrackingView/client.cjs +159 -0
  12. package/dist/views/EmailTrackingView/index.cjs +34 -0
  13. package/dist/views/ImportConversationView/client.cjs +205 -0
  14. package/dist/views/ImportConversationView/index.cjs +34 -0
  15. package/dist/views/LogsView/client.cjs +149 -0
  16. package/dist/views/LogsView/index.cjs +32 -0
  17. package/dist/views/NewTicketView/client.cjs +229 -0
  18. package/dist/views/NewTicketView/index.cjs +32 -0
  19. package/dist/views/PendingEmailsView/client.cjs +173 -0
  20. package/dist/views/PendingEmailsView/index.cjs +34 -0
  21. package/dist/views/SupportDashboardView/client.cjs +301 -0
  22. package/dist/views/SupportDashboardView/index.cjs +34 -0
  23. package/dist/views/TicketDetailView/client.cjs +850 -0
  24. package/dist/views/TicketDetailView/index.cjs +34 -0
  25. package/dist/views/TicketInboxView/client.cjs +299 -0
  26. package/dist/views/TicketInboxView/index.cjs +32 -0
  27. package/dist/views/TicketingSettingsView/client.cjs +733 -0
  28. package/dist/views/TicketingSettingsView/index.cjs +34 -0
  29. package/dist/views/TimeDashboardView/client.cjs +169 -0
  30. package/dist/views/TimeDashboardView/index.cjs +34 -0
  31. package/dist/views/shared/AdminViewHeader.cjs +68 -0
  32. package/dist/views/shared/ErrorBoundary.cjs +55 -0
  33. package/dist/views/shared/Skeleton.cjs +77 -0
  34. package/dist/views/shared/adminTokens.cjs +34 -0
  35. package/dist/views/shared/adminTokens.js +0 -1
  36. package/dist/{components/TicketConversation/config.js → views/shared/config.cjs} +5 -2
  37. package/dist/views/shared/config.js +0 -1
  38. package/dist/views/shared/index.cjs +58 -0
  39. package/dist/views/shared/index.js +0 -1
  40. package/dist/views.cjs +86 -0
  41. package/dist/views.d.cts +13 -0
  42. package/dist/views.d.ts +13 -0
  43. package/dist/views.js +13 -0
  44. package/package.json +14 -7
  45. package/src/plugin.ts +11 -11
  46. package/src/views.ts +17 -0
  47. package/dist/components/TicketConversation/components/AISummaryPanel.js +0 -76
  48. package/dist/components/TicketConversation/components/ActionPanels.js +0 -118
  49. package/dist/components/TicketConversation/components/ActivityLog.js +0 -23
  50. package/dist/components/TicketConversation/components/ClientBar.js +0 -42
  51. package/dist/components/TicketConversation/components/ClientHistory.js +0 -137
  52. package/dist/components/TicketConversation/components/CodeBlock.js +0 -152
  53. package/dist/components/TicketConversation/components/CodeBlockInserter.js +0 -155
  54. package/dist/components/TicketConversation/components/QuickActions.js +0 -65
  55. package/dist/components/TicketConversation/components/TicketHeader.js +0 -92
  56. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +0 -133
  57. package/dist/components/TicketConversation/constants.js +0 -96
  58. package/dist/components/TicketConversation/context.js +0 -11
  59. package/dist/components/TicketConversation/hooks/useAI.js +0 -176
  60. package/dist/components/TicketConversation/hooks/useMessageActions.js +0 -135
  61. package/dist/components/TicketConversation/hooks/useReply.js +0 -187
  62. package/dist/components/TicketConversation/hooks/useTicketActions.js +0 -230
  63. package/dist/components/TicketConversation/hooks/useTimeTracking.js +0 -122
  64. package/dist/components/TicketConversation/hooks/useTranslation.js +0 -70
  65. package/dist/components/TicketConversation/index.js +0 -1123
  66. package/dist/components/TicketConversation/locales/en.json +0 -878
  67. package/dist/components/TicketConversation/locales/fr.json +0 -878
  68. package/dist/components/TicketConversation/types.js +0 -2
  69. package/dist/components/TicketConversation/utils.js +0 -25
  70. package/dist/styles/BillingView.module.scss +0 -311
  71. package/dist/styles/ChatView.module.scss +0 -438
  72. package/dist/styles/CommandPalette.module.scss +0 -160
  73. package/dist/styles/CrmView.module.scss +0 -554
  74. package/dist/styles/EmailTracking.module.scss +0 -238
  75. package/dist/styles/ImportConversation.module.scss +0 -267
  76. package/dist/styles/Layout.module.scss +0 -55
  77. package/dist/styles/Logs.module.scss +0 -164
  78. package/dist/styles/NewTicket.module.scss +0 -143
  79. package/dist/styles/PendingEmails.module.scss +0 -629
  80. package/dist/styles/SupportDashboard.module.scss +0 -649
  81. package/dist/styles/TicketDetail.module.scss +0 -1050
  82. package/dist/styles/TicketInbox.module.scss +0 -296
  83. package/dist/styles/TicketingSettings.module.scss +0 -358
  84. package/dist/styles/TimeDashboard.module.scss +0 -287
  85. package/dist/styles/_tokens.scss +0 -78
@@ -1,1123 +0,0 @@
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 };