@consilioweb/payload-support 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7927,7 +7927,6 @@ function createClientSummariesCollection(slugs) {
7927
7927
  type: "relationship",
7928
7928
  relationTo: slugs.supportClients,
7929
7929
  required: true,
7930
- unique: true,
7931
7930
  index: true,
7932
7931
  label: "Client"
7933
7932
  },
package/dist/index.js CHANGED
@@ -7921,7 +7921,6 @@ function createClientSummariesCollection(slugs) {
7921
7921
  type: "relationship",
7922
7922
  relationTo: slugs.supportClients,
7923
7923
  required: true,
7924
- unique: true,
7925
7924
  index: true,
7926
7925
  label: "Client"
7927
7926
  },
@@ -31,6 +31,9 @@ const CrmClient = () => {
31
31
  const [detailLoading, setDetailLoading] = react.useState(false);
32
32
  const [showMerge, setShowMerge] = react.useState(false);
33
33
  const [mergeSearch, setMergeSearch] = react.useState("");
34
+ const [intelligence, setIntelligence] = react.useState(null);
35
+ const [intelLoading, setIntelLoading] = react.useState(false);
36
+ const [intelRefreshing, setIntelRefreshing] = react.useState(false);
34
37
  const [mergeResults, setMergeResults] = react.useState([]);
35
38
  const [merging, setMerging] = react.useState(false);
36
39
  const [mergeSuccess, setMergeSuccess] = react.useState("");
@@ -77,11 +80,28 @@ const CrmClient = () => {
77
80
  }
78
81
  setDetailLoading(false);
79
82
  }, []);
83
+ const fetchIntelligence = react.useCallback(async (clientId, force = false) => {
84
+ if (force) setIntelRefreshing(true);
85
+ else setIntelLoading(true);
86
+ try {
87
+ const method = force ? "POST" : "GET";
88
+ const url = force ? "/api/support/client-intelligence" : `/api/support/client-intelligence?clientId=${clientId}`;
89
+ const opts = { method, credentials: "include", headers: { "Content-Type": "application/json" } };
90
+ if (force) opts.body = JSON.stringify({ clientId });
91
+ const res = await fetch(url, opts);
92
+ if (res.ok) setIntelligence(await res.json());
93
+ } catch {
94
+ }
95
+ setIntelLoading(false);
96
+ setIntelRefreshing(false);
97
+ }, []);
80
98
  const selectClient = (id) => {
81
99
  setSelectedId(id);
82
100
  fetchDetail(id);
101
+ fetchIntelligence(id);
83
102
  setShowMerge(false);
84
103
  setMergeSuccess("");
104
+ setIntelligence(null);
85
105
  };
86
106
  react.useEffect(() => {
87
107
  if (!mergeSearch || mergeSearch.length < 2) {
@@ -203,6 +223,63 @@ const CrmClient = () => {
203
223
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)", marginBottom: 2 }, children: stat.label }),
204
224
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 18, fontWeight: 700, color: "var(--theme-text)" }, children: stat.value })
205
225
  ] }, stat.label)) }),
