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