@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.
Files changed (50) hide show
  1. package/dist/components/RichTextEditor/index.cjs +233 -0
  2. package/dist/components/RichTextEditor/index.js +232 -0
  3. package/dist/components/TicketConversation/components/CodeBlock.cjs +24 -7
  4. package/dist/components/TicketConversation/components/CodeBlock.js +23 -9
  5. package/dist/index.cjs +19 -1
  6. package/dist/index.js +19 -1
  7. package/dist/views/BillingView/client.cjs +260 -103
  8. package/dist/views/BillingView/client.js +259 -103
  9. package/dist/views/ChatView/client.cjs +184 -137
  10. package/dist/views/ChatView/client.js +180 -137
  11. package/dist/views/CrmView/client.cjs +270 -122
  12. package/dist/views/CrmView/client.js +266 -122
  13. package/dist/views/EmailTrackingView/client.cjs +80 -69
  14. package/dist/views/EmailTrackingView/client.js +80 -70
  15. package/dist/views/ImportConversationView/client.cjs +127 -94
  16. package/dist/views/ImportConversationView/client.js +123 -94
  17. package/dist/views/LogsView/client.cjs +56 -58
  18. package/dist/views/LogsView/client.js +52 -58
  19. package/dist/views/NewTicketView/client.cjs +39 -55
  20. package/dist/views/NewTicketView/client.js +38 -55
  21. package/dist/views/PendingEmailsView/client.cjs +399 -102
  22. package/dist/views/PendingEmailsView/client.js +396 -103
  23. package/dist/views/SupportDashboardView/client.cjs +276 -137
  24. package/dist/views/SupportDashboardView/client.js +275 -137
  25. package/dist/views/TicketDetailView/client.cjs +487 -204
  26. package/dist/views/TicketDetailView/client.js +486 -204
  27. package/dist/views/TicketInboxView/client.cjs +62 -65
  28. package/dist/views/TicketInboxView/client.js +62 -66
  29. package/dist/views/TicketingSettingsView/client.cjs +10 -8
  30. package/dist/views/TicketingSettingsView/client.js +10 -8
  31. package/dist/views/TimeDashboardView/client.cjs +70 -59
  32. package/dist/views/TimeDashboardView/client.js +69 -59
  33. package/package.json +6 -2
  34. package/src/components/RichTextEditor/index.tsx +261 -0
  35. package/src/components/TicketConversation/components/CodeBlock.tsx +53 -14
  36. package/src/plugin.ts +2 -0
  37. package/src/utils/emailTemplate.ts +37 -0
  38. package/src/views/BillingView/client.tsx +362 -69
  39. package/src/views/ChatView/client.tsx +225 -140
  40. package/src/views/CrmView/client.tsx +447 -189
  41. package/src/views/EmailTrackingView/client.tsx +111 -71
  42. package/src/views/ImportConversationView/client.tsx +255 -70
  43. package/src/views/LogsView/client.tsx +85 -50
  44. package/src/views/NewTicketView/client.tsx +37 -53
  45. package/src/views/PendingEmailsView/client.tsx +512 -92
  46. package/src/views/SupportDashboardView/client.tsx +294 -134
  47. package/src/views/TicketDetailView/client.tsx +486 -213
  48. package/src/views/TicketInboxView/client.tsx +52 -61
  49. package/src/views/TicketingSettingsView/client.tsx +10 -9
  50. package/src/views/TimeDashboardView/client.tsx +184 -69
@@ -1,117 +1,371 @@
1
1
  "use client";
2
- import { jsxs, jsx } from 'react/jsx-runtime';
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 "a l'instant";
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
- return `il y a ${Math.floor(hours / 24)}j`;
16
+ const days = Math.floor(hours / 24);
17
+ return `il y a ${days}j`;
14
18
  }
