@consilioweb/payload-support 0.8.2 → 0.9.5

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