@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
@@ -2,6 +2,7 @@
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { useState, useRef, useCallback, useEffect } from 'react';
4
4
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
5
+ import styles from '../../styles/ChatView.module.scss';
5
6
 
6
7
  const ChatViewClient = () => {
7
8
  const { t } = useTranslation();
@@ -15,10 +16,13 @@ const ChatViewClient = () => {
15
16
  const [cannedResponses, setCannedResponses] = useState([]);
16
17
  const messagesEndRef = useRef(null);
17
18
  const lastFetchRef = useRef(null);
19
+ const sessionsPollInterval = useRef(5e3);
20
+ const sessionsPollTimeout = useRef(void 0);
21
+ const messagesPollInterval = useRef(3e3);
22
+ const messagesPollTimeout = useRef(void 0);
18
23
  const [sessionExpired, setSessionExpired] = useState(false);
19
- const sessionsESRef = useRef(null);
20
- const messagesESRef = useRef(null);
21
24
  const fetchSessions = useCallback(async () => {
25
+ let hadChanges = false;
22
26
  try {
23
27
  const res = await fetch("/api/support/admin-chat");
24
28
  if (res.status === 401 || res.status === 403) {
@@ -27,41 +31,36 @@ const ChatViewClient = () => {
27
31
  }
28
32
  if (res.ok) {
29
33
  const data = await res.json();
30
- setSessions({ active: data.active || [], closed: data.closed || [] });
34
+ setSessions((prev) => {
35
+ const newActive = data.active || [];
36
+ const newClosed = data.closed || [];
37
+ if (JSON.stringify(prev.active) !== JSON.stringify(newActive) || JSON.stringify(prev.closed) !== JSON.stringify(newClosed)) {
38
+ hadChanges = true;
39
+ return { active: newActive, closed: newClosed };
40
+ }
41
+ return prev;
42
+ });
31
43
  }
32
44
  } catch {
33
45
  }
34
46
  setLoading(false);
47
+ if (hadChanges) {
48
+ sessionsPollInterval.current = 5e3;
49
+ } else {
50
+ sessionsPollInterval.current = Math.min(sessionsPollInterval.current + 2e3, 15e3);
51
+ }
35
52
  }, []);
36
53
  useEffect(() => {
37
- if (sessionExpired) return;
38
54
  fetchSessions();
39
- if (typeof EventSource !== "undefined") {
40
- const es = new EventSource("/api/support/admin-chat-stream");
41
- sessionsESRef.current = es;
42
- es.onmessage = (event) => {
43
- try {
44
- const parsed = JSON.parse(event.data);
45
- if (parsed.type === "sessions" && parsed.data) {
46
- setSessions({ active: parsed.data.active || [], closed: parsed.data.closed || [] });
47
- setLoading(false);
48
- }
49
- } catch {
50
- }
51
- };
52
- es.onerror = () => {
53
- es.close();
54
- sessionsESRef.current = null;
55
- const iv2 = setInterval(fetchSessions, 5e3);
56
- return () => clearInterval(iv2);
57
- };
58
- return () => {
59
- es.close();
60
- sessionsESRef.current = null;
61
- };
62
- }
63
- const iv = setInterval(fetchSessions, 5e3);
64
- return () => clearInterval(iv);
55
+ if (sessionExpired) return;
56
+ const schedulePoll = () => {
57
+ sessionsPollTimeout.current = setTimeout(async () => {
58
+ await fetchSessions();
59
+ schedulePoll();
60
+ }, sessionsPollInterval.current);
61
+ };
62
+ schedulePoll();
63
+ return () => clearTimeout(sessionsPollTimeout.current);
65
64
  }, [fetchSessions, sessionExpired]);
66
65
  useEffect(() => {
67
66
  fetch("/api/canned-responses?sort=sortOrder&limit=50&depth=0", { credentials: "include" }).then((res) => res.ok ? res.json() : null).then((data) => {
@@ -72,6 +71,7 @@ const ChatViewClient = () => {
72
71
  useEffect(() => {
73
72
  if (!selectedSession) return;
74
73
  const fetchMessages = async () => {
74
+ let hadNewMessages = false;
75
75
  try {
76
76
  const after = lastFetchRef.current || "";
77
77
  const url = `/api/support/admin-chat?session=${selectedSession}${after ? `&after=${after}` : ""}`;
@@ -80,12 +80,14 @@ const ChatViewClient = () => {
80
80
  const data = await res.json();
81
81
  if (!lastFetchRef.current) {
82
82
  setMessages(data.messages || []);
83
+ hadNewMessages = (data.messages?.length || 0) > 0;
83
84
  } else if (data.messages?.length > 0) {
84
85
  setMessages((prev) => {
85
86
  const ids = new Set(prev.map((m) => m.id));
86
87
  const newMsgs = data.messages.filter((m) => !ids.has(m.id));
87
88
  return newMsgs.length > 0 ? [...prev, ...newMsgs] : prev;
88
89
  });
90
+ hadNewMessages = true;
89
91
  }
90
92
  if (data.messages?.length > 0) {
91
93
  lastFetchRef.current = data.messages[data.messages.length - 1].createdAt;
@@ -93,39 +95,23 @@ const ChatViewClient = () => {
93
95
  }
94
96
  } catch {
95
97
  }
98
+ if (hadNewMessages) {
99
+ messagesPollInterval.current = 3e3;
100
+ } else {
101
+ messagesPollInterval.current = Math.min(messagesPollInterval.current + 1e3, 1e4);
102
+ }
96
103
  };
97
104
  lastFetchRef.current = null;
105
+ messagesPollInterval.current = 3e3;
98
106
  fetchMessages();
99
- if (typeof EventSource !== "undefined") {
100
- const es = new EventSource(`/api/support/admin-chat-stream?session=${selectedSession}`);
101
- messagesESRef.current = es;
102
- es.onmessage = (event) => {
103
- try {
104
- const parsed = JSON.parse(event.data);
105
- if (parsed.type === "messages" && parsed.data?.length > 0) {
106
- setMessages((prev) => {
107
- const ids = new Set(prev.map((m) => m.id));
108
- const newMsgs = parsed.data.filter((m) => !ids.has(m.id));
109
- return newMsgs.length > 0 ? [...prev, ...newMsgs] : prev;
110
- });
111
- }
112
- } catch {
113
- }
114
- };
115
- es.onerror = () => {
116
- es.close();
117
- messagesESRef.current = null;
118
- const iv2 = setInterval(fetchMessages, 3e3);
119
- fetchMessages._fallbackIv = iv2;
120
- };
121
- return () => {
122
- es.close();
123
- messagesESRef.current = null;
124
- if (fetchMessages._fallbackIv) clearInterval(fetchMessages._fallbackIv);
125
- };
126
- }
127
- const iv = setInterval(fetchMessages, 3e3);
128
- return () => clearInterval(iv);
107
+ const schedulePoll = () => {
108
+ messagesPollTimeout.current = setTimeout(async () => {
109
+ await fetchMessages();
110
+ schedulePoll();
111
+ }, messagesPollInterval.current);
112
+ };
113
+ schedulePoll();
114
+ return () => clearTimeout(messagesPollTimeout.current);
129
115
  }, [selectedSession]);
130
116
  useEffect(() => {
131
117
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -135,7 +121,11 @@ const ChatViewClient = () => {
135
121
  if (!input.trim() || !selectedSession || sending) return;
136
122
  setSending(true);
137
123
  try {
138
- const res = await fetch("/api/support/admin-chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "send", session: selectedSession, message: input.trim() }) });
124
+ const res = await fetch("/api/support/admin-chat", {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({ action: "send", session: selectedSession, message: input.trim() })
128
+ });
139
129
  if (res.ok) {
140
130
  const data = await res.json();
141
131
  setMessages((prev) => [...prev, data.message]);
@@ -149,7 +139,11 @@ const ChatViewClient = () => {
149
139
  const closeSession = async () => {
150
140
  if (!selectedSession) return;
151
141
  try {
152
- await fetch("/api/support/admin-chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "close", session: selectedSession }) });
142
+ await fetch("/api/support/admin-chat", {
143
+ method: "POST",
144
+ headers: { "Content-Type": "application/json" },
145
+ body: JSON.stringify({ action: "close", session: selectedSession })
146
+ });
153
147
  setSelectedSession(null);
154
148
  fetchSessions();
155
149
  } catch {
@@ -161,87 +155,136 @@ const ChatViewClient = () => {
161
155
  if (parts.length > 0) return parts.join(" ");
162
156
  return client.email || `Client #${client.id}`;
163
157
  };
164
- const displayedSessions = showClosed ? sessions.closed : sessions.active;
165
- const S = {
166
- page: { padding: "20px 30px", maxWidth: 1200, margin: "0 auto" },
167
- container: { display: "grid", gridTemplateColumns: "320px 1fr", gap: 16, minHeight: "calc(100vh - 300px)" },
168
- sidebar: { borderRight: "1px solid var(--theme-elevation-200)" },
169
- sessionItem: { display: "block", width: "100%", padding: "10px 14px", border: "none", background: "none", cursor: "pointer", textAlign: "left", borderBottom: "1px solid var(--theme-elevation-100)", fontSize: 13 },
170
- sessionActive: { background: "var(--theme-elevation-50)" },
171
- chatPanel: { display: "flex", flexDirection: "column" },
172
- messagesArea: { flex: 1, overflowY: "auto", padding: "12px 0" },
173
- bubble: { maxWidth: "70%", padding: "8px 12px", borderRadius: 10, marginBottom: 8, fontSize: 14 },
174
- bubbleAgent: { background: "#dbeafe", color: "#1e3a5f", marginLeft: "auto" },
175
- bubbleClient: { background: "var(--theme-elevation-100)", color: "var(--theme-text)" },
176
- bubbleSystem: { margin: "4px auto", padding: "4px 12px", fontSize: 11, color: "#6b7280", textAlign: "center" },
177
- composer: { borderTop: "1px solid var(--theme-elevation-200)", padding: "8px 0" },
178
- composerInput: { flex: 1, padding: "8px 12px", borderRadius: 8, border: "1px solid var(--theme-elevation-200)", fontSize: 13, background: "var(--theme-elevation-0)", color: "var(--theme-text)" },
179
- sendBtn: { padding: "8px 16px", borderRadius: 8, background: "#2563eb", color: "#fff", border: "none", fontWeight: 600, cursor: "pointer", fontSize: 13 },
180
- tabsRow: { display: "flex", gap: 4, padding: "8px 14px", borderBottom: "1px solid var(--theme-elevation-200)" },
181
- tab: { padding: "4px 10px", borderRadius: 6, border: "none", background: "none", cursor: "pointer", fontSize: 12, color: "var(--theme-elevation-500)" },
182
- tabActive: { background: "var(--theme-elevation-100)", fontWeight: 700, color: "var(--theme-text)" }
158
+ const getClientCompany = (client) => {
159
+ if (typeof client === "number") return "";
160
+ return client.company || "";
183
161
  };
184
- return /* @__PURE__ */ jsxs("div", { style: S.page, children: [
185
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
186
- /* @__PURE__ */ jsx("h1", { style: { fontSize: 22, fontWeight: 700, margin: 0, color: "var(--theme-text)" }, children: t("chat.title") }),
187
- /* @__PURE__ */ jsx("p", { style: { color: "var(--theme-elevation-500)", fontSize: 13, margin: "4px 0 0" }, children: sessions.active.length !== 1 ? t("chat.sessionCountPlural", { count: String(sessions.active.length) }) : t("chat.sessionCount", { count: String(sessions.active.length) }) })
188
- ] }),
189
- /* @__PURE__ */ jsxs("div", { style: S.container, children: [
190
- /* @__PURE__ */ jsxs("div", { style: S.sidebar, children: [
191
- /* @__PURE__ */ jsxs("div", { style: S.tabsRow, children: [
192
- /* @__PURE__ */ jsxs("button", { onClick: () => setShowClosed(false), style: { ...S.tab, ...!showClosed ? S.tabActive : {} }, children: [
193
- t("chat.tabs.active"),
194
- " (",
195
- sessions.active.length,
196
- ")"
197
- ] }),
198
- /* @__PURE__ */ jsxs("button", { onClick: () => setShowClosed(true), style: { ...S.tab, ...showClosed ? S.tabActive : {} }, children: [
199
- t("chat.tabs.closed"),
200
- " (",
201
- sessions.closed.length,
202
- ")"
203
- ] })
162
+ const displayedSessions = showClosed ? sessions.closed : sessions.active;
163
+ return /* @__PURE__ */ jsxs("div", { className: styles.page, children: [
164
+ /* @__PURE__ */ jsx("div", { className: styles.header, children: /* @__PURE__ */ jsxs("div", { children: [
165
+ /* @__PURE__ */ jsx("h1", { className: styles.title, children: t("chat.title") }),
166
+ /* @__PURE__ */ jsx("p", { className: styles.subtitle, children: sessions.active.length !== 1 ? t("chat.sessionCountPlural", { count: String(sessions.active.length) }) : t("chat.sessionCount", { count: String(sessions.active.length) }) })
167
+ ] }) }),
168
+ /* @__PURE__ */ jsxs("div", { className: styles.container, children: [
169
+ /* @__PURE__ */ jsxs("div", { className: styles.sidebar, children: [
170
+ /* @__PURE__ */ jsxs("div", { className: styles.tabs, children: [
171
+ /* @__PURE__ */ jsxs(
172
+ "button",
173
+ {
174
+ onClick: () => setShowClosed(false),
175
+ className: `${styles.tab} ${!showClosed ? styles.tabActive : ""}`,
176
+ children: [
177
+ t("chat.tabs.active"),
178
+ " (",
179
+ sessions.active.length,
180
+ ")"
181
+ ]
182
+ }
183
+ ),
184
+ /* @__PURE__ */ jsxs(
185
+ "button",
186
+ {
187
+ onClick: () => setShowClosed(true),
188
+ className: `${styles.tab} ${showClosed ? styles.tabActive : ""}`,
189
+ children: [
190
+ t("chat.tabs.closed"),
191
+ " (",
192
+ sessions.closed.length,
193
+ ")"
194
+ ]
195
+ }
196
+ )
204
197
  ] }),
205
- /* @__PURE__ */ jsx("div", { children: loading ? /* @__PURE__ */ jsx("div", { style: { padding: 20, textAlign: "center", color: "#94a3b8" }, children: t("common.loading") }) : displayedSessions.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: 20, textAlign: "center", color: "#94a3b8" }, children: showClosed ? t("chat.noSessionClosed") : t("chat.noSessionActive") }) : displayedSessions.map((s2) => /* @__PURE__ */ jsxs("button", { onClick: () => setSelectedSession(s2.session), style: { ...S.sessionItem, ...selectedSession === s2.session ? S.sessionActive : {} }, children: [
206
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
207
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: getClientName(s2.client) }),
208
- s2.unreadCount > 0 && /* @__PURE__ */ jsx("span", { style: { padding: "1px 6px", borderRadius: 10, background: "#dc2626", color: "#fff", fontSize: 10, fontWeight: 700 }, children: s2.unreadCount })
209
- ] }),
210
- /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "var(--theme-elevation-500)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: s2.lastMessage }),
211
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: "var(--theme-elevation-400)", marginTop: 2 }, children: [
212
- new Date(s2.lastMessageAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
213
- " -- ",
214
- s2.messageCount,
215
- " ",
216
- t("chat.msg")
217
- ] })
218
- ] }, s2.session)) })
198
+ /* @__PURE__ */ jsx("div", { className: styles.sessionList, children: loading ? /* @__PURE__ */ jsx("div", { className: styles.loadingState, children: /* @__PURE__ */ jsx("div", { className: styles.emptyState, children: t("common.loading") }) }) : displayedSessions.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.emptyState, children: showClosed ? t("chat.noSessionClosed") : t("chat.noSessionActive") }) : displayedSessions.map((s) => /* @__PURE__ */ jsxs(
199
+ "button",
200
+ {
201
+ onClick: () => setSelectedSession(s.session),
202
+ className: `${styles.sessionItem} ${selectedSession === s.session ? styles.sessionItemActive : ""}`,
203
+ children: [
204
+ /* @__PURE__ */ jsxs("div", { className: styles.sessionHeader, children: [
205
+ /* @__PURE__ */ jsx("span", { className: styles.sessionName, children: getClientName(s.client) }),
206
+ s.unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: styles.unreadBadge, children: s.unreadCount })
207
+ ] }),
208
+ getClientCompany(s.client) && /* @__PURE__ */ jsx("div", { className: styles.sessionCompany, children: getClientCompany(s.client) }),
209
+ /* @__PURE__ */ jsx("div", { className: styles.sessionPreview, children: s.lastMessage.startsWith("Note:") ? /* @__PURE__ */ jsx("span", { className: styles.sessionRating, children: s.lastMessage.match(/[★☆]+/)?.[0] || "\u2B50" }) : s.lastMessage }),
210
+ /* @__PURE__ */ jsxs("div", { className: styles.sessionMeta, children: [
211
+ new Date(s.lastMessageAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
212
+ " \xB7 ",
213
+ s.messageCount,
214
+ " ",
215
+ t("chat.msg")
216
+ ] })
217
+ ]
218
+ },
219
+ s.session
220
+ )) })
219
221
  ] }),
220
- /* @__PURE__ */ jsx("div", { style: S.chatPanel, children: !selectedSession ? /* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "#94a3b8", fontSize: 14 }, children: t("chat.selectSession") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
221
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid var(--theme-elevation-200)" }, children: [
222
- /* @__PURE__ */ jsx("span", { style: { fontFamily: "monospace", fontSize: 12, color: "var(--theme-elevation-500)" }, children: selectedSession }),
223
- /* @__PURE__ */ jsx("button", { onClick: closeSession, style: { padding: "4px 12px", borderRadius: 6, border: "1px solid #dc2626", background: "none", color: "#dc2626", fontSize: 12, cursor: "pointer" }, children: t("chat.closeChat") })
222
+ /* @__PURE__ */ jsx("div", { className: styles.chatPanel, children: !selectedSession ? /* @__PURE__ */ jsx("div", { className: styles.chatEmpty, children: t("chat.selectSession") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
223
+ /* @__PURE__ */ jsxs("div", { className: styles.chatHeader, children: [
224
+ /* @__PURE__ */ jsx("span", { className: styles.chatSessionId, children: selectedSession }),
225
+ /* @__PURE__ */ jsx("button", { onClick: closeSession, className: styles.closeBtn, children: t("chat.closeChat") })
224
226
  ] }),
225
- /* @__PURE__ */ jsxs("div", { style: S.messagesArea, children: [
226
- messages.map((msg) => /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: msg.senderType === "agent" ? "row-reverse" : "row", padding: "2px 14px" }, children: msg.senderType === "system" ? /* @__PURE__ */ jsx("div", { style: S.bubbleSystem, children: msg.message }) : /* @__PURE__ */ jsxs("div", { style: { ...S.bubble, ...msg.senderType === "agent" ? S.bubbleAgent : S.bubbleClient }, children: [
227
- /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 600, marginBottom: 2 }, children: msg.senderType === "agent" ? t("chat.you") : t("chat.clientLabel") }),
228
- /* @__PURE__ */ jsx("div", { children: msg.message }),
229
- /* @__PURE__ */ jsx("div", { style: { fontSize: 10, color: msg.senderType === "agent" ? "#1e40af" : "var(--theme-elevation-400)", marginTop: 2 }, children: new Date(msg.createdAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }) })
230
- ] }) }, msg.id)),
227
+ /* @__PURE__ */ jsxs("div", { className: styles.messagesArea, children: [
228
+ messages.map((msg) => /* @__PURE__ */ jsx(
229
+ "div",
230
+ {
231
+ className: `${styles.messageRow} ${msg.senderType === "agent" ? styles.messageRowAgent : msg.senderType === "system" ? styles.messageRowSystem : styles.messageRowClient}`,
232
+ children: msg.senderType === "system" ? msg.message.startsWith("Note:") || msg.message.startsWith("Commentaire:") ? /* @__PURE__ */ jsxs("div", { className: styles.bubbleRating, children: [
233
+ msg.message.includes("\u2605") && /* @__PURE__ */ jsx("div", { className: styles.ratingStars, children: msg.message.match(/[★☆]+/)?.[0] || "" }),
234
+ /* @__PURE__ */ jsx("div", { className: styles.ratingComment, children: msg.message.includes("\u2014") ? msg.message.split("\u2014").slice(1).join("\u2014").trim() : msg.message.replace(/Note:\s*[★☆]+\s*\(\d\/5\)\s*/, "").replace("Commentaire: ", "") }),
235
+ /* @__PURE__ */ jsxs("div", { className: styles.ratingMeta, children: [
236
+ t("chat.clientReview"),
237
+ " \xB7 ",
238
+ new Date(msg.createdAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })
239
+ ] })
240
+ ] }) : /* @__PURE__ */ jsx("div", { className: styles.bubbleSystem, children: msg.message }) : /* @__PURE__ */ jsxs("div", { className: `${styles.bubble} ${msg.senderType === "agent" ? styles.bubbleAgent : styles.bubbleClient}`, children: [
241
+ /* @__PURE__ */ jsx("div", { className: styles.bubbleSender, children: msg.senderType === "agent" ? msg.agent ? `${msg.agent.firstName || t("chat.agent")}` : t("chat.you") : t("chat.clientLabel") }),
242
+ /* @__PURE__ */ jsx("div", { className: styles.bubbleBody, children: msg.message }),
243
+ /* @__PURE__ */ jsx("div", { className: styles.bubbleTime, children: new Date(msg.createdAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }) })
244
+ ] })
245
+ },
246
+ msg.id
247
+ )),
231
248
  /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
232
249
  ] }),
233
- /* @__PURE__ */ jsxs("form", { onSubmit: sendMessage, style: S.composer, children: [
234
- cannedResponses.length > 0 && /* @__PURE__ */ jsxs("select", { onChange: (e) => {
235
- const cr = cannedResponses.find((c) => String(c.id) === e.target.value);
236
- if (cr) setInput(cr.body);
237
- e.target.value = "";
238
- }, style: { padding: "4px 8px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 11, marginBottom: 6, color: "var(--theme-text)", background: "var(--theme-elevation-0)" }, children: [
239
- /* @__PURE__ */ jsx("option", { value: "", children: t("chat.quickReply") }),
240
- cannedResponses.map((cr) => /* @__PURE__ */ jsx("option", { value: String(cr.id), children: cr.title }, cr.id))
241
- ] }),
242
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
243
- /* @__PURE__ */ jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: t("chat.inputPlaceholder"), maxLength: 2e3, style: S.composerInput, autoFocus: true }),
244
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: !input.trim() || sending, style: S.sendBtn, children: t("chat.sendButton") })
250
+ /* @__PURE__ */ jsxs("form", { onSubmit: sendMessage, className: styles.composer, children: [
251
+ cannedResponses.length > 0 && /* @__PURE__ */ jsxs(
252
+ "select",
253
+ {
254
+ onChange: (e) => {
255
+ const cr = cannedResponses.find((c) => String(c.id) === e.target.value);
256
+ if (cr) setInput(cr.body);
257
+ e.target.value = "";
258
+ },
259
+ className: styles.cannedSelect,
260
+ children: [
261
+ /* @__PURE__ */ jsx("option", { value: "", children: t("chat.quickReply") }),
262
+ cannedResponses.map((cr) => /* @__PURE__ */ jsx("option", { value: String(cr.id), children: cr.title }, cr.id))
263
+ ]
264
+ }
265
+ ),
266
+ /* @__PURE__ */ jsxs("div", { className: styles.composerRow, children: [
267
+ /* @__PURE__ */ jsx(
268
+ "input",
269
+ {
270
+ type: "text",
271
+ value: input,
272
+ onChange: (e) => setInput(e.target.value),
273
+ placeholder: t("chat.inputPlaceholder"),
274
+ maxLength: 2e3,
275
+ className: styles.composerInput,
276
+ autoFocus: true
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ type: "submit",
283
+ disabled: !input.trim() || sending,
284
+ className: styles.sendBtn,
285
+ children: t("chat.sendButton")
286
+ }
287
+ )
245
288
  ] })
246
289
  ] })
247
290
  ] }) })