15
- function EmailCard({ email, onProcess, processing, t }) {
16
- const [expanded, setExpanded] = useState(false);
17
- const [showLinkModal, setShowLinkModal] = useState(false);
18
- const [linkSearch, setLinkSearch] = useState("");
19
- const [linkResults, setLinkResults] = useState([]);
20
- const isPending = email.status === "pending";
21
- const preview = email.body.slice(0, 200) + (email.body.length > 200 ? "..." : "");
22
- const suggestions = email.suggestedTickets || [];
23
- useEffect(() => {
24
- if (!linkSearch || linkSearch.length < 2) {
25
- setLinkResults([]);
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
- const timer = setTimeout(async () => {
29
- try {
30
- const res = await fetch(`/api/tickets?where[or][0][ticketNumber][contains]=${encodeURIComponent(linkSearch)}&where[or][1][subject][contains]=${encodeURIComponent(linkSearch)}&limit=10&sort=-updatedAt&depth=0`);
31
- if (res.ok) {
32
- const data = await res.json();
33
- setLinkResults(data.docs.map((d) => ({ id: d.id, ticketNumber: d.ticketNumber, subject: d.subject })));
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
- } catch (err) {
36
- console.warn("[support] ticket search error:", err);
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
- }, 300);
120
+ } catch {
121
+ }
122
+ setSearching(false);
123
+ }, []);
124
+ useEffect(() => {
125
+ const timer = setTimeout(() => doSearch(search), 300);
39
126
  return () => clearTimeout(timer);
40
- }, [linkSearch]);
41
- const S = {
42
- card: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 12, opacity: processing ? 0.5 : 1 },
43
- senderName: { fontWeight: 600, fontSize: 14 },
44
- senderEmail: { fontSize: 12, color: "var(--theme-elevation-500)" },
45
- subject: { fontWeight: 600, fontSize: 13, marginTop: 4 },
46
- meta: { fontSize: 12, color: "var(--theme-elevation-400)", marginTop: 2 },
47
- body: { fontSize: 13, color: "var(--theme-text)", padding: "8px 0", whiteSpace: "pre-wrap", lineHeight: 1.5 },
48
- actions: { display: "flex", gap: 8, marginTop: 8 },
49
- btn: { padding: "6px 14px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, fontWeight: 600, cursor: "pointer", background: "var(--theme-elevation-0)", color: "var(--theme-text)" },
50
- btnCreate: { background: "#2563eb", color: "#fff", border: "none" },
51
- btnIgnore: { color: "#dc2626", borderColor: "#dc2626" },
52
- overlay: { position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 100 },
53
- modal: { background: "var(--theme-elevation-0)", borderRadius: 12, padding: 24, maxWidth: 480, width: "100%", maxHeight: "80vh", overflowY: "auto" }
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
- return /* @__PURE__ */ jsxs("div", { style: S.card, children: [
56
- /* @__PURE__ */ jsxs("div", { children: [
57
- /* @__PURE__ */ jsx("span", { style: S.senderName, children: email.senderName || email.senderEmail }),
58
- email.senderName && /* @__PURE__ */ jsxs("span", { style: S.senderEmail, children: [
59
- " <",
60
- email.senderEmail,
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__ */ jsx("div", { style: S.subject, children: email.subject }),
65
- /* @__PURE__ */ jsxs("div", { style: S.meta, children: [
66
- timeAgo(email.createdAt),
67
- " ",
68
- email.attachments?.length ? `-- ${email.attachments.length} PJ` : ""
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", { style: { display: "flex", gap: 6, marginTop: 6, flexWrap: "wrap" }, children: suggestions.map((s2) => /* @__PURE__ */ jsxs("span", { style: { padding: "2px 8px", borderRadius: 4, fontSize: 11, background: s2.score >= 0.7 ? "#dcfce7" : "#fef3c7", color: s2.score >= 0.7 ? "#166534" : "#92400e" }, children: [
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
- s2.ticketNumber,
293
+ s.ticketNumber,
73
294
  " (",
74
- Math.round(s2.score * 100),
295
+ Math.round(s.score * 100),
75
296
  "%)"
76
- ] }, s2.id)) }),
77
- /* @__PURE__ */ jsx("div", { style: S.body, children: expanded ? email.body : preview }),
78
- email.body.length > 200 && /* @__PURE__ */ jsx("button", { onClick: () => setExpanded(!expanded), style: { ...S.btn, fontSize: 11, padding: "2px 8px" }, children: expanded ? t("pendingEmails.collapse") : t("pendingEmails.expand") }),
79
- isPending && /* @__PURE__ */ jsxs("div", { style: S.actions, children: [
80
- /* @__PURE__ */ jsx("button", { onClick: () => {
81
- const clientId = typeof email.client === "object" && email.client ? email.client.id : void 0;
82
- onProcess("create_ticket", void 0, clientId);
83
- }, disabled: processing, style: { ...S.btn, ...S.btnCreate }, children: t("pendingEmails.actions.createTicket") }),
84
- /* @__PURE__ */ jsx("button", { onClick: () => setShowLinkModal(true), disabled: processing, style: S.btn, children: t("pendingEmails.actions.linkToTicket") }),
85
- /* @__PURE__ */ jsx("button", { onClick: () => onProcess("ignore"), disabled: processing, style: { ...S.btn, ...S.btnIgnore }, children: t("pendingEmails.actions.ignore") })
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
- showLinkModal && /* @__PURE__ */ jsx("div", { style: S.overlay, onClick: () => setShowLinkModal(false), children: /* @__PURE__ */ jsxs("div", { style: S.modal, onClick: (e) => e.stopPropagation(), children: [
88
- /* @__PURE__ */ jsx("h3", { style: { margin: "0 0 12px", fontSize: 16, fontWeight: 700 }, children: t("pendingEmails.linkModal.title") }),
89
- suggestions.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
90
- /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("pendingEmails.linkModal.suggestions") }),
91
- suggestions.map((s2) => /* @__PURE__ */ jsxs("button", { onClick: () => {
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", s2.id);
94
- }, style: { display: "block", width: "100%", padding: "8px 12px", border: "1px solid var(--theme-elevation-200)", borderRadius: 6, background: "var(--theme-elevation-0)", cursor: "pointer", textAlign: "left", marginBottom: 4, fontSize: 13 }, children: [
95
- /* @__PURE__ */ jsx("strong", { children: s2.ticketNumber }),
96
- " ",
97
- s2.subject,
98
- " ",
99
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, color: "#16a34a" }, children: [
100
- Math.round(s2.score * 100),
101
- "%"
102
- ] })
103
- ] }, s2.id))
104
- ] }),
105
- /* @__PURE__ */ jsx("input", { type: "text", placeholder: t("pendingEmails.linkModal.searchPlaceholder"), value: linkSearch, onChange: (e) => setLinkSearch(e.target.value), style: { width: "100%", padding: "8px 12px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 13, marginBottom: 8, color: "var(--theme-text)", background: "var(--theme-elevation-0)" } }),
106
- linkResults.map((r) => /* @__PURE__ */ jsxs("button", { onClick: () => {
107
- setShowLinkModal(false);
108
- onProcess("add_to_ticket", r.id);
109
- }, style: { display: "block", width: "100%", padding: "8px 12px", border: "1px solid var(--theme-elevation-200)", borderRadius: 6, background: "var(--theme-elevation-0)", cursor: "pointer", textAlign: "left", marginBottom: 4, fontSize: 13 }, children: [
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 iv = setInterval(fetchEmails, 3e4);
141
- return () => clearInterval(iv);
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`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, ticketId, clientId }) });
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
- const tabs = [{ key: "pending", label: t("pendingEmails.tabs.pending") }, { key: "processed", label: t("pendingEmails.tabs.processed") }, { key: "ignored", label: t("pendingEmails.tabs.ignored") }];
159
- return /* @__PURE__ */ jsxs("div", { style: { padding: "20px 30px", maxWidth: 900, margin: "0 auto" }, children: [
160
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
161
- /* @__PURE__ */ jsxs("div", { children: [
162
- /* @__PURE__ */ jsx("h1", { style: { fontSize: 22, fontWeight: 700, margin: 0, color: "var(--theme-text)" }, children: t("pendingEmails.title") }),
163
- /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: "var(--theme-elevation-500)", margin: "4px 0 0" }, children: t("pendingEmails.subtitle") })
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", { style: { padding: "4px 10px", borderRadius: 10, background: "#fef2f2", color: "#dc2626", fontSize: 12, fontWeight: 700 }, children: t("pendingEmails.pendingCount", { count: String(emails.length) }) })
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", { style: { display: "flex", gap: 4, marginBottom: 16 }, children: tabs.map((tb) => /* @__PURE__ */ jsx("button", { onClick: () => setTab(tb.key), style: { padding: "6px 12px", borderRadius: 6, border: "none", background: tab === tb.key ? "var(--theme-elevation-100)" : "none", cursor: "pointer", fontSize: 13, fontWeight: tab === tb.key ? 700 : 500, color: tab === tb.key ? "var(--theme-text)" : "var(--theme-elevation-500)" }, children: tb.label }, tb.key)) }),
168
- loading ? /* @__PURE__ */ jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t("common.loading") }) : emails.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t(`pendingEmails.empty.${tab}`) }) : emails.map((email) => /* @__PURE__ */ jsx(EmailCard, { email, onProcess: (action, ticketId, clientId) => handleProcess(email.id, action, ticketId, clientId), processing: processing === email.id, t }, email.id))
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