226
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 16, background: "linear-gradient(135deg, rgba(37,99,235,0.03) 0%, rgba(139,92,246,0.03) 100%)" }, children: [
227
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
228
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { style: { fontSize: 14, fontWeight: 700, margin: 0, display: "flex", alignItems: "center", gap: 6 }, children: [
229
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 16 }, children: "\u{1F9E0}" }),
230
+ " R\xE9sum\xE9 IA"
231
+ ] }),
232
+ /* @__PURE__ */ jsxRuntime.jsx(
233
+ "button",
234
+ {
235
+ onClick: () => selectedId && fetchIntelligence(selectedId, true),
236
+ disabled: intelRefreshing,
237
+ style: { padding: "4px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", background: "var(--theme-elevation-0)", fontSize: 11, fontWeight: 600, cursor: "pointer", color: "var(--theme-text)" },
238
+ children: intelRefreshing ? "\u23F3 G\xE9n\xE9ration..." : "\u{1F504} Actualiser"
239
+ }
240
+ )
241
+ ] }),
242
+ intelLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 20, textAlign: "center", color: "var(--theme-elevation-400)", fontSize: 13 }, children: "Chargement du r\xE9sum\xE9..." }) : intelligence ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, fontSize: 13, lineHeight: 1.6, color: "var(--theme-text)" }, children: intelligence.summary }),
244
+ intelligence.recurringTopics && intelligence.recurringTopics.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
245
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "var(--theme-elevation-500)", marginBottom: 6, textTransform: "uppercase" }, children: "Sujets r\xE9currents" }),
246
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: intelligence.recurringTopics.map((t2, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { padding: "3px 10px", borderRadius: 12, background: "rgba(37,99,235,0.08)", color: "#2563eb", fontSize: 11, fontWeight: 600 }, children: [
247
+ t2.topic,
248
+ " (",
249
+ t2.count,
250
+ "x)"
251
+ ] }, i)) })
252
+ ] }),
253
+ intelligence.patterns && intelligence.patterns.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
254
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "var(--theme-elevation-500)", marginBottom: 6, textTransform: "uppercase" }, children: "Patterns d\xE9tect\xE9s" }),
255
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { margin: 0, paddingLeft: 18, fontSize: 12, color: "var(--theme-text)", lineHeight: 1.8 }, children: intelligence.patterns.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: p }, i)) })
256
+ ] }),
257
+ intelligence.keyFacts && intelligence.keyFacts.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
258
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "var(--theme-elevation-500)", marginBottom: 6, textTransform: "uppercase" }, children: "Faits cl\xE9s" }),
259
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: intelligence.keyFacts.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { padding: "3px 10px", borderRadius: 12, background: "rgba(22,163,74,0.08)", color: "#16a34a", fontSize: 11, fontWeight: 600 }, children: f }, i)) })
260
+ ] }),
261
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 10, color: "var(--theme-elevation-400)", display: "flex", gap: 12, marginTop: 4 }, children: [
262
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
263
+ intelligence.ticketCount,
264
+ " tickets analys\xE9s"
265
+ ] }),
266
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
267
+ intelligence.messageCount,
268
+ " messages"
269
+ ] }),
270
+ intelligence.averageSatisfaction && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
271
+ "Satisfaction: ",
272
+ intelligence.averageSatisfaction,
273
+ "/5"
274
+ ] }),
275
+ intelligence.fromCache && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Cache" }),
276
+ intelligence.generatedAt && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
277
+ "G\xE9n\xE9r\xE9 ",
278
+ timeAgo(intelligence.generatedAt)
279
+ ] })
280
+ ] })
281
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, textAlign: "center", color: "var(--theme-elevation-400)", fontSize: 13 }, children: 'Cliquez sur "Actualiser" pour g\xE9n\xE9rer le r\xE9sum\xE9 IA de ce client.' })
282
+ ] }),
206
283
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 16 }, children: [
207
284
  /* @__PURE__ */ jsxRuntime.jsxs("h3", { style: { fontSize: 14, fontWeight: 700, margin: "0 0 8px" }, children: [
208
285
  t("crm.sections.tickets"),
@@ -30,6 +30,9 @@ const CrmClient = () => {
30
30
  const [detailLoading, setDetailLoading] = useState(false);
31
31
  const [showMerge, setShowMerge] = useState(false);
32
32
  const [mergeSearch, setMergeSearch] = useState("");
33
+ const [intelligence, setIntelligence] = useState(null);
34
+ const [intelLoading, setIntelLoading] = useState(false);
35
+ const [intelRefreshing, setIntelRefreshing] = useState(false);
33
36
  const [mergeResults, setMergeResults] = useState([]);
34
37
  const [merging, setMerging] = useState(false);
35
38
  const [mergeSuccess, setMergeSuccess] = useState("");
@@ -76,11 +79,28 @@ const CrmClient = () => {
76
79
  }
77
80
  setDetailLoading(false);
78
81
  }, []);
82
+ const fetchIntelligence = useCallback(async (clientId, force = false) => {
83
+ if (force) setIntelRefreshing(true);
84
+ else setIntelLoading(true);
85
+ try {
86
+ const method = force ? "POST" : "GET";
87
+ const url = force ? "/api/support/client-intelligence" : `/api/support/client-intelligence?clientId=${clientId}`;
88
+ const opts = { method, credentials: "include", headers: { "Content-Type": "application/json" } };
89
+ if (force) opts.body = JSON.stringify({ clientId });
90
+ const res = await fetch(url, opts);
91
+ if (res.ok) setIntelligence(await res.json());
92
+ } catch {
93
+ }
94
+ setIntelLoading(false);
95
+ setIntelRefreshing(false);
96
+ }, []);
79
97
  const selectClient = (id) => {
80
98
  setSelectedId(id);
81
99
  fetchDetail(id);
100
+ fetchIntelligence(id);
82
101
  setShowMerge(false);
83
102
  setMergeSuccess("");
103
+ setIntelligence(null);
84
104
  };
85
105
  useEffect(() => {
86
106
  if (!mergeSearch || mergeSearch.length < 2) {
@@ -202,6 +222,63 @@ const CrmClient = () => {
202
222
  /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)", marginBottom: 2 }, children: stat.label }),
203
223
  /* @__PURE__ */ jsx("div", { style: { fontSize: 18, fontWeight: 700, color: "var(--theme-text)" }, children: stat.value })
204
224
  ] }, stat.label)) }),
