@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
|
@@ -1,117 +1,371 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs,
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { Inbox, Paperclip, ChevronUp, ChevronDown, Plus, Link2, X, Search } from 'lucide-react';
|
|
5
|
+
import { SkeletonDashboard } from '../shared/Skeleton.js';
|
|
4
6
|
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
|
|
7
|
+
import styles from '../../styles/PendingEmails.module.scss';
|
|
5
8
|
|
|
6
9
|
function timeAgo(dateStr) {
|
|
7
10
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
8
11
|
const mins = Math.floor(diff / 6e4);
|
|
9
|
-
if (mins < 1) return "
|
|
12
|
+
if (mins < 1) return "\xE0 l'instant";
|
|
10
13
|
if (mins < 60) return `il y a ${mins}min`;
|
|
11
14
|
const hours = Math.floor(mins / 60);
|
|
12
15
|
if (hours < 24) return `il y a ${hours}h`;
|
|
13
|
-
|
|
16
|
+
const days = Math.floor(hours / 24);
|
|
17
|
+
return `il y a ${days}j`;
|
|
14
18
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
19
|
+
function TicketSearchModal({
|
|
20
|
+
suggestions,
|
|
21
|
+
onSelect,
|
|
22
|
+
onClose
|
|
23
|
+
}) {
|
|
24
|
+
const [search, setSearch] = useState("");
|
|
25
|
+
const [results, setResults] = useState([]);
|
|
26
|
+
const [searching, setSearching] = useState(false);
|
|
27
|
+
const doSearch = useCallback(async (q) => {
|
|
28
|
+
if (q.length < 2) {
|
|
29
|
+
setResults([]);
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
setSearching(true);
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(`/api/tickets?where[or][0][ticketNumber][contains]=${encodeURIComponent(q)}&where[or][1][subject][contains]=${encodeURIComponent(q)}&limit=10&sort=-updatedAt&depth=0`);
|
|
35
|
+
if (res.ok) {
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
setResults(data.docs.map((d) => ({
|
|
38
|
+
id: d.id,
|
|
39
|
+
ticketNumber: d.ticketNumber,
|
|
40
|
+
subject: d.subject
|
|
41
|
+
})));
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
setSearching(false);
|
|
46
|
+
}, []);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const timer = setTimeout(() => doSearch(search), 300);
|
|
49
|
+
return () => clearTimeout(timer);
|
|
50
|
+
}, [search, doSearch]);
|
|
51
|
+
const scoreClass = (score) => score >= 0.7 ? styles.scoreHigh : score >= 0.5 ? styles.scoreMedium : styles.scoreLow;
|
|
52
|
+
return /* @__PURE__ */ jsx("div", { className: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { className: styles.modal, onClick: (e) => e.stopPropagation(), children: [
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: styles.modalHeader, children: [
|
|
54
|
+
/* @__PURE__ */ jsx("h3", { className: styles.modalTitle, children: "Rattacher a un ticket" }),
|
|
55
|
+
/* @__PURE__ */ jsx("button", { onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
|
|
56
|
+
] }),
|
|
57
|
+
suggestions.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles.sectionBlock, children: [
|
|
58
|
+
/* @__PURE__ */ jsx("div", { className: styles.sectionLabel, children: "Suggestions" }),
|
|
59
|
+
suggestions.map((s) => /* @__PURE__ */ jsxs("button", { onClick: () => onSelect(s.id), className: styles.resultRow, children: [
|
|
60
|
+
/* @__PURE__ */ jsx("span", { className: styles.resultTicketNum, children: s.ticketNumber }),
|
|
61
|
+
/* @__PURE__ */ jsx("span", { className: styles.resultSubject, children: s.subject }),
|
|
62
|
+
/* @__PURE__ */ jsxs("span", { className: `${styles.scoreBadge} ${scoreClass(s.score)}`, children: [
|
|
63
|
+
Math.round(s.score * 100),
|
|
64
|
+
"%"
|
|
65
|
+
] })
|
|
66
|
+
] }, s.id))
|
|
67
|
+
] }),
|
|
68
|
+
/* @__PURE__ */ jsxs("div", { className: styles.searchWrap, children: [
|
|
69
|
+
/* @__PURE__ */ jsx(Search, { size: 16, className: styles.searchIcon }),
|
|
70
|
+
/* @__PURE__ */ jsx(
|
|
71
|
+
"input",
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
placeholder: "Rechercher un ticket (TK-0042, sujet...)...",
|
|
75
|
+
value: search,
|
|
76
|
+
onChange: (e) => setSearch(e.target.value),
|
|
77
|
+
className: styles.searchInput
|
|
34
78
|
}
|
|
35
|
-
|
|
36
|
-
|
|
79
|
+
)
|
|
80
|
+
] }),
|
|
81
|
+
searching && /* @__PURE__ */ jsx("div", { className: styles.searchingHint, children: "Recherche..." }),
|
|
82
|
+
results.map((r) => /* @__PURE__ */ jsxs("button", { onClick: () => onSelect(r.id), className: styles.resultRow, children: [
|
|
83
|
+
/* @__PURE__ */ jsx("span", { className: styles.resultTicketNum, children: r.ticketNumber }),
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: styles.resultSubject, children: r.subject })
|
|
85
|
+
] }, r.id)),
|
|
86
|
+
search.length >= 2 && !searching && results.length === 0 && /* @__PURE__ */ jsx("div", { className: styles.noResults, children: "Aucun ticket trouve" })
|
|
87
|
+
] }) });
|
|
88
|
+
}
|
|
89
|
+
function ClientPickerModal({
|
|
90
|
+
defaultEmail,
|
|
91
|
+
detectedClient,
|
|
92
|
+
onSelect,
|
|
93
|
+
onClose
|
|
94
|
+
}) {
|
|
95
|
+
const [search, setSearch] = useState("");
|
|
96
|
+
const [results, setResults] = useState([]);
|
|
97
|
+
const [searching, setSearching] = useState(false);
|
|
98
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
99
|
+
const [creating, setCreating] = useState(false);
|
|
100
|
+
const [newClient, setNewClient] = useState({ firstName: "", lastName: "", email: defaultEmail, company: "" });
|
|
101
|
+
const [createError, setCreateError] = useState("");
|
|
102
|
+
const doSearch = useCallback(async (q) => {
|
|
103
|
+
if (q.length < 2) {
|
|
104
|
+
setResults([]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
setSearching(true);
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(`/api/support-clients?where[or][0][email][contains]=${encodeURIComponent(q)}&where[or][1][firstName][contains]=${encodeURIComponent(q)}&where[or][2][lastName][contains]=${encodeURIComponent(q)}&where[or][3][company][contains]=${encodeURIComponent(q)}&limit=10&sort=-updatedAt&depth=0`);
|
|
110
|
+
if (res.ok) {
|
|
111
|
+
const data = await res.json();
|
|
112
|
+
setResults(data.docs.map((d) => ({
|
|
113
|
+
id: d.id,
|
|
114
|
+
firstName: d.firstName,
|
|
115
|
+
lastName: d.lastName,
|
|
116
|
+
email: d.email,
|
|
117
|
+
company: d.company
|
|
118
|
+
})));
|
|
37
119
|
}
|
|
38
|
-
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
setSearching(false);
|
|
123
|
+
}, []);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const timer = setTimeout(() => doSearch(search), 300);
|
|
39
126
|
return () => clearTimeout(timer);
|
|
40
|
-
}, [
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
127
|
+
}, [search, doSearch]);
|
|
128
|
+
const handleCreate = async () => {
|
|
129
|
+
setCreateError("");
|
|
130
|
+
if (!newClient.email.trim() || !newClient.firstName.trim() || !newClient.company.trim()) {
|
|
131
|
+
setCreateError("Email, prenom et entreprise sont obligatoires");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setCreating(true);
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch("/api/support-clients", {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
email: newClient.email.trim(),
|
|
141
|
+
firstName: newClient.firstName.trim(),
|
|
142
|
+
lastName: newClient.lastName.trim() || void 0,
|
|
143
|
+
company: newClient.company.trim(),
|
|
144
|
+
password: crypto.randomUUID()
|
|
145
|
+
})
|
|
146
|
+
});
|
|
147
|
+
if (res.ok) {
|
|
148
|
+
const data = await res.json();
|
|
149
|
+
onSelect(data.doc.id);
|
|
150
|
+
} else {
|
|
151
|
+
const err = await res.json().catch(() => ({}));
|
|
152
|
+
setCreateError(err.errors?.[0]?.message || "Erreur lors de la creation");
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
setCreateError("Erreur reseau");
|
|
156
|
+
}
|
|
157
|
+
setCreating(false);
|
|
54
158
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
159
|
+
const clientLabel = (c) => {
|
|
160
|
+
const parts = [c.firstName, c.lastName].filter(Boolean).join(" ");
|
|
161
|
+
return parts ? `${parts}${c.company ? ` \u2014 ${c.company}` : ""}` : c.email || "";
|
|
162
|
+
};
|
|
163
|
+
return /* @__PURE__ */ jsx("div", { className: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { className: styles.modal, onClick: (e) => e.stopPropagation(), children: [
|
|
164
|
+
/* @__PURE__ */ jsxs("div", { className: styles.modalHeader, children: [
|
|
165
|
+
/* @__PURE__ */ jsx("h3", { className: styles.modalTitle, children: "Choisir un client" }),
|
|
166
|
+
/* @__PURE__ */ jsx("button", { onClick: onClose, className: styles.modalClose, children: /* @__PURE__ */ jsx(X, { size: 20 }) })
|
|
167
|
+
] }),
|
|
168
|
+
detectedClient && /* @__PURE__ */ jsxs("div", { className: styles.sectionBlock, children: [
|
|
169
|
+
/* @__PURE__ */ jsx("div", { className: styles.sectionLabel, children: "Client detecte" }),
|
|
170
|
+
/* @__PURE__ */ jsxs("button", { onClick: () => onSelect(detectedClient.id), className: styles.detectedRow, children: [
|
|
171
|
+
/* @__PURE__ */ jsxs("span", { className: styles.detectedLabel, children: [
|
|
172
|
+
/* @__PURE__ */ jsx("span", { className: styles.detectedName, children: clientLabel(detectedClient) }),
|
|
173
|
+
detectedClient.email && /* @__PURE__ */ jsx("span", { className: styles.detectedEmail, children: detectedClient.email })
|
|
174
|
+
] }),
|
|
175
|
+
/* @__PURE__ */ jsx("span", { className: styles.useBtn, children: "Utiliser" })
|
|
62
176
|
] })
|
|
63
177
|
] }),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
178
|
+
/* @__PURE__ */ jsxs("div", { className: styles.searchWrap, children: [
|
|
179
|
+
/* @__PURE__ */ jsx(Search, { size: 16, className: styles.searchIcon }),
|
|
180
|
+
/* @__PURE__ */ jsx(
|
|
181
|
+
"input",
|
|
182
|
+
{
|
|
183
|
+
type: "text",
|
|
184
|
+
placeholder: "Rechercher un client (nom, email, entreprise)...",
|
|
185
|
+
value: search,
|
|
186
|
+
onChange: (e) => setSearch(e.target.value),
|
|
187
|
+
className: styles.searchInput
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
] }),
|
|
191
|
+
searching && /* @__PURE__ */ jsx("div", { className: styles.searchingHint, children: "Recherche..." }),
|
|
192
|
+
results.map((r) => /* @__PURE__ */ jsx("button", { onClick: () => onSelect(r.id), className: styles.resultRow, children: /* @__PURE__ */ jsxs("span", { className: styles.clientResultLabel, children: [
|
|
193
|
+
/* @__PURE__ */ jsx("span", { className: styles.clientResultName, children: clientLabel(r) }),
|
|
194
|
+
r.email && /* @__PURE__ */ jsx("span", { className: styles.clientResultEmail, children: r.email })
|
|
195
|
+
] }) }, r.id)),
|
|
196
|
+
search.length >= 2 && !searching && results.length === 0 && /* @__PURE__ */ jsx("div", { className: styles.noResults, children: "Aucun client trouve" }),
|
|
197
|
+
/* @__PURE__ */ jsx("div", { className: styles.separator, children: /* @__PURE__ */ jsxs("button", { onClick: () => setShowCreate(!showCreate), className: styles.createToggle, children: [
|
|
198
|
+
/* @__PURE__ */ jsx(Plus, { size: 14 }),
|
|
199
|
+
showCreate ? "Annuler la creation" : "Creer un nouveau client"
|
|
200
|
+
] }) }),
|
|
201
|
+
showCreate && /* @__PURE__ */ jsxs("div", { className: styles.createForm, children: [
|
|
202
|
+
createError && /* @__PURE__ */ jsx("div", { className: styles.formError, children: createError }),
|
|
203
|
+
/* @__PURE__ */ jsxs("div", { className: styles.createFormRow, children: [
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
"input",
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
placeholder: "Prenom *",
|
|
209
|
+
value: newClient.firstName,
|
|
210
|
+
onChange: (e) => setNewClient((p) => ({ ...p, firstName: e.target.value })),
|
|
211
|
+
className: styles.formInputHalf
|
|
212
|
+
}
|
|
213
|
+
),
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
"input",
|
|
216
|
+
{
|
|
217
|
+
type: "text",
|
|
218
|
+
placeholder: "Nom",
|
|
219
|
+
value: newClient.lastName,
|
|
220
|
+
onChange: (e) => setNewClient((p) => ({ ...p, lastName: e.target.value })),
|
|
221
|
+
className: styles.formInputHalf
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
] }),
|
|
225
|
+
/* @__PURE__ */ jsx(
|
|
226
|
+
"input",
|
|
227
|
+
{
|
|
228
|
+
type: "email",
|
|
229
|
+
placeholder: "Email *",
|
|
230
|
+
value: newClient.email,
|
|
231
|
+
onChange: (e) => setNewClient((p) => ({ ...p, email: e.target.value })),
|
|
232
|
+
className: styles.formInput
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
/* @__PURE__ */ jsx(
|
|
236
|
+
"input",
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
placeholder: "Entreprise *",
|
|
240
|
+
value: newClient.company,
|
|
241
|
+
onChange: (e) => setNewClient((p) => ({ ...p, company: e.target.value })),
|
|
242
|
+
className: styles.formInput
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
/* @__PURE__ */ jsx("button", { onClick: handleCreate, disabled: creating, className: styles.submitBtn, children: creating ? "Creation..." : "Creer et utiliser" })
|
|
246
|
+
] })
|
|
247
|
+
] }) });
|
|
248
|
+
}
|
|
249
|
+
function EmailCard({
|
|
250
|
+
email,
|
|
251
|
+
onProcess,
|
|
252
|
+
processing
|
|
253
|
+
}) {
|
|
254
|
+
const [expanded, setExpanded] = useState(false);
|
|
255
|
+
const [showLinkModal, setShowLinkModal] = useState(false);
|
|
256
|
+
const [showClientPicker, setShowClientPicker] = useState(false);
|
|
257
|
+
const attachmentCount = email.attachments?.length || 0;
|
|
258
|
+
const preview = email.body.slice(0, 200) + (email.body.length > 200 ? "..." : "");
|
|
259
|
+
const suggestions = email.suggestedTickets || [];
|
|
260
|
+
const isPending = email.status === "pending";
|
|
261
|
+
const processedTicketNumber = typeof email.processedTicket === "object" ? email.processedTicket?.ticketNumber : null;
|
|
262
|
+
const suggestionClass = (score) => score >= 0.7 ? styles.suggestionHigh : score >= 0.5 ? styles.suggestionMedium : styles.suggestionLow;
|
|
263
|
+
return /* @__PURE__ */ jsxs("div", { className: `${styles.card} ${processing ? styles.cardProcessing : ""}`, children: [
|
|
264
|
+
/* @__PURE__ */ jsxs("div", { className: styles.cardHeader, children: [
|
|
265
|
+
/* @__PURE__ */ jsxs("div", { className: styles.cardInfo, children: [
|
|
266
|
+
/* @__PURE__ */ jsxs("div", { className: styles.senderRow, children: [
|
|
267
|
+
/* @__PURE__ */ jsx("span", { className: styles.senderName, children: email.senderName || email.senderEmail }),
|
|
268
|
+
email.senderName && /* @__PURE__ */ jsxs("span", { className: styles.senderEmail, children: [
|
|
269
|
+
"<",
|
|
270
|
+
email.senderEmail,
|
|
271
|
+
">"
|
|
272
|
+
] })
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsx("div", { className: styles.cardSubject, children: email.subject }),
|
|
275
|
+
/* @__PURE__ */ jsxs("div", { className: styles.cardMeta, children: [
|
|
276
|
+
/* @__PURE__ */ jsx("span", { children: timeAgo(email.createdAt) }),
|
|
277
|
+
attachmentCount > 0 && /* @__PURE__ */ jsxs("span", { className: styles.attachment, children: [
|
|
278
|
+
/* @__PURE__ */ jsx(Paperclip, { size: 12 }),
|
|
279
|
+
" ",
|
|
280
|
+
attachmentCount,
|
|
281
|
+
" PJ"
|
|
282
|
+
] })
|
|
283
|
+
] })
|
|
284
|
+
] }),
|
|
285
|
+
!isPending && /* @__PURE__ */ jsxs("div", { className: `${styles.statusBadge} ${email.status === "processed" ? styles.statusProcessed : styles.statusIgnored}`, children: [
|
|
286
|
+
email.processedAction === "ticket_created" && `Ticket cree${processedTicketNumber ? ` (${processedTicketNumber})` : ""}`,
|
|
287
|
+
email.processedAction === "message_added" && `Rattache${processedTicketNumber ? ` a ${processedTicketNumber}` : ""}`,
|
|
288
|
+
email.processedAction === "ignored" && "Ignore"
|
|
289
|
+
] })
|
|
69
290
|
] }),
|
|
70
|
-
suggestions.length > 0 && isPending && /* @__PURE__ */ jsx("div", {
|
|
291
|
+
suggestions.length > 0 && isPending && /* @__PURE__ */ jsx("div", { className: styles.suggestions, children: suggestions.map((s) => /* @__PURE__ */ jsxs("span", { className: `${styles.suggestionChip} ${suggestionClass(s.score)}`, children: [
|
|
71
292
|
"Similaire a ",
|
|
72
|
-
|
|
293
|
+
s.ticketNumber,
|
|
73
294
|
" (",
|
|
74
|
-
Math.round(
|
|
295
|
+
Math.round(s.score * 100),
|
|
75
296
|
"%)"
|
|
76
|
-
] },
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
297
|
+
] }, s.id)) }),
|
|
298
|
+
/* @__PURE__ */ jsxs("div", { className: styles.bodySection, children: [
|
|
299
|
+
/* @__PURE__ */ jsx("div", { className: `${styles.bodyText} ${expanded ? styles.bodyExpanded : ""}`, children: expanded ? email.body : preview }),
|
|
300
|
+
email.body.length > 200 && /* @__PURE__ */ jsx("button", { onClick: () => setExpanded(!expanded), className: styles.expandBtn, children: expanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
301
|
+
/* @__PURE__ */ jsx(ChevronUp, { size: 14 }),
|
|
302
|
+
" Reduire"
|
|
303
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
304
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: 14 }),
|
|
305
|
+
" Voir tout"
|
|
306
|
+
] }) })
|
|
86
307
|
] }),
|
|
87
|
-
|
|
88
|
-
/* @__PURE__ */
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
308
|
+
isPending && /* @__PURE__ */ jsxs("div", { className: styles.actions, children: [
|
|
309
|
+
/* @__PURE__ */ jsxs(
|
|
310
|
+
"button",
|
|
311
|
+
{
|
|
312
|
+
onClick: () => setShowClientPicker(true),
|
|
313
|
+
disabled: processing,
|
|
314
|
+
className: `${styles.actionBtn} ${styles.btnCreate}`,
|
|
315
|
+
children: [
|
|
316
|
+
/* @__PURE__ */ jsx(Plus, { size: 14 }),
|
|
317
|
+
"Creer un ticket"
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
/* @__PURE__ */ jsxs(
|
|
322
|
+
"button",
|
|
323
|
+
{
|
|
324
|
+
onClick: () => setShowLinkModal(true),
|
|
325
|
+
disabled: processing,
|
|
326
|
+
className: `${styles.actionBtn} ${styles.btnLink}`,
|
|
327
|
+
children: [
|
|
328
|
+
/* @__PURE__ */ jsx(Link2, { size: 14 }),
|
|
329
|
+
"Rattacher a un ticket"
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
/* @__PURE__ */ jsxs(
|
|
334
|
+
"button",
|
|
335
|
+
{
|
|
336
|
+
onClick: () => onProcess("ignore"),
|
|
337
|
+
disabled: processing,
|
|
338
|
+
className: `${styles.actionBtn} ${styles.btnIgnore}`,
|
|
339
|
+
children: [
|
|
340
|
+
/* @__PURE__ */ jsx(X, { size: 14 }),
|
|
341
|
+
"Ignorer"
|
|
342
|
+
]
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
] }),
|
|
346
|
+
showLinkModal && /* @__PURE__ */ jsx(
|
|
347
|
+
TicketSearchModal,
|
|
348
|
+
{
|
|
349
|
+
suggestions,
|
|
350
|
+
onSelect: (ticketId) => {
|
|
92
351
|
setShowLinkModal(false);
|
|
93
|
-
onProcess("add_to_ticket",
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/* @__PURE__ */ jsx("strong", { children: r.ticketNumber }),
|
|
111
|
-
" ",
|
|
112
|
-
r.subject
|
|
113
|
-
] }, r.id))
|
|
114
|
-
] }) })
|
|
352
|
+
onProcess("add_to_ticket", ticketId);
|
|
353
|
+
},
|
|
354
|
+
onClose: () => setShowLinkModal(false)
|
|
355
|
+
}
|
|
356
|
+
),
|
|
357
|
+
showClientPicker && /* @__PURE__ */ jsx(
|
|
358
|
+
ClientPickerModal,
|
|
359
|
+
{
|
|
360
|
+
defaultEmail: email.senderEmail,
|
|
361
|
+
detectedClient: typeof email.client === "object" && email.client ? { id: email.client.id, firstName: email.client.firstName, lastName: email.client.lastName, email: email.client.email, company: email.client.company } : null,
|
|
362
|
+
onSelect: (clientId) => {
|
|
363
|
+
setShowClientPicker(false);
|
|
364
|
+
onProcess("create_ticket", void 0, clientId);
|
|
365
|
+
},
|
|
366
|
+
onClose: () => setShowClientPicker(false)
|
|
367
|
+
}
|
|
368
|
+
)
|
|
115
369
|
] });
|
|
116
370
|
}
|
|
117
371
|
const PendingEmailsClient = () => {
|
|
@@ -120,12 +374,15 @@ const PendingEmailsClient = () => {
|
|
|
120
374
|
const [loading, setLoading] = useState(true);
|
|
121
375
|
const [tab, setTab] = useState("pending");
|
|
122
376
|
const [processing, setProcessing] = useState(null);
|
|
377
|
+
const [sessionExpired, setSessionExpired] = useState(false);
|
|
123
378
|
const fetchEmails = useCallback(async () => {
|
|
124
379
|
try {
|
|
125
380
|
const res = await fetch(`/api/pending-emails?where[status][equals]=${tab}&sort=-createdAt&limit=50&depth=1`);
|
|
126
381
|
if (res.ok) {
|
|
127
382
|
const data = await res.json();
|
|
128
383
|
setEmails(data.docs);
|
|
384
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
385
|
+
setSessionExpired(true);
|
|
129
386
|
}
|
|
130
387
|
} catch {
|
|
131
388
|
}
|
|
@@ -136,14 +393,24 @@ const PendingEmailsClient = () => {
|
|
|
136
393
|
fetchEmails();
|
|
137
394
|
}, [fetchEmails]);
|
|
138
395
|
useEffect(() => {
|
|
139
|
-
if (tab !== "pending") return;
|
|
140
|
-
const
|
|
141
|
-
return () => clearInterval(
|
|
142
|
-
}, [fetchEmails, tab]);
|
|
396
|
+
if (sessionExpired || tab !== "pending") return;
|
|
397
|
+
const interval = setInterval(fetchEmails, 3e4);
|
|
398
|
+
return () => clearInterval(interval);
|
|
399
|
+
}, [fetchEmails, sessionExpired, tab]);
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
if (sessionExpired) return;
|
|
402
|
+
const onFocus = () => fetchEmails();
|
|
403
|
+
window.addEventListener("focus", onFocus);
|
|
404
|
+
return () => window.removeEventListener("focus", onFocus);
|
|
405
|
+
}, [fetchEmails, sessionExpired]);
|
|
143
406
|
const handleProcess = async (emailId, action, ticketId, clientId) => {
|
|
144
407
|
setProcessing(emailId);
|
|
145
408
|
try {
|
|
146
|
-
const res = await fetch(`/api/support/pending-emails/${emailId}/process`, {
|
|
409
|
+
const res = await fetch(`/api/support/pending-emails/${emailId}/process`, {
|
|
410
|
+
method: "POST",
|
|
411
|
+
headers: { "Content-Type": "application/json" },
|
|
412
|
+
body: JSON.stringify({ action, ticketId, clientId })
|
|
413
|
+
});
|
|
147
414
|
if (res.ok) {
|
|
148
415
|
setEmails((prev) => prev.filter((e) => e.id !== emailId));
|
|
149
416
|
} else {
|
|
@@ -155,17 +422,43 @@ const PendingEmailsClient = () => {
|
|
|
155
422
|
}
|
|
156
423
|
setProcessing(null);
|
|
157
424
|
};
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
425
|
+
if (loading) {
|
|
426
|
+
return /* @__PURE__ */ jsx("div", { className: styles.loadingWrap, children: /* @__PURE__ */ jsx(SkeletonDashboard, {}) });
|
|
427
|
+
}
|
|
428
|
+
const tabs = [
|
|
429
|
+
{ key: "pending", label: t("pendingEmails.tabs.pending") },
|
|
430
|
+
{ key: "processed", label: t("pendingEmails.tabs.processed") },
|
|
431
|
+
{ key: "ignored", label: t("pendingEmails.tabs.ignored") }
|
|
432
|
+
];
|
|
433
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.page, children: [
|
|
434
|
+
/* @__PURE__ */ jsxs("div", { className: styles.header, children: [
|
|
435
|
+
/* @__PURE__ */ jsxs("div", { className: styles.headerLeft, children: [
|
|
436
|
+
/* @__PURE__ */ jsxs("h1", { className: styles.title, children: [
|
|
437
|
+
/* @__PURE__ */ jsx("span", { className: styles.titleIcon, children: /* @__PURE__ */ jsx(Inbox, { size: 24 }) }),
|
|
438
|
+
t("pendingEmails.title")
|
|
439
|
+
] }),
|
|
440
|
+
/* @__PURE__ */ jsx("p", { className: styles.subtitle, children: t("pendingEmails.subtitle") })
|
|
164
441
|
] }),
|
|
165
|
-
tab === "pending" && emails.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
442
|
+
tab === "pending" && emails.length > 0 && /* @__PURE__ */ jsx("span", { className: styles.pendingBadge, children: t("pendingEmails.pendingCount", { count: String(emails.length) }) })
|
|
166
443
|
] }),
|
|
167
|
-
/* @__PURE__ */ jsx("div", {
|
|
168
|
-
|
|
444
|
+
/* @__PURE__ */ jsx("div", { className: styles.tabs, children: tabs.map((tk) => /* @__PURE__ */ jsx(
|
|
445
|
+
"button",
|
|
446
|
+
{
|
|
447
|
+
onClick: () => setTab(tk.key),
|
|
448
|
+
className: `${styles.tab} ${tab === tk.key ? styles.tabActive : ""}`,
|
|
449
|
+
children: tk.label
|
|
450
|
+
},
|
|
451
|
+
tk.key
|
|
452
|
+
)) }),
|
|
453
|
+
emails.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.empty, children: tab === "pending" ? t("pendingEmails.empty.pending") : tab === "processed" ? t("pendingEmails.empty.processed") : t("pendingEmails.empty.ignored") }) : emails.map((email) => /* @__PURE__ */ jsx(
|
|
454
|
+
EmailCard,
|
|
455
|
+
{
|
|
456
|
+
email,
|
|
457
|
+
onProcess: (action, ticketId, clientId) => handleProcess(email.id, action, ticketId, clientId),
|
|
458
|
+
processing: processing === email.id
|
|
459
|
+
},
|
|
460
|
+
email.id
|
|
461
|
+
))
|
|
169
462
|
] });
|
|
170
463
|
};
|
|
171
464
|
|