@consilioweb/payload-support 0.6.1 → 0.6.3

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 (134) 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.cjs +209 -0
  65. package/dist/views/BillingView/client.js +204 -0
  66. package/dist/views/BillingView/index.cjs +34 -0
  67. package/dist/views/BillingView/index.js +29 -0
  68. package/dist/views/ChatView/client.cjs +253 -0
  69. package/dist/views/ChatView/client.js +252 -0
  70. package/dist/views/ChatView/index.cjs +34 -0
  71. package/dist/views/ChatView/index.js +29 -0
  72. package/dist/views/CrmView/client.cjs +233 -0
  73. package/dist/views/CrmView/client.js +232 -0
  74. package/dist/views/CrmView/index.cjs +34 -0
  75. package/dist/views/CrmView/index.js +29 -0
  76. package/dist/views/EmailTrackingView/client.cjs +159 -0
  77. package/dist/views/EmailTrackingView/client.js +154 -0
  78. package/dist/views/EmailTrackingView/index.cjs +34 -0
  79. package/dist/views/EmailTrackingView/index.js +29 -0
  80. package/dist/views/ImportConversationView/client.cjs +205 -0
  81. package/dist/views/ImportConversationView/client.js +204 -0
  82. package/dist/views/ImportConversationView/index.cjs +34 -0
  83. package/dist/views/ImportConversationView/index.js +29 -0
  84. package/dist/views/LogsView/client.cjs +149 -0
  85. package/dist/views/LogsView/client.js +148 -0
  86. package/dist/views/LogsView/index.cjs +32 -0
  87. package/dist/views/LogsView/index.js +27 -0
  88. package/dist/views/NewTicketView/client.cjs +229 -0
  89. package/dist/views/NewTicketView/client.js +224 -0
  90. package/dist/views/NewTicketView/index.cjs +32 -0
  91. package/dist/views/NewTicketView/index.js +27 -0
  92. package/dist/views/PendingEmailsView/client.cjs +173 -0
  93. package/dist/views/PendingEmailsView/client.js +172 -0
  94. package/dist/views/PendingEmailsView/index.cjs +34 -0
  95. package/dist/views/PendingEmailsView/index.js +29 -0
  96. package/dist/views/SupportDashboardView/client.cjs +301 -0
  97. package/dist/views/SupportDashboardView/client.js +296 -0
  98. package/dist/views/SupportDashboardView/index.cjs +34 -0
  99. package/dist/views/SupportDashboardView/index.js +29 -0
  100. package/dist/views/TicketDetailView/client.cjs +850 -0
  101. package/dist/views/TicketDetailView/client.js +844 -0
  102. package/dist/views/TicketDetailView/index.cjs +34 -0
  103. package/dist/views/TicketDetailView/index.js +29 -0
  104. package/dist/views/TicketInboxView/client.cjs +299 -0
  105. package/dist/views/TicketInboxView/client.js +294 -0
  106. package/dist/views/TicketInboxView/index.cjs +32 -0
  107. package/dist/views/TicketInboxView/index.js +27 -0
  108. package/dist/{views.css → views/TicketingSettingsView/TicketingSettings.module.scss} +138 -66
  109. package/dist/views/TicketingSettingsView/client.cjs +733 -0
  110. package/dist/views/TicketingSettingsView/client.js +728 -0
  111. package/dist/views/TicketingSettingsView/index.cjs +34 -0
  112. package/dist/views/TicketingSettingsView/index.js +29 -0
  113. package/dist/views/TimeDashboardView/client.cjs +169 -0
  114. package/dist/views/TimeDashboardView/client.js +164 -0
  115. package/dist/views/TimeDashboardView/index.cjs +34 -0
  116. package/dist/views/TimeDashboardView/index.js +29 -0
  117. package/dist/views/shared/AdminViewHeader.cjs +68 -0
  118. package/dist/views/shared/AdminViewHeader.js +67 -0
  119. package/dist/views/shared/ErrorBoundary.cjs +55 -0
  120. package/dist/views/shared/ErrorBoundary.js +50 -0
  121. package/dist/views/shared/Skeleton.cjs +77 -0
  122. package/dist/views/shared/Skeleton.js +72 -0
  123. package/dist/views/shared/adminTokens.cjs +34 -0
  124. package/dist/views/shared/adminTokens.js +31 -0
  125. package/dist/views/shared/config.cjs +44 -0
  126. package/dist/views/shared/config.js +40 -0
  127. package/dist/views/shared/index.cjs +58 -0
  128. package/dist/views/shared/index.js +5 -0
  129. package/dist/views.cjs +77 -6175
  130. package/dist/views.d.cts +13 -30
  131. package/dist/views.d.ts +13 -30
  132. package/dist/views.js +13 -6165
  133. package/package.json +2 -2
  134. package/dist/components/TicketConversation.js +0 -3004
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ function useReply(id, client, cannedResponses, ticketNumber, ticketSubject, fetchAll, handleNextTicket, replyEditorRef) {
6
+ const fileInputRef = react.useRef(null);
7
+ const [replyBody, setReplyBody] = react.useState("");
8
+ const [replyHtml, setReplyHtml] = react.useState("");
9
+ const [replyFiles, setReplyFiles] = react.useState([]);
10
+ const [isInternal, setIsInternal] = react.useState(false);
11
+ const [notifyClient, setNotifyClient] = react.useState(false);
12
+ const [sendAsClient, setSendAsClient] = react.useState(false);
13
+ const [sending, setSending] = react.useState(false);
14
+ const [showSchedule, setShowSchedule] = react.useState(false);
15
+ const [scheduleDate, setScheduleDate] = react.useState("");
16
+ const handleEditorFileUpload = react.useCallback(async (file) => {
17
+ if (file.size > 5 * 1024 * 1024) return null;
18
+ const formData = new FormData();
19
+ formData.append("file", file);
20
+ formData.append("_payload", JSON.stringify({ alt: file.name }));
21
+ try {
22
+ const res = await fetch("/api/media", { method: "POST", credentials: "include", body: formData });
23
+ if (res.ok) {
24
+ const data = await res.json();
25
+ return data.doc?.url || null;
26
+ }
27
+ } catch {
28
+ }
29
+ return null;
30
+ }, []);
31
+ const replaceCannedVariables = react.useCallback((text) => {
32
+ let result = text;
33
+ if (client) {
34
+ result = result.replace(/\{\{client\.firstName\}\}/g, client.firstName || "Client");
35
+ result = result.replace(/\{\{client\.lastName\}\}/g, client.lastName || "");
36
+ result = result.replace(/\{\{client\.company\}\}/g, client.company || "");
37
+ result = result.replace(/\{\{client\.email\}\}/g, client.email || "");
38
+ result = result.replace(/\{\{clientName\}\}/g, client.firstName || "Client");
39
+ }
40
+ result = result.replace(/\{\{ticket\.number\}\}/g, ticketNumber || "");
41
+ result = result.replace(/\{\{ticket\.subject\}\}/g, ticketSubject || "");
42
+ result = result.replace(/\{\{agent\.name\}\}/g, "Support");
43
+ return result;
44
+ }, [client, ticketNumber, ticketSubject]);
45
+ const handleCannedSelect = (e) => {
46
+ const selected = cannedResponses.find((cr) => String(cr.id) === e.target.value);
47
+ if (selected) {
48
+ const body = replaceCannedVariables(selected.body);
49
+ const html = body.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br />");
50
+ setReplyBody(body);
51
+ setReplyHtml(html);
52
+ replyEditorRef.current?.setContent(html);
53
+ }
54
+ e.target.value = "";
55
+ };
56
+ const handleReplyFileChange = (e) => {
57
+ if (e.target.files) {
58
+ const maxSize = 1 * 1024 * 1024;
59
+ const newFiles = Array.from(e.target.files);
60
+ const tooLarge = newFiles.filter((f) => f.size > maxSize);
61
+ if (tooLarge.length > 0) {
62
+ alert(`Fichier(s) trop volumineux (max 1 Mo) : ${tooLarge.map((f) => f.name).join(", ")}`);
63
+ }
64
+ setReplyFiles((prev) => [...prev, ...newFiles.filter((f) => f.size <= maxSize)]);
65
+ }
66
+ if (fileInputRef.current) fileInputRef.current.value = "";
67
+ };
68
+ const uploadFiles = async (files) => {
69
+ const uploadedIds = [];
70
+ for (const file of files) {
71
+ if (file.size > 1 * 1024 * 1024) continue;
72
+ const formData = new FormData();
73
+ formData.append("file", file);
74
+ formData.append("_payload", JSON.stringify({ alt: file.name }));
75
+ const uploadRes = await fetch("/api/media", { method: "POST", credentials: "include", body: formData });
76
+ if (uploadRes.ok) {
77
+ const d = await uploadRes.json();
78
+ if (d.doc?.id) uploadedIds.push(d.doc.id);
79
+ }
80
+ }
81
+ return uploadedIds;
82
+ };
83
+ const resetReply = () => {
84
+ setReplyBody("");
85
+ setReplyHtml("");
86
+ setReplyFiles([]);
87
+ setIsInternal(false);
88
+ setNotifyClient(false);
89
+ setSendAsClient(false);
90
+ replyEditorRef.current?.clear();
91
+ };
92
+ const handleSendReply = async () => {
93
+ if (!replyBody.trim() || !id) return;
94
+ setSending(true);
95
+ try {
96
+ const uploadedIds = await uploadFiles(replyFiles);
97
+ const finalBody = replyBody.trim() || (replyHtml ? "[Contenu enrichi]" : "");
98
+ const messageData = {
99
+ ticket: id,
100
+ body: finalBody,
101
+ ...replyHtml ? { bodyHtml: replyHtml } : {},
102
+ authorType: sendAsClient ? "client" : "admin",
103
+ isInternal,
104
+ skipNotification: isInternal || !notifyClient,
105
+ ...sendAsClient && client ? { authorClient: client.id } : {}
106
+ };
107
+ if (uploadedIds.length > 0) {
108
+ messageData.attachments = uploadedIds.map((mid) => ({ file: mid }));
109
+ }
110
+ const res = await fetch("/api/ticket-messages", {
111
+ method: "POST",
112
+ headers: { "Content-Type": "application/json" },
113
+ credentials: "include",
114
+ body: JSON.stringify(messageData)
115
+ });
116
+ if (res.ok) {
117
+ resetReply();
118
+ fetchAll();
119
+ if (!isInternal) handleNextTicket();
120
+ }
121
+ } catch {
122
+ } finally {
123
+ setSending(false);
124
+ }
125
+ };
126
+ const handleScheduleReply = async () => {
127
+ if (!replyBody.trim() || !id || !scheduleDate) return;
128
+ setSending(true);
129
+ try {
130
+ const uploadedIds = await uploadFiles(replyFiles);
131
+ const messageData = {
132
+ ticket: id,
133
+ body: replyBody.trim(),
134
+ ...replyHtml ? { bodyHtml: replyHtml } : {},
135
+ authorType: "admin",
136
+ isInternal: false,
137
+ skipNotification: true,
138
+ scheduledAt: new Date(scheduleDate).toISOString(),
139
+ scheduledSent: false
140
+ };
141
+ if (uploadedIds.length > 0) {
142
+ messageData.attachments = uploadedIds.map((mid) => ({ file: mid }));
143
+ }
144
+ const res = await fetch("/api/ticket-messages", {
145
+ method: "POST",
146
+ headers: { "Content-Type": "application/json" },
147
+ credentials: "include",
148
+ body: JSON.stringify(messageData)
149
+ });
150
+ if (res.ok) {
151
+ resetReply();
152
+ setShowSchedule(false);
153
+ setScheduleDate("");
154
+ fetchAll();
155
+ }
156
+ } catch {
157
+ } finally {
158
+ setSending(false);
159
+ }
160
+ };
161
+ return {
162
+ fileInputRef,
163
+ replyBody,
164
+ setReplyBody,
165
+ replyHtml,
166
+ setReplyHtml,
167
+ replyFiles,
168
+ setReplyFiles,
169
+ isInternal,
170
+ setIsInternal,
171
+ notifyClient,
172
+ setNotifyClient,
173
+ sendAsClient,
174
+ setSendAsClient,
175
+ sending,
176
+ showSchedule,
177
+ setShowSchedule,
178
+ scheduleDate,
179
+ setScheduleDate,
180
+ handleEditorFileUpload,
181
+ handleCannedSelect,
182
+ handleReplyFileChange,
183
+ handleSendReply,
184
+ handleScheduleReply
185
+ };
186
+ }
187
+
188
+ exports.useReply = useReply;
@@ -0,0 +1,187 @@
1
+ "use client";
2
+ import { useRef, useState, useCallback } from 'react';
3
+
4
+ function useReply(id, client, cannedResponses, ticketNumber, ticketSubject, fetchAll, handleNextTicket, replyEditorRef) {
5
+ const fileInputRef = useRef(null);
6
+ const [replyBody, setReplyBody] = useState("");
7
+ const [replyHtml, setReplyHtml] = useState("");
8
+ const [replyFiles, setReplyFiles] = useState([]);
9
+ const [isInternal, setIsInternal] = useState(false);
10
+ const [notifyClient, setNotifyClient] = useState(false);
11
+ const [sendAsClient, setSendAsClient] = useState(false);
12
+ const [sending, setSending] = useState(false);
13
+ const [showSchedule, setShowSchedule] = useState(false);
14
+ const [scheduleDate, setScheduleDate] = useState("");
15
+ const handleEditorFileUpload = useCallback(async (file) => {
16
+ if (file.size > 5 * 1024 * 1024) return null;
17
+ const formData = new FormData();
18
+ formData.append("file", file);
19
+ formData.append("_payload", JSON.stringify({ alt: file.name }));
20
+ try {
21
+ const res = await fetch("/api/media", { method: "POST", credentials: "include", body: formData });
22
+ if (res.ok) {
23
+ const data = await res.json();
24
+ return data.doc?.url || null;
25
+ }
26
+ } catch {
27
+ }
28
+ return null;
29
+ }, []);
30
+ const replaceCannedVariables = useCallback((text) => {
31
+ let result = text;
32
+ if (client) {
33
+ result = result.replace(/\{\{client\.firstName\}\}/g, client.firstName || "Client");
34
+ result = result.replace(/\{\{client\.lastName\}\}/g, client.lastName || "");
35
+ result = result.replace(/\{\{client\.company\}\}/g, client.company || "");
36
+ result = result.replace(/\{\{client\.email\}\}/g, client.email || "");
37
+ result = result.replace(/\{\{clientName\}\}/g, client.firstName || "Client");
38
+ }
39
+ result = result.replace(/\{\{ticket\.number\}\}/g, ticketNumber || "");
40
+ result = result.replace(/\{\{ticket\.subject\}\}/g, ticketSubject || "");
41
+ result = result.replace(/\{\{agent\.name\}\}/g, "Support");
42
+ return result;
43
+ }, [client, ticketNumber, ticketSubject]);
44
+ const handleCannedSelect = (e) => {
45
+ const selected = cannedResponses.find((cr) => String(cr.id) === e.target.value);
46
+ if (selected) {
47
+ const body = replaceCannedVariables(selected.body);
48
+ const html = body.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br />");
49
+ setReplyBody(body);
50
+ setReplyHtml(html);
51
+ replyEditorRef.current?.setContent(html);
52
+ }
53
+ e.target.value = "";
54
+ };
55
+ const handleReplyFileChange = (e) => {
56
+ if (e.target.files) {
57
+ const maxSize = 1 * 1024 * 1024;
58
+ const newFiles = Array.from(e.target.files);
59
+ const tooLarge = newFiles.filter((f) => f.size > maxSize);
60
+ if (tooLarge.length > 0) {
61
+ alert(`Fichier(s) trop volumineux (max 1 Mo) : ${tooLarge.map((f) => f.name).join(", ")}`);
62
+ }
63
+ setReplyFiles((prev) => [...prev, ...newFiles.filter((f) => f.size <= maxSize)]);
64
+ }
65
+ if (fileInputRef.current) fileInputRef.current.value = "";
66
+ };
67
+ const uploadFiles = async (files) => {
68
+ const uploadedIds = [];
69
+ for (const file of files) {
70
+ if (file.size > 1 * 1024 * 1024) continue;
71
+ const formData = new FormData();
72
+ formData.append("file", file);
73
+ formData.append("_payload", JSON.stringify({ alt: file.name }));
74
+ const uploadRes = await fetch("/api/media", { method: "POST", credentials: "include", body: formData });
75
+ if (uploadRes.ok) {
76
+ const d = await uploadRes.json();
77
+ if (d.doc?.id) uploadedIds.push(d.doc.id);
78
+ }
79
+ }
80
+ return uploadedIds;
81
+ };
82
+ const resetReply = () => {
83
+ setReplyBody("");
84
+ setReplyHtml("");
85
+ setReplyFiles([]);
86
+ setIsInternal(false);
87
+ setNotifyClient(false);
88
+ setSendAsClient(false);
89
+ replyEditorRef.current?.clear();
90
+ };
91
+ const handleSendReply = async () => {
92
+ if (!replyBody.trim() || !id) return;
93
+ setSending(true);
94
+ try {
95
+ const uploadedIds = await uploadFiles(replyFiles);
96
+ const finalBody = replyBody.trim() || (replyHtml ? "[Contenu enrichi]" : "");
97
+ const messageData = {
98
+ ticket: id,
99
+ body: finalBody,
100
+ ...replyHtml ? { bodyHtml: replyHtml } : {},
101
+ authorType: sendAsClient ? "client" : "admin",
102
+ isInternal,
103
+ skipNotification: isInternal || !notifyClient,
104
+ ...sendAsClient && client ? { authorClient: client.id } : {}
105
+ };
106
+ if (uploadedIds.length > 0) {
107
+ messageData.attachments = uploadedIds.map((mid) => ({ file: mid }));
108
+ }
109
+ const res = await fetch("/api/ticket-messages", {
110
+ method: "POST",
111
+ headers: { "Content-Type": "application/json" },
112
+ credentials: "include",
113
+ body: JSON.stringify(messageData)
114
+ });
115
+ if (res.ok) {
116
+ resetReply();
117
+ fetchAll();
118
+ if (!isInternal) handleNextTicket();
119
+ }
120
+ } catch {
121
+ } finally {
122
+ setSending(false);
123
+ }
124
+ };
125
+ const handleScheduleReply = async () => {
126
+ if (!replyBody.trim() || !id || !scheduleDate) return;
127
+ setSending(true);
128
+ try {
129
+ const uploadedIds = await uploadFiles(replyFiles);
130
+ const messageData = {
131
+ ticket: id,
132
+ body: replyBody.trim(),
133
+ ...replyHtml ? { bodyHtml: replyHtml } : {},
134
+ authorType: "admin",
135
+ isInternal: false,
136
+ skipNotification: true,
137
+ scheduledAt: new Date(scheduleDate).toISOString(),
138
+ scheduledSent: false
139
+ };
140
+ if (uploadedIds.length > 0) {
141
+ messageData.attachments = uploadedIds.map((mid) => ({ file: mid }));
142
+ }
143
+ const res = await fetch("/api/ticket-messages", {
144
+ method: "POST",
145
+ headers: { "Content-Type": "application/json" },
146
+ credentials: "include",
147
+ body: JSON.stringify(messageData)
148
+ });
149
+ if (res.ok) {
150
+ resetReply();
151
+ setShowSchedule(false);
152
+ setScheduleDate("");
153
+ fetchAll();
154
+ }
155
+ } catch {
156
+ } finally {
157
+ setSending(false);
158
+ }
159
+ };
160
+ return {
161
+ fileInputRef,
162
+ replyBody,
163
+ setReplyBody,
164
+ replyHtml,
165
+ setReplyHtml,
166
+ replyFiles,
167
+ setReplyFiles,
168
+ isInternal,
169
+ setIsInternal,
170
+ notifyClient,
171
+ setNotifyClient,
172
+ sendAsClient,
173
+ setSendAsClient,
174
+ sending,
175
+ showSchedule,
176
+ setShowSchedule,
177
+ scheduleDate,
178
+ setScheduleDate,
179
+ handleEditorFileUpload,
180
+ handleCannedSelect,
181
+ handleReplyFileChange,
182
+ handleSendReply,
183
+ handleScheduleReply
184
+ };
185
+ }
186
+
187
+ export { useReply };
@@ -0,0 +1,231 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ function useTicketActions(id, fetchAll) {
6
+ const [statusUpdating, setStatusUpdating] = react.useState(false);
7
+ const [showMerge, setShowMerge] = react.useState(false);
8
+ const [mergeTarget, setMergeTarget] = react.useState("");
9
+ const [mergeTargetInfo, setMergeTargetInfo] = react.useState(null);
10
+ const [merging, setMerging] = react.useState(false);
11
+ const [mergeError, setMergeError] = react.useState("");
12
+ const [showExtMsg, setShowExtMsg] = react.useState(false);
13
+ const [extMsgBody, setExtMsgBody] = react.useState("");
14
+ const [extMsgAuthor, setExtMsgAuthor] = react.useState("client");
15
+ const [extMsgDate, setExtMsgDate] = react.useState("");
16
+ const [extMsgFiles, setExtMsgFiles] = react.useState([]);
17
+ const [sendingExtMsg, setSendingExtMsg] = react.useState(false);
18
+ const extFileInputRef = react.useRef(null);
19
+ const [showSnooze, setShowSnooze] = react.useState(false);
20
+ const [snoozeUntil, setSnoozeUntil] = react.useState(null);
21
+ const [snoozeSaving, setSnoozeSaving] = react.useState(false);
22
+ const [showNextTicket, setShowNextTicket] = react.useState(false);
23
+ const [nextTicketId, setNextTicketId] = react.useState(null);
24
+ const [nextTicketInfo, setNextTicketInfo] = react.useState("");
25
+ const handleStatusChange = async (newStatus) => {
26
+ if (!id) return;
27
+ setStatusUpdating(true);
28
+ try {
29
+ await fetch(`/api/tickets/${id}`, {
30
+ method: "PATCH",
31
+ headers: { "Content-Type": "application/json" },
32
+ credentials: "include",
33
+ body: JSON.stringify({ status: newStatus })
34
+ });
35
+ window.location.reload();
36
+ } catch {
37
+ } finally {
38
+ setStatusUpdating(false);
39
+ }
40
+ };
41
+ const handleMergeLookup = async () => {
42
+ if (!mergeTarget.trim()) return;
43
+ setMergeTargetInfo(null);
44
+ setMergeError("");
45
+ try {
46
+ const searchVal = mergeTarget.trim().toUpperCase();
47
+ const res = await fetch(`/api/tickets?where[ticketNumber][equals]=${encodeURIComponent(searchVal)}&limit=1&depth=0`, { credentials: "include" });
48
+ if (res.ok) {
49
+ const d = await res.json();
50
+ if (d.docs?.length > 0) {
51
+ const t = d.docs[0];
52
+ if (String(t.id) === String(id)) {
53
+ setMergeError("Impossible de fusionner un ticket avec lui-m\xEAme");
54
+ } else {
55
+ setMergeTargetInfo({ id: t.id, ticketNumber: t.ticketNumber, subject: t.subject });
56
+ }
57
+ } else {
58
+ setMergeError("Ticket introuvable");
59
+ }
60
+ }
61
+ } catch {
62
+ setMergeError("Erreur de recherche");
63
+ }
64
+ };
65
+ const handleMerge = async () => {
66
+ if (!mergeTargetInfo || !id) return;
67
+ setMerging(true);
68
+ setMergeError("");
69
+ try {
70
+ const res = await fetch("/api/support/merge-tickets", {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ credentials: "include",
74
+ body: JSON.stringify({ sourceTicketId: id, targetTicketId: mergeTargetInfo.id })
75
+ });
76
+ if (res.ok) {
77
+ window.location.href = `/admin/collections/tickets/${mergeTargetInfo.id}`;
78
+ } else {
79
+ const d = await res.json().catch(() => ({}));
80
+ setMergeError(d.error || "Erreur de fusion");
81
+ }
82
+ } catch {
83
+ setMergeError("Erreur r\xE9seau");
84
+ } finally {
85
+ setMerging(false);
86
+ }
87
+ };
88
+ const handleExtFileChange = (e) => {
89
+ if (e.target.files) {
90
+ const maxSize = 1 * 1024 * 1024;
91
+ const newFiles = Array.from(e.target.files);
92
+ const tooLarge = newFiles.filter((f) => f.size > maxSize);
93
+ if (tooLarge.length > 0) {
94
+ alert(`Fichier(s) trop volumineux (max 1 Mo) : ${tooLarge.map((f) => f.name).join(", ")}`);
95
+ }
96
+ setExtMsgFiles((prev) => [...prev, ...newFiles.filter((f) => f.size <= maxSize)]);
97
+ }
98
+ if (extFileInputRef.current) extFileInputRef.current.value = "";
99
+ };
100
+ const handleSendExtMsg = async () => {
101
+ if (!extMsgBody.trim() || !id) return;
102
+ setSendingExtMsg(true);
103
+ try {
104
+ const uploadedIds = [];
105
+ for (const file of extMsgFiles) {
106
+ if (file.size > 1 * 1024 * 1024) continue;
107
+ const formData = new FormData();
108
+ formData.append("file", file);
109
+ formData.append("_payload", JSON.stringify({ alt: file.name }));
110
+ const uploadRes = await fetch("/api/media", { method: "POST", credentials: "include", body: formData });
111
+ if (uploadRes.ok) {
112
+ const d = await uploadRes.json();
113
+ if (d.doc?.id) uploadedIds.push(d.doc.id);
114
+ }
115
+ }
116
+ const messageData = {
117
+ ticket: id,
118
+ body: extMsgBody.trim(),
119
+ authorType: extMsgAuthor,
120
+ isInternal: false,
121
+ skipNotification: true
122
+ };
123
+ if (extMsgDate) {
124
+ messageData.createdAt = new Date(extMsgDate).toISOString();
125
+ }
126
+ if (uploadedIds.length > 0) {
127
+ messageData.attachments = uploadedIds.map((mid) => ({ file: mid }));
128
+ }
129
+ const res = await fetch("/api/ticket-messages", {
130
+ method: "POST",
131
+ headers: { "Content-Type": "application/json" },
132
+ credentials: "include",
133
+ body: JSON.stringify(messageData)
134
+ });
135
+ if (res.ok) {
136
+ setExtMsgBody("");
137
+ setExtMsgDate("");
138
+ setExtMsgFiles([]);
139
+ setShowExtMsg(false);
140
+ fetchAll();
141
+ }
142
+ } catch {
143
+ } finally {
144
+ setSendingExtMsg(false);
145
+ }
146
+ };
147
+ const handleSnooze = async (days, customDate) => {
148
+ if (!id) return;
149
+ setSnoozeSaving(true);
150
+ try {
151
+ let newSnooze = null;
152
+ if (days !== null) {
153
+ const d = /* @__PURE__ */ new Date();
154
+ d.setDate(d.getDate() + days);
155
+ newSnooze = d.toISOString();
156
+ } else if (customDate) {
157
+ newSnooze = new Date(customDate).toISOString();
158
+ }
159
+ await fetch(`/api/tickets/${id}`, {
160
+ method: "PATCH",
161
+ headers: { "Content-Type": "application/json" },
162
+ credentials: "include",
163
+ body: JSON.stringify({ snoozeUntil: newSnooze })
164
+ });
165
+ setSnoozeUntil(newSnooze);
166
+ setShowSnooze(false);
167
+ fetchAll();
168
+ } catch {
169
+ } finally {
170
+ setSnoozeSaving(false);
171
+ }
172
+ };
173
+ const handleNextTicket = async () => {
174
+ try {
175
+ const res = await fetch("/api/tickets?where[status][equals]=open&sort=updatedAt&limit=1&depth=0", { credentials: "include" });
176
+ if (res.ok) {
177
+ const d = await res.json();
178
+ if (d.docs?.length > 0 && String(d.docs[0].id) !== String(id)) {
179
+ setNextTicketId(d.docs[0].id);
180
+ setNextTicketInfo(`${d.docs[0].ticketNumber} \u2014 ${d.docs[0].subject}`);
181
+ } else {
182
+ setNextTicketId(null);
183
+ setNextTicketInfo("Aucun autre ticket ouvert");
184
+ }
185
+ setShowNextTicket(true);
186
+ }
187
+ } catch {
188
+ }
189
+ };
190
+ return {
191
+ statusUpdating,
192
+ handleStatusChange,
193
+ showMerge,
194
+ setShowMerge,
195
+ mergeTarget,
196
+ setMergeTarget,
197
+ mergeTargetInfo,
198
+ setMergeTargetInfo,
199
+ mergeError,
200
+ setMergeError,
201
+ merging,
202
+ handleMergeLookup,
203
+ handleMerge,
204
+ showExtMsg,
205
+ setShowExtMsg,
206
+ extMsgBody,
207
+ setExtMsgBody,
208
+ extMsgAuthor,
209
+ setExtMsgAuthor,
210
+ extMsgDate,
211
+ setExtMsgDate,
212
+ extMsgFiles,
213
+ setExtMsgFiles,
214
+ sendingExtMsg,
215
+ handleExtFileChange,
216
+ handleSendExtMsg,
217
+ showSnooze,
218
+ setShowSnooze,
219
+ snoozeUntil,
220
+ setSnoozeUntil,
221
+ snoozeSaving,
222
+ handleSnooze,
223
+ showNextTicket,
224
+ setShowNextTicket,
225
+ nextTicketId,
226
+ nextTicketInfo,
227
+ handleNextTicket
228
+ };
229
+ }
230
+
231
+ exports.useTicketActions = useTicketActions;