225
+ /* @__PURE__ */ jsxs("div", { style: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 16, background: "linear-gradient(135deg, rgba(37,99,235,0.03) 0%, rgba(139,92,246,0.03) 100%)" }, children: [
226
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
227
+ /* @__PURE__ */ jsxs("h3", { style: { fontSize: 14, fontWeight: 700, margin: 0, display: "flex", alignItems: "center", gap: 6 }, children: [
228
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 16 }, children: "\u{1F9E0}" }),
229
+ " R\xE9sum\xE9 IA"
230
+ ] }),
231
+ /* @__PURE__ */ jsx(
232
+ "button",
233
+ {
234
+ onClick: () => selectedId && fetchIntelligence(selectedId, true),
235
+ disabled: intelRefreshing,
236
+ style: { padding: "4px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", background: "var(--theme-elevation-0)", fontSize: 11, fontWeight: 600, cursor: "pointer", color: "var(--theme-text)" },
237
+ children: intelRefreshing ? "\u23F3 G\xE9n\xE9ration..." : "\u{1F504} Actualiser"
238
+ }
239
+ )
240
+ ] }),
241
+ intelLoading ? /* @__PURE__ */ jsx("div", { style: { padding: 20, textAlign: "center", color: "var(--theme-elevation-400)", fontSize: 13 }, children: "Chargement du r\xE9sum\xE9..." }) : intelligence ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
242
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 13, lineHeight: 1.6, color: "var(--theme-text)" }, children: intelligence.summary }),
243
+ intelligence.recurringTopics && intelligence.recurringTopics.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
244
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "var(--theme-elevation-500)", marginBottom: 6, textTransform: "uppercase" }, children: "Sujets r\xE9currents" }),
245
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: intelligence.recurringTopics.map((t2, i) => /* @__PURE__ */ jsxs("span", { style: { padding: "3px 10px", borderRadius: 12, background: "rgba(37,99,235,0.08)", color: "#2563eb", fontSize: 11, fontWeight: 600 }, children: [
246
+ t2.topic,
247
+ " (",
248
+ t2.count,
249
+ "x)"
250
+ ] }, i)) })
251
+ ] }),
252
+ intelligence.patterns && intelligence.patterns.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
253
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "var(--theme-elevation-500)", marginBottom: 6, textTransform: "uppercase" }, children: "Patterns d\xE9tect\xE9s" }),
254
+ /* @__PURE__ */ jsx("ul", { style: { margin: 0, paddingLeft: 18, fontSize: 12, color: "var(--theme-text)", lineHeight: 1.8 }, children: intelligence.patterns.map((p, i) => /* @__PURE__ */ jsx("li", { children: p }, i)) })
255
+ ] }),
256
+ intelligence.keyFacts && intelligence.keyFacts.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
257
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "var(--theme-elevation-500)", marginBottom: 6, textTransform: "uppercase" }, children: "Faits cl\xE9s" }),
258
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: intelligence.keyFacts.map((f, i) => /* @__PURE__ */ jsx("span", { style: { padding: "3px 10px", borderRadius: 12, background: "rgba(22,163,74,0.08)", color: "#16a34a", fontSize: 11, fontWeight: 600 }, children: f }, i)) })
259
+ ] }),
260
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 10, color: "var(--theme-elevation-400)", display: "flex", gap: 12, marginTop: 4 }, children: [
261
+ /* @__PURE__ */ jsxs("span", { children: [
262
+ intelligence.ticketCount,
263
+ " tickets analys\xE9s"
264
+ ] }),
265
+ /* @__PURE__ */ jsxs("span", { children: [
266
+ intelligence.messageCount,
267
+ " messages"
268
+ ] }),
269
+ intelligence.averageSatisfaction && /* @__PURE__ */ jsxs("span", { children: [
270
+ "Satisfaction: ",
271
+ intelligence.averageSatisfaction,
272
+ "/5"
273
+ ] }),
274
+ intelligence.fromCache && /* @__PURE__ */ jsx("span", { children: "Cache" }),
275
+ intelligence.generatedAt && /* @__PURE__ */ jsxs("span", { children: [
276
+ "G\xE9n\xE9r\xE9 ",
277
+ timeAgo(intelligence.generatedAt)
278
+ ] })
279
+ ] })
280
+ ] }) : /* @__PURE__ */ jsx("div", { style: { padding: 16, textAlign: "center", color: "var(--theme-elevation-400)", fontSize: 13 }, children: 'Cliquez sur "Actualiser" pour g\xE9n\xE9rer le r\xE9sum\xE9 IA de ce client.' })
281
+ ] }),
205
282
  /* @__PURE__ */ jsxs("div", { style: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 16 }, children: [
206
283
  /* @__PURE__ */ jsxs("h3", { style: { fontSize: 14, fontWeight: 700, margin: "0 0 8px" }, children: [
207
284
  t("crm.sections.tickets"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consilioweb/payload-support",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -22,7 +22,6 @@ export function createClientSummariesCollection(slugs: CollectionSlugs): Collect
22
22
  type: 'relationship',
23
23
  relationTo: slugs.supportClients,
24
24
  required: true,
25
- unique: true,
26
25
  index: true,
27
26
  label: 'Client',
28
27
  },
@@ -18,6 +18,19 @@ const statusColors: Record<string, string> = { open: '#3b82f6', waiting_client:
18
18
  function formatDuration(minutes: number): string { const h = Math.floor(minutes / 60); const m = minutes % 60; if (h === 0) return `${m}min`; if (m === 0) return `${h}h`; return `${h}h${m}m` }
19
19
  function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const days = Math.floor(diff / 86400000); if (days === 0) return "Aujourd'hui"; if (days === 1) return 'Hier'; if (days < 30) return `Il y a ${days}j`; return `Il y a ${Math.floor(days / 30)} mois` }
20
20
 
21
+ interface ClientSummary {
22
+ summary: string
23
+ recurringTopics: { topic: string; count: number; lastSeen: string }[]
24
+ patterns: string[]
25
+ keyFacts: string[]
26
+ ticketCount: number
27
+ messageCount: number
28
+ averageSatisfaction: number | null
29
+ generatedAt: string
30
+ aiModel: string
31
+ fromCache?: boolean
32
+ }
33
+
21
34
  export const CrmClient: React.FC = () => {
22
35
  const { t } = useTranslation()
23
36
  const [clients, setClients] = useState<Client[]>([])
@@ -28,6 +41,10 @@ export const CrmClient: React.FC = () => {
28
41
  const [detailLoading, setDetailLoading] = useState(false)
29
42
  const [showMerge, setShowMerge] = useState(false)
30
43
  const [mergeSearch, setMergeSearch] = useState('')
44
+ // Client Intelligence
45
+ const [intelligence, setIntelligence] = useState<ClientSummary | null>(null)
46
+ const [intelLoading, setIntelLoading] = useState(false)
47
+ const [intelRefreshing, setIntelRefreshing] = useState(false)
31
48
  const [mergeResults, setMergeResults] = useState<Client[]>([])
32
49
  const [merging, setMerging] = useState(false)
33
50
  const [mergeSuccess, setMergeSuccess] = useState('')
@@ -66,7 +83,22 @@ export const CrmClient: React.FC = () => {
66
83
  setDetailLoading(false)
67
84
  }, [])
68
85
 
69
- const selectClient = (id: number) => { setSelectedId(id); fetchDetail(id); setShowMerge(false); setMergeSuccess('') }
86
+ const fetchIntelligence = useCallback(async (clientId: number, force = false) => {
87
+ if (force) setIntelRefreshing(true); else setIntelLoading(true)
88
+ try {
89
+ const method = force ? 'POST' : 'GET'
90
+ const url = force
91
+ ? '/api/support/client-intelligence'
92
+ : `/api/support/client-intelligence?clientId=${clientId}`
93
+ const opts: RequestInit = { method, credentials: 'include', headers: { 'Content-Type': 'application/json' } }
94
+ if (force) opts.body = JSON.stringify({ clientId })
95
+ const res = await fetch(url, opts)
96
+ if (res.ok) setIntelligence(await res.json())
97
+ } catch { /* silent */ }
98
+ setIntelLoading(false); setIntelRefreshing(false)
99
+ }, [])
100
+
101
+ const selectClient = (id: number) => { setSelectedId(id); fetchDetail(id); fetchIntelligence(id); setShowMerge(false); setMergeSuccess(''); setIntelligence(null) }
70
102
 
71
103
  useEffect(() => {
72
104
  if (!mergeSearch || mergeSearch.length < 2) { setMergeResults([]); return }
@@ -178,6 +210,81 @@ export const CrmClient: React.FC = () => {
178
210
  ))}
179
211
  </div>
180
212
 
213
+ {/* Client Intelligence */}
214
+ <div style={{ padding: 16, borderRadius: 10, border: '1px solid var(--theme-elevation-150)', marginBottom: 16, background: 'linear-gradient(135deg, rgba(37,99,235,0.03) 0%, rgba(139,92,246,0.03) 100%)' }}>
215
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
216
+ <h3 style={{ fontSize: 14, fontWeight: 700, margin: 0, display: 'flex', alignItems: 'center', gap: 6 }}>
217
+ <span style={{ fontSize: 16 }}>🧠</span> Résumé IA
218
+ </h3>
219
+ <button
220
+ onClick={() => selectedId && fetchIntelligence(selectedId, true)}
221
+ disabled={intelRefreshing}
222
+ style={{ padding: '4px 10px', borderRadius: 6, border: '1px solid var(--theme-elevation-200)', background: 'var(--theme-elevation-0)', fontSize: 11, fontWeight: 600, cursor: 'pointer', color: 'var(--theme-text)' }}
223
+ >
224
+ {intelRefreshing ? '⏳ Génération...' : '🔄 Actualiser'}
225
+ </button>
226
+ </div>
227
+ {intelLoading ? (
228
+ <div style={{ padding: 20, textAlign: 'center', color: 'var(--theme-elevation-400)', fontSize: 13 }}>Chargement du résumé...</div>
229
+ ) : intelligence ? (
230
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
231
+ {/* Summary */}
232
+ <p style={{ margin: 0, fontSize: 13, lineHeight: 1.6, color: 'var(--theme-text)' }}>{intelligence.summary}</p>
233
+
234
+ {/* Recurring Topics */}
235
+ {intelligence.recurringTopics && intelligence.recurringTopics.length > 0 && (
236
+ <div>
237
+ <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--theme-elevation-500)', marginBottom: 6, textTransform: 'uppercase' }}>Sujets récurrents</div>
238
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
239
+ {intelligence.recurringTopics.map((t, i) => (
240
+ <span key={i} style={{ padding: '3px 10px', borderRadius: 12, background: 'rgba(37,99,235,0.08)', color: '#2563eb', fontSize: 11, fontWeight: 600 }}>
241
+ {t.topic} ({t.count}x)
242
+ </span>
243
+ ))}
244
+ </div>
245
+ </div>
246
+ )}
247
+
248
+ {/* Patterns */}
249
+ {intelligence.patterns && intelligence.patterns.length > 0 && (
250
+ <div>
251
+ <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--theme-elevation-500)', marginBottom: 6, textTransform: 'uppercase' }}>Patterns détectés</div>
252
+ <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: 'var(--theme-text)', lineHeight: 1.8 }}>
253
+ {intelligence.patterns.map((p, i) => <li key={i}>{p}</li>)}
254
+ </ul>
255
+ </div>
256
+ )}
257
+
258
+ {/* Key Facts */}
259
+ {intelligence.keyFacts && intelligence.keyFacts.length > 0 && (
260
+ <div>
261
+ <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--theme-elevation-500)', marginBottom: 6, textTransform: 'uppercase' }}>Faits clés</div>
262
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
263
+ {intelligence.keyFacts.map((f, i) => (
264
+ <span key={i} style={{ padding: '3px 10px', borderRadius: 12, background: 'rgba(22,163,74,0.08)', color: '#16a34a', fontSize: 11, fontWeight: 600 }}>
265
+ {f}
266
+ </span>
267
+ ))}
268
+ </div>
269
+ </div>
270
+ )}
271
+
272
+ {/* Meta */}
273
+ <div style={{ fontSize: 10, color: 'var(--theme-elevation-400)', display: 'flex', gap: 12, marginTop: 4 }}>
274
+ <span>{intelligence.ticketCount} tickets analysés</span>
275
+ <span>{intelligence.messageCount} messages</span>
276
+ {intelligence.averageSatisfaction && <span>Satisfaction: {intelligence.averageSatisfaction}/5</span>}
277
+ {intelligence.fromCache && <span>Cache</span>}
278
+ {intelligence.generatedAt && <span>Généré {timeAgo(intelligence.generatedAt)}</span>}
279
+ </div>
280
+ </div>
281
+ ) : (
282
+ <div style={{ padding: 16, textAlign: 'center', color: 'var(--theme-elevation-400)', fontSize: 13 }}>
283
+ Cliquez sur "Actualiser" pour générer le résumé IA de ce client.
284
+ </div>
285
+ )}
286
+ </div>
287
+
181
288
  {/* Tickets table */}
182
289
  <div style={{ padding: 16, borderRadius: 10, border: '1px solid var(--theme-elevation-150)', marginBottom: 16 }}>
183
290
  <h3 style={{ fontSize: 14, fontWeight: 700, margin: '0 0 8px' }}>{t('crm.sections.tickets')} ({detail.tickets.length})</h3>