@consilioweb/spellcheck 0.10.1

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/client.js ADDED
@@ -0,0 +1,1702 @@
1
+ "use client";
2
+ import React3, { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { useDocumentInfo } from '@payloadcms/ui';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/components/SpellCheckField.tsx
7
+ var styles = {
8
+ card: {
9
+ border: "1px solid var(--theme-elevation-150)",
10
+ borderRadius: "4px",
11
+ padding: "12px",
12
+ marginBottom: "8px",
13
+ backgroundColor: "var(--theme-elevation-50)"
14
+ },
15
+ header: {
16
+ display: "flex",
17
+ justifyContent: "space-between",
18
+ alignItems: "flex-start",
19
+ gap: "8px",
20
+ marginBottom: "8px"
21
+ },
22
+ message: {
23
+ fontSize: "13px",
24
+ lineHeight: "1.4",
25
+ color: "var(--theme-text)",
26
+ flex: 1
27
+ },
28
+ badge: {
29
+ fontSize: "10px",
30
+ padding: "2px 6px",
31
+ borderRadius: "3px",
32
+ backgroundColor: "var(--theme-elevation-200)",
33
+ color: "var(--theme-elevation-800)",
34
+ whiteSpace: "nowrap",
35
+ flexShrink: 0
36
+ },
37
+ context: {
38
+ fontSize: "12px",
39
+ color: "var(--theme-elevation-500)",
40
+ backgroundColor: "var(--theme-elevation-100)",
41
+ padding: "6px 8px",
42
+ borderRadius: "3px",
43
+ fontFamily: "monospace",
44
+ marginBottom: "8px",
45
+ wordBreak: "break-word"
46
+ },
47
+ original: {
48
+ textDecoration: "line-through",
49
+ color: "var(--theme-error-500)",
50
+ fontWeight: 600
51
+ },
52
+ suggestion: {
53
+ display: "flex",
54
+ alignItems: "center",
55
+ gap: "6px",
56
+ marginBottom: "8px"
57
+ },
58
+ suggestionLabel: {
59
+ fontSize: "11px",
60
+ color: "var(--theme-elevation-500)"
61
+ },
62
+ suggestionText: {
63
+ fontSize: "13px",
64
+ fontWeight: 600,
65
+ color: "var(--theme-success-500)"
66
+ },
67
+ actions: {
68
+ display: "flex",
69
+ gap: "6px"
70
+ },
71
+ btn: {
72
+ fontSize: "11px",
73
+ padding: "4px 10px",
74
+ borderRadius: "3px",
75
+ border: "1px solid var(--theme-elevation-200)",
76
+ backgroundColor: "var(--theme-elevation-0)",
77
+ color: "var(--theme-text)",
78
+ cursor: "pointer"
79
+ },
80
+ btnFix: {
81
+ fontSize: "11px",
82
+ padding: "4px 10px",
83
+ borderRadius: "3px",
84
+ border: "none",
85
+ backgroundColor: "var(--theme-success-500)",
86
+ color: "#fff",
87
+ cursor: "pointer",
88
+ fontWeight: 600
89
+ },
90
+ fixed: {
91
+ opacity: 0.5,
92
+ pointerEvents: "none"
93
+ },
94
+ diff: {
95
+ padding: "8px 10px",
96
+ borderRadius: "3px",
97
+ backgroundColor: "var(--theme-elevation-100)",
98
+ marginBottom: "8px",
99
+ fontSize: "13px",
100
+ lineHeight: "1.6",
101
+ fontFamily: "monospace",
102
+ wordBreak: "break-word"
103
+ },
104
+ diffBefore: {
105
+ textDecoration: "line-through",
106
+ backgroundColor: "rgba(239, 68, 68, 0.15)",
107
+ color: "var(--theme-error-500)",
108
+ padding: "1px 3px",
109
+ borderRadius: "2px"
110
+ },
111
+ diffAfter: {
112
+ backgroundColor: "rgba(34, 197, 94, 0.15)",
113
+ color: "var(--theme-success-500)",
114
+ fontWeight: 600,
115
+ padding: "1px 3px",
116
+ borderRadius: "2px"
117
+ },
118
+ diffArrow: {
119
+ display: "inline-block",
120
+ margin: "0 6px",
121
+ color: "var(--theme-elevation-400)",
122
+ fontSize: "12px"
123
+ }
124
+ };
125
+ var IssueCard = ({
126
+ issue,
127
+ onFix,
128
+ onIgnore,
129
+ onAddToDict,
130
+ isFixed = false
131
+ }) => {
132
+ const [showDiff, setShowDiff] = useState(false);
133
+ const [selectedReplacement, setSelectedReplacement] = useState(0);
134
+ const [manualEdit, setManualEdit] = useState(false);
135
+ const [manualValue, setManualValue] = useState("");
136
+ const [addedToDict, setAddedToDict] = useState(false);
137
+ const getContextIdx = (ctx, original) => {
138
+ if (typeof issue.contextOffset === "number" && issue.contextOffset >= 0) {
139
+ return issue.contextOffset;
140
+ }
141
+ return ctx.indexOf(original);
142
+ };
143
+ const highlightContext = (ctx, original) => {
144
+ const idx = getContextIdx(ctx, original);
145
+ if (!original || idx < 0) return ctx;
146
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
147
+ ctx.slice(0, idx),
148
+ /* @__PURE__ */ jsx("span", { style: styles.original, children: original }),
149
+ ctx.slice(idx + original.length)
150
+ ] });
151
+ };
152
+ const renderDiff = (ctx, original, replacement) => {
153
+ const idx = getContextIdx(ctx, original);
154
+ if (!original || idx < 0) {
155
+ return /* @__PURE__ */ jsxs("div", { style: styles.diff, children: [
156
+ /* @__PURE__ */ jsx("span", { style: styles.diffBefore, children: original }),
157
+ /* @__PURE__ */ jsx("span", { style: styles.diffArrow, children: "\u2192" }),
158
+ /* @__PURE__ */ jsx("span", { style: styles.diffAfter, children: replacement })
159
+ ] });
160
+ }
161
+ return /* @__PURE__ */ jsxs("div", { style: styles.diff, children: [
162
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: "4px", color: "var(--theme-elevation-400)", fontSize: "10px", textTransform: "uppercase", letterSpacing: "0.5px" }, children: "Avant / Apr\xE8s" }),
163
+ /* @__PURE__ */ jsxs("div", { children: [
164
+ ctx.slice(0, idx),
165
+ /* @__PURE__ */ jsx("span", { style: styles.diffBefore, children: original }),
166
+ ctx.slice(idx + original.length)
167
+ ] }),
168
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "4px" }, children: [
169
+ ctx.slice(0, idx),
170
+ /* @__PURE__ */ jsx("span", { style: styles.diffAfter, children: replacement }),
171
+ ctx.slice(idx + original.length)
172
+ ] })
173
+ ] });
174
+ };
175
+ const currentReplacement = manualEdit && manualValue ? manualValue : issue.replacements[selectedReplacement] || "";
176
+ return /* @__PURE__ */ jsxs("div", { style: { ...styles.card, ...isFixed ? styles.fixed : {} }, children: [
177
+ /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
178
+ /* @__PURE__ */ jsx("div", { style: styles.message, children: issue.message }),
179
+ /* @__PURE__ */ jsx("span", { style: styles.badge, children: issue.source === "claude" ? "IA" : issue.category })
180
+ ] }),
181
+ showDiff && issue.context && currentReplacement ? renderDiff(issue.context, issue.original, currentReplacement) : issue.context ? /* @__PURE__ */ jsx("div", { style: styles.context, children: highlightContext(issue.context, issue.original) }) : null,
182
+ !manualEdit && issue.replacements.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.suggestion, children: [
183
+ /* @__PURE__ */ jsx("span", { style: styles.suggestionLabel, children: "Suggestion :" }),
184
+ issue.replacements.length > 1 ? /* @__PURE__ */ jsx(
185
+ "select",
186
+ {
187
+ value: selectedReplacement,
188
+ onChange: (e) => setSelectedReplacement(Number(e.target.value)),
189
+ style: {
190
+ fontSize: "13px",
191
+ fontWeight: 600,
192
+ color: "var(--theme-success-500)",
193
+ border: "1px solid var(--theme-elevation-200)",
194
+ borderRadius: "3px",
195
+ padding: "2px 4px",
196
+ backgroundColor: "var(--theme-elevation-0)"
197
+ },
198
+ children: issue.replacements.map((r, i) => /* @__PURE__ */ jsx("option", { value: i, children: r }, i))
199
+ }
200
+ ) : /* @__PURE__ */ jsx("span", { style: styles.suggestionText, children: currentReplacement })
201
+ ] }),
202
+ manualEdit && !isFixed && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "8px" }, children: [
203
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "11px", color: "var(--theme-elevation-500)", marginBottom: "4px" }, children: "Correction manuelle :" }),
204
+ /* @__PURE__ */ jsx(
205
+ "input",
206
+ {
207
+ type: "text",
208
+ value: manualValue,
209
+ onChange: (e) => setManualValue(e.target.value),
210
+ placeholder: issue.original,
211
+ style: {
212
+ width: "100%",
213
+ padding: "6px 8px",
214
+ fontSize: "13px",
215
+ border: "1px solid var(--theme-elevation-200)",
216
+ borderRadius: "3px",
217
+ backgroundColor: "var(--theme-elevation-0)",
218
+ color: "var(--theme-text)",
219
+ boxSizing: "border-box"
220
+ }
221
+ }
222
+ )
223
+ ] }),
224
+ /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
225
+ issue.context && currentReplacement && !isFixed && /* @__PURE__ */ jsx(
226
+ "button",
227
+ {
228
+ type: "button",
229
+ style: { ...styles.btn, fontSize: "10px" },
230
+ onClick: () => setShowDiff(!showDiff),
231
+ children: showDiff ? "Masquer" : "Avant/Apr\xE8s"
232
+ }
233
+ ),
234
+ !isFixed && /* @__PURE__ */ jsx(
235
+ "button",
236
+ {
237
+ type: "button",
238
+ style: { ...styles.btn, fontSize: "10px" },
239
+ onClick: () => {
240
+ setManualEdit(!manualEdit);
241
+ if (!manualEdit) setManualValue(issue.original);
242
+ },
243
+ children: manualEdit ? "Suggestion" : "Manuel"
244
+ }
245
+ ),
246
+ currentReplacement && onFix && !isFixed && /* @__PURE__ */ jsx(
247
+ "button",
248
+ {
249
+ type: "button",
250
+ style: styles.btnFix,
251
+ onClick: () => onFix(issue.original, currentReplacement, issue.offset, issue.length),
252
+ children: "Corriger"
253
+ }
254
+ ),
255
+ onAddToDict && !isFixed && !addedToDict && issue.original && /* @__PURE__ */ jsx(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ style: { ...styles.btn, fontSize: "10px" },
260
+ onClick: () => {
261
+ onAddToDict(issue.original);
262
+ setAddedToDict(true);
263
+ },
264
+ title: "Ajouter au dictionnaire",
265
+ children: "+ Dico"
266
+ }
267
+ ),
268
+ addedToDict && /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: "var(--theme-success-500)" }, children: "Ajout\xE9 au dico" }),
269
+ onIgnore && !isFixed && /* @__PURE__ */ jsx(
270
+ "button",
271
+ {
272
+ type: "button",
273
+ style: styles.btn,
274
+ onClick: () => onIgnore(issue.ruleId),
275
+ children: "Ignorer"
276
+ }
277
+ ),
278
+ isFixed && /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: "var(--theme-success-500)" }, children: "Corrige" })
279
+ ] })
280
+ ] });
281
+ };
282
+ var styles2 = {
283
+ container: {
284
+ padding: "12px 0"
285
+ },
286
+ header: {
287
+ display: "flex",
288
+ justifyContent: "space-between",
289
+ alignItems: "center",
290
+ marginBottom: "12px"
291
+ },
292
+ title: {
293
+ fontSize: "13px",
294
+ fontWeight: 600,
295
+ color: "var(--theme-text)",
296
+ textTransform: "uppercase",
297
+ letterSpacing: "0.5px"
298
+ },
299
+ scoreBadge: (score) => ({
300
+ fontSize: "14px",
301
+ fontWeight: 700,
302
+ padding: "3px 10px",
303
+ borderRadius: "12px",
304
+ color: "#fff",
305
+ backgroundColor: score >= 95 ? "var(--theme-success-500)" : score >= 80 ? "#f59e0b" : "var(--theme-error-500)"
306
+ }),
307
+ checkBtn: {
308
+ display: "block",
309
+ width: "100%",
310
+ padding: "8px 12px",
311
+ fontSize: "13px",
312
+ fontWeight: 600,
313
+ border: "1px solid var(--theme-elevation-200)",
314
+ borderRadius: "4px",
315
+ backgroundColor: "var(--theme-elevation-0)",
316
+ color: "var(--theme-text)",
317
+ cursor: "pointer",
318
+ textAlign: "center",
319
+ marginBottom: "12px"
320
+ },
321
+ checkBtnDisabled: {
322
+ opacity: 0.6,
323
+ cursor: "not-allowed"
324
+ },
325
+ stats: {
326
+ display: "flex",
327
+ justifyContent: "space-between",
328
+ fontSize: "12px",
329
+ color: "var(--theme-elevation-500)",
330
+ marginBottom: "12px",
331
+ padding: "6px 8px",
332
+ backgroundColor: "var(--theme-elevation-50)",
333
+ borderRadius: "4px"
334
+ },
335
+ issueList: {
336
+ maxHeight: "400px",
337
+ overflowY: "auto"
338
+ },
339
+ empty: {
340
+ textAlign: "center",
341
+ padding: "20px 0",
342
+ fontSize: "13px",
343
+ color: "var(--theme-elevation-500)"
344
+ },
345
+ error: {
346
+ padding: "8px 12px",
347
+ fontSize: "12px",
348
+ color: "var(--theme-error-500)",
349
+ backgroundColor: "var(--theme-error-100)",
350
+ borderRadius: "4px",
351
+ marginBottom: "12px"
352
+ }
353
+ };
354
+ var SpellCheckField = () => {
355
+ const { id, collectionSlug } = useDocumentInfo();
356
+ const [result, setResult] = useState(null);
357
+ const [resultDbId, setResultDbId] = useState(null);
358
+ const [loading, setLoading] = useState(false);
359
+ const [error, setError] = useState(null);
360
+ const resultRef = useRef(result);
361
+ resultRef.current = result;
362
+ const resultDbIdRef = useRef(resultDbId);
363
+ resultDbIdRef.current = resultDbId;
364
+ useEffect(() => {
365
+ if (!id || !collectionSlug) return;
366
+ fetch(`/api/spellcheck-results?where[docId][equals]=${id}&where[collection][equals]=${collectionSlug}&limit=1`).then((res) => res.json()).then((data) => {
367
+ if (data.docs?.[0]) {
368
+ setResultDbId(data.docs[0].id);
369
+ setResult({
370
+ docId: String(id),
371
+ collection: collectionSlug,
372
+ score: data.docs[0].score,
373
+ issueCount: data.docs[0].issueCount,
374
+ wordCount: data.docs[0].wordCount || 0,
375
+ issues: data.docs[0].issues || [],
376
+ lastChecked: data.docs[0].lastChecked
377
+ });
378
+ }
379
+ }).catch(() => {
380
+ });
381
+ }, [id, collectionSlug]);
382
+ const handleCheck = useCallback(async () => {
383
+ if (!id || !collectionSlug || loading) return;
384
+ setLoading(true);
385
+ setError(null);
386
+ try {
387
+ const res = await fetch("/api/spellcheck/validate", {
388
+ method: "POST",
389
+ headers: { "Content-Type": "application/json" },
390
+ body: JSON.stringify({ id, collection: collectionSlug })
391
+ });
392
+ if (!res.ok) {
393
+ const err = await res.json();
394
+ throw new Error(err.error || `HTTP ${res.status}`);
395
+ }
396
+ const data = await res.json();
397
+ setResult(data);
398
+ } catch (err) {
399
+ setError(err.message);
400
+ } finally {
401
+ setLoading(false);
402
+ }
403
+ }, [id, collectionSlug, loading]);
404
+ const removeIssue = useCallback((matcher) => {
405
+ const prev = resultRef.current;
406
+ if (!prev) return;
407
+ const updatedIssues = prev.issues.filter((issue) => {
408
+ if (typeof matcher.offset === "number" && issue.offset === matcher.offset) {
409
+ if (matcher.original && issue.original !== matcher.original) return true;
410
+ if (matcher.ruleId && issue.ruleId !== matcher.ruleId) return true;
411
+ return false;
412
+ }
413
+ return true;
414
+ });
415
+ const updatedCount = updatedIssues.length;
416
+ const updatedScore = prev.wordCount > 0 ? Math.min(100, Math.max(0, Math.round(100 - updatedCount / prev.wordCount * 1e3))) : prev.score;
417
+ setResult({ ...prev, issues: updatedIssues, issueCount: updatedCount, score: updatedScore });
418
+ if (resultDbIdRef.current) {
419
+ fetch(`/api/spellcheck-results/${resultDbIdRef.current}`, {
420
+ method: "PATCH",
421
+ headers: { "Content-Type": "application/json" },
422
+ body: JSON.stringify({ issues: updatedIssues, issueCount: updatedCount, score: updatedScore })
423
+ }).catch(() => {
424
+ });
425
+ }
426
+ }, []);
427
+ const handleFix = useCallback(async (original, replacement, offset, length) => {
428
+ if (!id || !collectionSlug) return;
429
+ try {
430
+ const res = await fetch("/api/spellcheck/fix", {
431
+ method: "POST",
432
+ headers: { "Content-Type": "application/json" },
433
+ body: JSON.stringify({ id, collection: collectionSlug, original, replacement, offset, length })
434
+ });
435
+ if (res.ok) {
436
+ removeIssue({ offset, original });
437
+ }
438
+ } catch {
439
+ }
440
+ }, [id, collectionSlug, removeIssue]);
441
+ return /* @__PURE__ */ jsxs("div", { style: styles2.container, children: [
442
+ /* @__PURE__ */ jsxs("div", { style: styles2.header, children: [
443
+ /* @__PURE__ */ jsx("span", { style: styles2.title, children: "Orthographe" }),
444
+ result && /* @__PURE__ */ jsx("span", { style: styles2.scoreBadge(result.score), children: result.score })
445
+ ] }),
446
+ /* @__PURE__ */ jsx(
447
+ "button",
448
+ {
449
+ type: "button",
450
+ style: {
451
+ ...styles2.checkBtn,
452
+ ...loading ? styles2.checkBtnDisabled : {}
453
+ },
454
+ onClick: handleCheck,
455
+ disabled: loading,
456
+ children: loading ? "V\xE9rification..." : "V\xE9rifier"
457
+ }
458
+ ),
459
+ error && /* @__PURE__ */ jsx("div", { style: styles2.error, children: error }),
460
+ result && /* @__PURE__ */ jsxs(Fragment, { children: [
461
+ /* @__PURE__ */ jsxs("div", { style: styles2.stats, children: [
462
+ /* @__PURE__ */ jsxs("span", { children: [
463
+ result.wordCount,
464
+ " mots"
465
+ ] }),
466
+ /* @__PURE__ */ jsxs("span", { children: [
467
+ result.issueCount,
468
+ " probl\xE8me",
469
+ result.issueCount !== 1 ? "s" : ""
470
+ ] }),
471
+ result.lastChecked && /* @__PURE__ */ jsx("span", { children: new Date(result.lastChecked).toLocaleTimeString("fr-FR", {
472
+ hour: "2-digit",
473
+ minute: "2-digit"
474
+ }) })
475
+ ] }),
476
+ result.issues.length > 0 ? /* @__PURE__ */ jsx("div", { style: styles2.issueList, children: result.issues.map((issue, i) => /* @__PURE__ */ jsx(
477
+ IssueCard,
478
+ {
479
+ issue,
480
+ onFix: handleFix,
481
+ onIgnore: () => {
482
+ removeIssue({ offset: issue.offset, ruleId: issue.ruleId });
483
+ if (resultDbIdRef.current) {
484
+ fetch(`/api/spellcheck-results/${resultDbIdRef.current}?depth=0`).then((res) => res.json()).then((data) => {
485
+ const existing = Array.isArray(data.ignoredIssues) ? data.ignoredIssues : [];
486
+ const alreadyIgnored = existing.some((e) => e.ruleId === issue.ruleId && e.original === issue.original);
487
+ if (!alreadyIgnored) {
488
+ fetch(`/api/spellcheck-results/${resultDbIdRef.current}`, {
489
+ method: "PATCH",
490
+ headers: { "Content-Type": "application/json" },
491
+ body: JSON.stringify({ ignoredIssues: [...existing, { ruleId: issue.ruleId, original: issue.original }] })
492
+ }).catch(() => {
493
+ });
494
+ }
495
+ }).catch(() => {
496
+ });
497
+ }
498
+ },
499
+ onAddToDict: (word) => {
500
+ fetch("/api/spellcheck/dictionary", {
501
+ method: "POST",
502
+ headers: { "Content-Type": "application/json" },
503
+ body: JSON.stringify({ word })
504
+ }).catch(() => {
505
+ });
506
+ removeIssue({ offset: issue.offset, ruleId: issue.ruleId });
507
+ }
508
+ },
509
+ `${issue.ruleId}-${issue.offset}-${i}`
510
+ )) }) : /* @__PURE__ */ jsx("div", { style: styles2.empty, children: "Aucun probl\xE8me d\xE9tect\xE9 \u2713" })
511
+ ] }),
512
+ !result && !loading && /* @__PURE__ */ jsx("div", { style: styles2.empty, children: 'Cliquez sur "V\xE9rifier" pour analyser le contenu' })
513
+ ] });
514
+ };
515
+ var styles3 = {
516
+ container: {
517
+ padding: "24px",
518
+ maxWidth: "1200px"
519
+ },
520
+ header: {
521
+ display: "flex",
522
+ justifyContent: "space-between",
523
+ alignItems: "center",
524
+ marginBottom: "24px"
525
+ },
526
+ title: {
527
+ fontSize: "24px",
528
+ fontWeight: 700,
529
+ color: "var(--theme-text)",
530
+ margin: 0
531
+ },
532
+ description: {
533
+ fontSize: "14px",
534
+ color: "var(--theme-elevation-500)",
535
+ marginTop: "4px"
536
+ },
537
+ scanBtn: {
538
+ padding: "10px 20px",
539
+ fontSize: "14px",
540
+ fontWeight: 600,
541
+ border: "none",
542
+ borderRadius: "4px",
543
+ backgroundColor: "var(--theme-elevation-900)",
544
+ color: "var(--theme-elevation-0)",
545
+ cursor: "pointer"
546
+ },
547
+ scanBtnDisabled: {
548
+ opacity: 0.6,
549
+ cursor: "not-allowed"
550
+ },
551
+ summary: {
552
+ display: "grid",
553
+ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
554
+ gap: "16px",
555
+ marginBottom: "24px"
556
+ },
557
+ summaryCard: {
558
+ padding: "16px",
559
+ borderRadius: "6px",
560
+ backgroundColor: "var(--theme-elevation-50)",
561
+ border: "1px solid var(--theme-elevation-150)"
562
+ },
563
+ summaryLabel: {
564
+ fontSize: "12px",
565
+ color: "var(--theme-elevation-500)",
566
+ textTransform: "uppercase",
567
+ letterSpacing: "0.5px",
568
+ marginBottom: "4px"
569
+ },
570
+ summaryValue: {
571
+ fontSize: "24px",
572
+ fontWeight: 700,
573
+ color: "var(--theme-text)"
574
+ },
575
+ progress: {
576
+ padding: "12px 16px",
577
+ marginBottom: "16px",
578
+ borderRadius: "4px",
579
+ backgroundColor: "var(--theme-elevation-50)",
580
+ border: "1px solid var(--theme-elevation-150)",
581
+ fontSize: "13px",
582
+ color: "var(--theme-text)"
583
+ },
584
+ progressBar: {
585
+ height: "4px",
586
+ backgroundColor: "var(--theme-elevation-150)",
587
+ borderRadius: "2px",
588
+ marginTop: "8px",
589
+ overflow: "hidden"
590
+ },
591
+ progressFill: (pct) => ({
592
+ height: "100%",
593
+ width: `${pct}%`,
594
+ backgroundColor: "var(--theme-success-500)",
595
+ borderRadius: "2px",
596
+ transition: "width 0.3s"
597
+ }),
598
+ table: {
599
+ width: "100%",
600
+ borderCollapse: "collapse",
601
+ fontSize: "13px"
602
+ },
603
+ th: {
604
+ textAlign: "left",
605
+ padding: "10px 12px",
606
+ borderBottom: "2px solid var(--theme-elevation-200)",
607
+ fontSize: "11px",
608
+ fontWeight: 600,
609
+ textTransform: "uppercase",
610
+ letterSpacing: "0.5px",
611
+ color: "var(--theme-elevation-500)",
612
+ cursor: "pointer"
613
+ },
614
+ td: {
615
+ padding: "10px 12px",
616
+ borderBottom: "1px solid var(--theme-elevation-100)",
617
+ verticalAlign: "middle"
618
+ },
619
+ tr: {
620
+ cursor: "pointer"
621
+ },
622
+ trHover: {
623
+ backgroundColor: "var(--theme-elevation-50)"
624
+ },
625
+ scoreBadge: (score) => ({
626
+ display: "inline-block",
627
+ fontSize: "12px",
628
+ fontWeight: 700,
629
+ padding: "2px 8px",
630
+ borderRadius: "10px",
631
+ color: "#fff",
632
+ backgroundColor: score >= 95 ? "var(--theme-success-500)" : score >= 80 ? "#f59e0b" : "var(--theme-error-500)"
633
+ }),
634
+ expandedRow: {
635
+ padding: "16px",
636
+ backgroundColor: "var(--theme-elevation-50)",
637
+ borderBottom: "1px solid var(--theme-elevation-150)"
638
+ },
639
+ empty: {
640
+ textAlign: "center",
641
+ padding: "40px 0",
642
+ color: "var(--theme-elevation-500)",
643
+ fontSize: "14px"
644
+ },
645
+ link: {
646
+ color: "var(--theme-text)",
647
+ textDecoration: "none",
648
+ fontWeight: 500
649
+ },
650
+ // Tab styles
651
+ tabs: {
652
+ display: "flex",
653
+ gap: "0",
654
+ marginBottom: "24px",
655
+ borderBottom: "2px solid var(--theme-elevation-150)"
656
+ },
657
+ tab: (active) => ({
658
+ padding: "10px 20px",
659
+ fontSize: "14px",
660
+ fontWeight: active ? 600 : 400,
661
+ color: active ? "var(--theme-text)" : "var(--theme-elevation-500)",
662
+ backgroundColor: "transparent",
663
+ border: "none",
664
+ borderBottom: active ? "2px solid var(--theme-text)" : "2px solid transparent",
665
+ marginBottom: "-2px",
666
+ cursor: "pointer"
667
+ })
668
+ };
669
+ var SpellCheckDashboard = () => {
670
+ const [activeTab, setActiveTab] = useState("results");
671
+ const [results, setResults] = useState([]);
672
+ const [loading, setLoading] = useState(true);
673
+ const [scanning, setScanning] = useState(false);
674
+ const [scanProgress, setScanProgress] = useState("");
675
+ const [expandedId, setExpandedId] = useState(null);
676
+ const [sortKey, setSortKey] = useState("score");
677
+ const [sortDir, setSortDir] = useState("asc");
678
+ const [hoveredRow, setHoveredRow] = useState(null);
679
+ const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
680
+ const [filterCollection, setFilterCollection] = useState("");
681
+ const [allDocs, setAllDocs] = useState([]);
682
+ const [loadingDocs, setLoadingDocs] = useState(true);
683
+ const [dictWords, setDictWords] = useState([]);
684
+ const [dictLoading, setDictLoading] = useState(false);
685
+ const [dictSearch, setDictSearch] = useState("");
686
+ const [dictInput, setDictInput] = useState("");
687
+ const [dictSelectedIds, setDictSelectedIds] = useState(/* @__PURE__ */ new Set());
688
+ const [dictToast, setDictToast] = useState("");
689
+ const [showImport, setShowImport] = useState(false);
690
+ const [importText, setImportText] = useState("");
691
+ const resultsRef = useRef(results);
692
+ resultsRef.current = results;
693
+ const loadResults = useCallback(async () => {
694
+ setLoading(true);
695
+ try {
696
+ const res = await fetch("/api/spellcheck-results?limit=0&sort=-lastChecked");
697
+ const data = await res.json();
698
+ setResults(data.docs || []);
699
+ } catch {
700
+ } finally {
701
+ setLoading(false);
702
+ }
703
+ }, []);
704
+ const loadAllDocs = useCallback(async () => {
705
+ setLoadingDocs(true);
706
+ try {
707
+ let collections2 = ["pages", "posts"];
708
+ try {
709
+ const configRes = await fetch("/api/spellcheck/collections");
710
+ if (configRes.ok) {
711
+ const configData = await configRes.json();
712
+ if (Array.isArray(configData.collections)) {
713
+ collections2 = configData.collections;
714
+ }
715
+ }
716
+ } catch {
717
+ }
718
+ const docs = [];
719
+ for (const col of collections2) {
720
+ try {
721
+ const res = await fetch(`/api/${col}?limit=0&depth=0&where[_status][equals]=published`);
722
+ const data = await res.json();
723
+ if (data.docs) {
724
+ for (const doc of data.docs) {
725
+ docs.push({
726
+ id: String(doc.id),
727
+ collection: col,
728
+ title: doc.title || doc.slug || String(doc.id),
729
+ slug: doc.slug || ""
730
+ });
731
+ }
732
+ }
733
+ } catch {
734
+ }
735
+ }
736
+ setAllDocs(docs);
737
+ } finally {
738
+ setLoadingDocs(false);
739
+ }
740
+ }, []);
741
+ const loadDictionary = useCallback(async () => {
742
+ setDictLoading(true);
743
+ try {
744
+ const res = await fetch("/api/spellcheck/dictionary");
745
+ const data = await res.json();
746
+ setDictWords(data.words || []);
747
+ } catch {
748
+ } finally {
749
+ setDictLoading(false);
750
+ }
751
+ }, []);
752
+ useEffect(() => {
753
+ loadResults();
754
+ loadAllDocs();
755
+ }, [loadResults, loadAllDocs]);
756
+ useEffect(() => {
757
+ if (activeTab === "dictionary") {
758
+ loadDictionary();
759
+ }
760
+ }, [activeTab, loadDictionary]);
761
+ useEffect(() => {
762
+ if (!dictToast) return;
763
+ const t = setTimeout(() => setDictToast(""), 3e3);
764
+ return () => clearTimeout(t);
765
+ }, [dictToast]);
766
+ const toggleSelect = useCallback((docId, collection) => {
767
+ const key = `${collection}:${docId}`;
768
+ setSelectedIds((prev) => {
769
+ const next = new Set(prev);
770
+ if (next.has(key)) {
771
+ next.delete(key);
772
+ } else {
773
+ next.add(key);
774
+ }
775
+ return next;
776
+ });
777
+ }, []);
778
+ const [scanCurrent, setScanCurrent] = useState(0);
779
+ const [scanTotal, setScanTotal] = useState(0);
780
+ const [scanCurrentDoc, setScanCurrentDoc] = useState("");
781
+ useEffect(() => {
782
+ if (!scanning) return;
783
+ const interval = setInterval(async () => {
784
+ try {
785
+ const res = await fetch("/api/spellcheck/status");
786
+ if (!res.ok) return;
787
+ const data = await res.json();
788
+ if (data.status === "running") {
789
+ setScanCurrent(data.current || 0);
790
+ setScanTotal(data.total || 0);
791
+ setScanCurrentDoc(data.currentDoc || "");
792
+ setScanProgress(
793
+ `Analyse en cours... ${data.current}/${data.total} \u2014 ${data.currentDoc || ""}`
794
+ );
795
+ } else if (data.status === "completed") {
796
+ setScanProgress(
797
+ `Termin\xE9 \u2014 ${data.totalDocuments} documents, ${data.totalIssues} probl\xE8mes, score moyen ${data.averageScore}`
798
+ );
799
+ setScanning(false);
800
+ setScanCurrent(0);
801
+ setScanTotal(0);
802
+ setScanCurrentDoc("");
803
+ loadResults();
804
+ } else if (data.status === "error") {
805
+ setScanProgress(`Erreur : ${data.error || "Erreur inconnue"}`);
806
+ setScanning(false);
807
+ loadResults();
808
+ } else if (data.status === "idle") {
809
+ setScanProgress("");
810
+ setScanning(false);
811
+ loadResults();
812
+ }
813
+ } catch {
814
+ }
815
+ }, 2e3);
816
+ return () => clearInterval(interval);
817
+ }, [scanning, loadResults]);
818
+ useEffect(() => {
819
+ fetch("/api/spellcheck/status").then((res) => res.json()).then((data) => {
820
+ if (data.status === "running") {
821
+ setScanning(true);
822
+ setScanCurrent(data.current || 0);
823
+ setScanTotal(data.total || 0);
824
+ setScanCurrentDoc(data.currentDoc || "");
825
+ setScanProgress(
826
+ `Analyse en cours... ${data.current}/${data.total} \u2014 ${data.currentDoc || ""}`
827
+ );
828
+ }
829
+ }).catch(() => {
830
+ });
831
+ }, []);
832
+ const handleForceReset = useCallback(async () => {
833
+ try {
834
+ const res = await fetch("/api/spellcheck/bulk", {
835
+ method: "POST",
836
+ headers: { "Content-Type": "application/json" },
837
+ body: JSON.stringify({ force: true })
838
+ });
839
+ if (res.ok) {
840
+ setScanning(true);
841
+ setScanCurrent(0);
842
+ setScanTotal(0);
843
+ setScanProgress("Scan pr\xE9c\xE9dent r\xE9initialis\xE9, red\xE9marrage...");
844
+ }
845
+ } catch {
846
+ setScanProgress("Erreur r\xE9seau");
847
+ }
848
+ }, []);
849
+ const handleScan = useCallback(async (onlySelected = false) => {
850
+ if (scanning) return;
851
+ setScanning(true);
852
+ setSelectedIds(/* @__PURE__ */ new Set());
853
+ setScanCurrent(0);
854
+ setScanTotal(0);
855
+ setScanProgress("D\xE9marrage de l'analyse...");
856
+ const idsToScan = onlySelected ? [...selectedIds].map((key) => {
857
+ const [collection, id] = key.split(":");
858
+ return { id, collection };
859
+ }) : void 0;
860
+ try {
861
+ const res = await fetch("/api/spellcheck/bulk", {
862
+ method: "POST",
863
+ headers: { "Content-Type": "application/json" },
864
+ body: JSON.stringify(idsToScan ? { ids: idsToScan } : {})
865
+ });
866
+ if (res.ok) {
867
+ setScanProgress("Analyse en cours... 0/? \u2014 d\xE9marrage");
868
+ } else if (res.status === 409) {
869
+ setScanProgress("Une analyse est d\xE9j\xE0 en cours...");
870
+ setScanning(false);
871
+ } else {
872
+ setScanProgress("Erreur lors du lancement de l'analyse");
873
+ setScanning(false);
874
+ }
875
+ } catch {
876
+ setScanProgress("Erreur r\xE9seau");
877
+ setScanning(false);
878
+ }
879
+ }, [scanning, selectedIds]);
880
+ const removeIssueFromResults = useCallback((docId, collection, issueIdentifier) => {
881
+ const target = resultsRef.current.find(
882
+ (r) => r.docId === docId && r.collection === collection
883
+ );
884
+ if (!target) return;
885
+ const updatedIssues = target.issues.filter((issue) => {
886
+ if (typeof issueIdentifier.offset !== "number") return true;
887
+ if (issue.offset !== issueIdentifier.offset) return true;
888
+ if (issueIdentifier.original && issue.original !== issueIdentifier.original) return true;
889
+ if (issueIdentifier.ruleId && issue.ruleId !== issueIdentifier.ruleId) return true;
890
+ return false;
891
+ });
892
+ const updatedCount = updatedIssues.length;
893
+ const updatedScore = target.wordCount > 0 ? Math.min(100, Math.max(0, Math.round(100 - updatedCount / target.wordCount * 1e3))) : target.score;
894
+ setResults((prev) => prev.map((r) => {
895
+ if (r.id !== target.id) return r;
896
+ return { ...r, issues: updatedIssues, issueCount: updatedCount, score: updatedScore };
897
+ }));
898
+ if (target.id) {
899
+ fetch(`/api/spellcheck-results/${target.id}`, {
900
+ method: "PATCH",
901
+ headers: { "Content-Type": "application/json" },
902
+ body: JSON.stringify({ issues: updatedIssues, issueCount: updatedCount, score: updatedScore })
903
+ }).catch(() => {
904
+ });
905
+ }
906
+ }, []);
907
+ const handleFix = useCallback(async (docId, collection, original, replacement, offset, length) => {
908
+ try {
909
+ const res = await fetch("/api/spellcheck/fix", {
910
+ method: "POST",
911
+ headers: { "Content-Type": "application/json" },
912
+ body: JSON.stringify({ id: docId, collection, original, replacement, offset, length })
913
+ });
914
+ if (res.ok) {
915
+ removeIssueFromResults(docId, collection, { offset, original });
916
+ }
917
+ } catch {
918
+ }
919
+ }, [removeIssueFromResults]);
920
+ const handleIgnore = useCallback((docId, collection, ruleId, offset, original) => {
921
+ removeIssueFromResults(docId, collection, { offset, ruleId });
922
+ const target = resultsRef.current.find(
923
+ (r) => r.docId === docId && r.collection === collection
924
+ );
925
+ if (target?.id) {
926
+ fetch(`/api/spellcheck-results/${target.id}?depth=0`).then((res) => res.json()).then((data) => {
927
+ const existing = Array.isArray(data.ignoredIssues) ? data.ignoredIssues : [];
928
+ const alreadyIgnored = existing.some((e) => e.ruleId === ruleId && e.original === original);
929
+ if (!alreadyIgnored) {
930
+ fetch(`/api/spellcheck-results/${target.id}`, {
931
+ method: "PATCH",
932
+ headers: { "Content-Type": "application/json" },
933
+ body: JSON.stringify({ ignoredIssues: [...existing, { ruleId, original }] })
934
+ }).catch(() => {
935
+ });
936
+ }
937
+ }).catch(() => {
938
+ });
939
+ }
940
+ }, [removeIssueFromResults]);
941
+ const handleAddToDict = useCallback(async (word) => {
942
+ try {
943
+ const res = await fetch("/api/spellcheck/dictionary", {
944
+ method: "POST",
945
+ headers: { "Content-Type": "application/json" },
946
+ body: JSON.stringify({ word })
947
+ });
948
+ if (res.ok) {
949
+ setDictToast(`"${word}" ajout\xE9 au dictionnaire`);
950
+ if (activeTab === "dictionary") loadDictionary();
951
+ }
952
+ } catch {
953
+ }
954
+ }, [activeTab, loadDictionary]);
955
+ const handleDictAdd = useCallback(async () => {
956
+ if (!dictInput.trim()) return;
957
+ const words = dictInput.split(/[,;\n]+/).map((w) => w.trim()).filter(Boolean);
958
+ if (words.length === 0) return;
959
+ try {
960
+ const res = await fetch("/api/spellcheck/dictionary", {
961
+ method: "POST",
962
+ headers: { "Content-Type": "application/json" },
963
+ body: JSON.stringify({ words })
964
+ });
965
+ if (res.ok) {
966
+ const data = await res.json();
967
+ setDictToast(`${data.count} mot(s) ajout\xE9(s)${data.skipped.length > 0 ? `, ${data.skipped.length} doublon(s)` : ""}`);
968
+ setDictInput("");
969
+ loadDictionary();
970
+ }
971
+ } catch {
972
+ setDictToast("Erreur lors de l'ajout");
973
+ }
974
+ }, [dictInput, loadDictionary]);
975
+ const handleDictDelete = useCallback(async (ids) => {
976
+ try {
977
+ const res = await fetch("/api/spellcheck/dictionary", {
978
+ method: "DELETE",
979
+ headers: { "Content-Type": "application/json" },
980
+ body: JSON.stringify({ ids })
981
+ });
982
+ if (res.ok) {
983
+ const data = await res.json();
984
+ setDictToast(`${data.deleted} mot(s) supprim\xE9(s)`);
985
+ setDictSelectedIds(/* @__PURE__ */ new Set());
986
+ loadDictionary();
987
+ }
988
+ } catch {
989
+ setDictToast("Erreur lors de la suppression");
990
+ }
991
+ }, [loadDictionary]);
992
+ const handleDictImport = useCallback(async () => {
993
+ const words = importText.split(/[,;\n]+/).map((w) => w.trim()).filter(Boolean);
994
+ if (words.length === 0) return;
995
+ try {
996
+ const res = await fetch("/api/spellcheck/dictionary", {
997
+ method: "POST",
998
+ headers: { "Content-Type": "application/json" },
999
+ body: JSON.stringify({ words })
1000
+ });
1001
+ if (res.ok) {
1002
+ const data = await res.json();
1003
+ setDictToast(`${data.count} mot(s) import\xE9(s)${data.skipped.length > 0 ? `, ${data.skipped.length} doublon(s)` : ""}`);
1004
+ setImportText("");
1005
+ setShowImport(false);
1006
+ loadDictionary();
1007
+ }
1008
+ } catch {
1009
+ setDictToast("Erreur lors de l'import");
1010
+ }
1011
+ }, [importText, loadDictionary]);
1012
+ const handleDictExport = useCallback(() => {
1013
+ const content = dictWords.map((w) => w.word).join("\n");
1014
+ const blob = new Blob([content], { type: "text/plain" });
1015
+ const url = URL.createObjectURL(blob);
1016
+ const a = document.createElement("a");
1017
+ a.href = url;
1018
+ a.download = "spellcheck-dictionary.txt";
1019
+ a.click();
1020
+ URL.revokeObjectURL(url);
1021
+ setDictToast("Dictionnaire export\xE9");
1022
+ }, [dictWords]);
1023
+ const handleSort = (key) => {
1024
+ if (sortKey === key) {
1025
+ setSortDir(sortDir === "asc" ? "desc" : "asc");
1026
+ } else {
1027
+ setSortKey(key);
1028
+ setSortDir(key === "score" ? "asc" : "desc");
1029
+ }
1030
+ };
1031
+ const mergedDocs = React3.useMemo(() => {
1032
+ const resultMap = /* @__PURE__ */ new Map();
1033
+ for (const r of results) {
1034
+ resultMap.set(`${r.collection}:${r.docId}`, r);
1035
+ }
1036
+ const seen = /* @__PURE__ */ new Set();
1037
+ const merged = [];
1038
+ for (const doc of allDocs) {
1039
+ const key = `${doc.collection}:${doc.id}`;
1040
+ seen.add(key);
1041
+ const existing = resultMap.get(key);
1042
+ merged.push({
1043
+ docId: doc.id,
1044
+ collection: doc.collection,
1045
+ title: doc.title,
1046
+ slug: doc.slug,
1047
+ score: existing?.score ?? null,
1048
+ issueCount: existing?.issueCount ?? 0,
1049
+ wordCount: existing?.wordCount ?? 0,
1050
+ issues: existing?.issues ?? [],
1051
+ lastChecked: existing?.lastChecked ?? null,
1052
+ resultId: existing?.id ?? null
1053
+ });
1054
+ }
1055
+ for (const r of results) {
1056
+ const key = `${r.collection}:${r.docId}`;
1057
+ if (!seen.has(key)) {
1058
+ merged.push({
1059
+ docId: r.docId,
1060
+ collection: r.collection,
1061
+ title: r.title,
1062
+ slug: r.slug,
1063
+ score: r.score,
1064
+ issueCount: r.issueCount,
1065
+ wordCount: r.wordCount,
1066
+ issues: r.issues,
1067
+ lastChecked: r.lastChecked,
1068
+ resultId: r.id
1069
+ });
1070
+ }
1071
+ }
1072
+ return merged;
1073
+ }, [results, allDocs]);
1074
+ const collections = React3.useMemo(() => {
1075
+ return [...new Set(mergedDocs.map((d) => d.collection))].sort();
1076
+ }, [mergedDocs]);
1077
+ const filteredMergedDocs = React3.useMemo(() => {
1078
+ return filterCollection ? mergedDocs.filter((d) => d.collection === filterCollection) : mergedDocs;
1079
+ }, [mergedDocs, filterCollection]);
1080
+ const sortedResults = [...filteredMergedDocs].sort((a, b) => {
1081
+ const dir = sortDir === "asc" ? 1 : -1;
1082
+ switch (sortKey) {
1083
+ case "title":
1084
+ return dir * (a.title || "").localeCompare(b.title || "");
1085
+ case "score":
1086
+ return dir * ((a.score ?? 101) - (b.score ?? 101));
1087
+ case "issueCount":
1088
+ return dir * (a.issueCount - b.issueCount);
1089
+ case "wordCount":
1090
+ return dir * ((a.wordCount || 0) - (b.wordCount || 0));
1091
+ case "lastChecked": {
1092
+ const aTime = a.lastChecked ? new Date(a.lastChecked).getTime() : 0;
1093
+ const bTime = b.lastChecked ? new Date(b.lastChecked).getTime() : 0;
1094
+ return dir * (aTime - bTime);
1095
+ }
1096
+ default:
1097
+ return 0;
1098
+ }
1099
+ });
1100
+ const scannedResults = results.filter((r) => !filterCollection || r.collection === filterCollection);
1101
+ const totalIssues = scannedResults.reduce((sum, r) => sum + r.issueCount, 0);
1102
+ const avgScore = scannedResults.length > 0 ? Math.round(scannedResults.reduce((sum, r) => sum + r.score, 0) / scannedResults.length) : 0;
1103
+ const sortIndicator = (key) => {
1104
+ if (sortKey !== key) return "";
1105
+ return sortDir === "asc" ? " \u2191" : " \u2193";
1106
+ };
1107
+ const docsWithErrors = React3.useMemo(() => {
1108
+ return filteredMergedDocs.filter((d) => d.issueCount > 0 && d.lastChecked);
1109
+ }, [filteredMergedDocs]);
1110
+ const handleToggleSelectAll = () => {
1111
+ if (selectedIds.size === filteredMergedDocs.length && filteredMergedDocs.length > 0) {
1112
+ setSelectedIds(/* @__PURE__ */ new Set());
1113
+ } else {
1114
+ setSelectedIds(new Set(filteredMergedDocs.map((d) => `${d.collection}:${d.docId}`)));
1115
+ }
1116
+ };
1117
+ const handleScanErrors = useCallback(() => {
1118
+ if (scanning || docsWithErrors.length === 0) return;
1119
+ const errorIds = new Set(docsWithErrors.map((d) => `${d.collection}:${d.docId}`));
1120
+ setSelectedIds(errorIds);
1121
+ setTimeout(() => {
1122
+ setScanning(true);
1123
+ setScanCurrent(0);
1124
+ setScanTotal(0);
1125
+ setScanProgress("D\xE9marrage de l'analyse des pages avec erreurs...");
1126
+ const idsToScan = docsWithErrors.map((d) => ({ id: d.docId, collection: d.collection }));
1127
+ fetch("/api/spellcheck/bulk", {
1128
+ method: "POST",
1129
+ headers: { "Content-Type": "application/json" },
1130
+ body: JSON.stringify({ ids: idsToScan })
1131
+ }).then((res) => {
1132
+ if (res.ok) {
1133
+ setScanProgress(`Analyse en cours... 0/${idsToScan.length} \u2014 d\xE9marrage`);
1134
+ } else if (res.status === 409) {
1135
+ setScanProgress("Une analyse est d\xE9j\xE0 en cours...");
1136
+ } else {
1137
+ setScanProgress("Erreur lors du lancement");
1138
+ setScanning(false);
1139
+ }
1140
+ }).catch(() => {
1141
+ setScanProgress("Erreur r\xE9seau");
1142
+ setScanning(false);
1143
+ });
1144
+ }, 0);
1145
+ }, [scanning, docsWithErrors]);
1146
+ const filteredDictWords = React3.useMemo(() => {
1147
+ if (!dictSearch.trim()) return dictWords;
1148
+ const q = dictSearch.toLowerCase();
1149
+ return dictWords.filter((w) => w.word.toLowerCase().includes(q));
1150
+ }, [dictWords, dictSearch]);
1151
+ const handleDictToggleSelectAll = () => {
1152
+ if (dictSelectedIds.size === filteredDictWords.length && filteredDictWords.length > 0) {
1153
+ setDictSelectedIds(/* @__PURE__ */ new Set());
1154
+ } else {
1155
+ setDictSelectedIds(new Set(filteredDictWords.map((w) => w.id)));
1156
+ }
1157
+ };
1158
+ return /* @__PURE__ */ jsxs("div", { style: styles3.container, children: [
1159
+ /* @__PURE__ */ jsxs("div", { style: styles3.header, children: [
1160
+ /* @__PURE__ */ jsxs("div", { children: [
1161
+ /* @__PURE__ */ jsx("h1", { style: styles3.title, children: "Correcteur orthographique" }),
1162
+ /* @__PURE__ */ jsx("p", { style: styles3.description, children: "Analyse orthographique et grammaticale du contenu via LanguageTool" })
1163
+ ] }),
1164
+ activeTab === "results" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" }, children: [
1165
+ selectedIds.size > 0 && /* @__PURE__ */ jsx(
1166
+ "button",
1167
+ {
1168
+ type: "button",
1169
+ style: {
1170
+ ...styles3.scanBtn,
1171
+ backgroundColor: "var(--theme-success-500)",
1172
+ ...scanning ? styles3.scanBtnDisabled : {}
1173
+ },
1174
+ onClick: () => handleScan(true),
1175
+ disabled: scanning,
1176
+ children: scanning ? "Analyse..." : `Scanner (${selectedIds.size})`
1177
+ }
1178
+ ),
1179
+ docsWithErrors.length > 0 && selectedIds.size === 0 && /* @__PURE__ */ jsx(
1180
+ "button",
1181
+ {
1182
+ type: "button",
1183
+ style: {
1184
+ ...styles3.scanBtn,
1185
+ backgroundColor: "var(--theme-error-500)",
1186
+ ...scanning ? styles3.scanBtnDisabled : {}
1187
+ },
1188
+ onClick: handleScanErrors,
1189
+ disabled: scanning,
1190
+ children: scanning ? "Analyse..." : `Rescanner erreurs (${docsWithErrors.length})`
1191
+ }
1192
+ ),
1193
+ /* @__PURE__ */ jsx(
1194
+ "button",
1195
+ {
1196
+ type: "button",
1197
+ style: {
1198
+ ...styles3.scanBtn,
1199
+ ...scanning ? styles3.scanBtnDisabled : {}
1200
+ },
1201
+ onClick: () => handleScan(false),
1202
+ disabled: scanning,
1203
+ children: scanning ? "Analyse..." : "Scanner tout"
1204
+ }
1205
+ )
1206
+ ] })
1207
+ ] }),
1208
+ /* @__PURE__ */ jsxs("div", { style: styles3.tabs, children: [
1209
+ /* @__PURE__ */ jsx(
1210
+ "button",
1211
+ {
1212
+ type: "button",
1213
+ style: styles3.tab(activeTab === "results"),
1214
+ onClick: () => setActiveTab("results"),
1215
+ children: "R\xE9sultats"
1216
+ }
1217
+ ),
1218
+ /* @__PURE__ */ jsxs(
1219
+ "button",
1220
+ {
1221
+ type: "button",
1222
+ style: styles3.tab(activeTab === "dictionary"),
1223
+ onClick: () => setActiveTab("dictionary"),
1224
+ children: [
1225
+ "Dictionnaire ",
1226
+ dictWords.length > 0 && `(${dictWords.length})`
1227
+ ]
1228
+ }
1229
+ )
1230
+ ] }),
1231
+ dictToast && /* @__PURE__ */ jsx("div", { style: {
1232
+ padding: "8px 16px",
1233
+ marginBottom: "16px",
1234
+ borderRadius: "4px",
1235
+ backgroundColor: "var(--theme-success-100, #dcfce7)",
1236
+ border: "1px solid var(--theme-success-500)",
1237
+ color: "var(--theme-success-700, #15803d)",
1238
+ fontSize: "13px"
1239
+ }, children: dictToast }),
1240
+ activeTab === "results" && /* @__PURE__ */ jsxs(Fragment, { children: [
1241
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center", marginBottom: "16px", flexWrap: "wrap" }, children: [
1242
+ collections.length > 1 && /* @__PURE__ */ jsxs(
1243
+ "select",
1244
+ {
1245
+ value: filterCollection,
1246
+ onChange: (e) => {
1247
+ setFilterCollection(e.target.value);
1248
+ setSelectedIds(/* @__PURE__ */ new Set());
1249
+ },
1250
+ style: {
1251
+ padding: "6px 10px",
1252
+ fontSize: "13px",
1253
+ border: "1px solid var(--theme-elevation-200)",
1254
+ borderRadius: "4px",
1255
+ backgroundColor: "var(--theme-elevation-0)",
1256
+ color: "var(--theme-text)"
1257
+ },
1258
+ children: [
1259
+ /* @__PURE__ */ jsx("option", { value: "", children: "Toutes les collections" }),
1260
+ collections.map((c) => /* @__PURE__ */ jsx("option", { value: c, children: c }, c))
1261
+ ]
1262
+ }
1263
+ ),
1264
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "12px", color: "var(--theme-elevation-500)" }, children: [
1265
+ filteredMergedDocs.length,
1266
+ " document(s)",
1267
+ selectedIds.size > 0 && ` \u2014 ${selectedIds.size} s\xE9lectionn\xE9(s)`
1268
+ ] })
1269
+ ] }),
1270
+ scanProgress && /* @__PURE__ */ jsxs("div", { style: styles3.progress, children: [
1271
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
1272
+ /* @__PURE__ */ jsx("span", { children: scanProgress }),
1273
+ scanProgress.includes("d\xE9j\xE0 en cours") && !scanning && /* @__PURE__ */ jsx(
1274
+ "button",
1275
+ {
1276
+ type: "button",
1277
+ onClick: handleForceReset,
1278
+ style: {
1279
+ padding: "4px 12px",
1280
+ fontSize: "12px",
1281
+ fontWeight: 600,
1282
+ border: "none",
1283
+ borderRadius: "4px",
1284
+ backgroundColor: "var(--theme-error-500)",
1285
+ color: "#fff",
1286
+ cursor: "pointer",
1287
+ marginLeft: "12px"
1288
+ },
1289
+ children: "Forcer r\xE9initialisation"
1290
+ }
1291
+ )
1292
+ ] }),
1293
+ scanning && scanTotal > 0 && /* @__PURE__ */ jsx("div", { style: styles3.progressBar, children: /* @__PURE__ */ jsx("div", { style: styles3.progressFill(Math.round(scanCurrent / scanTotal * 100)) }) })
1294
+ ] }),
1295
+ results.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles3.summary, children: [
1296
+ /* @__PURE__ */ jsxs("div", { style: styles3.summaryCard, children: [
1297
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryLabel, children: "Documents" }),
1298
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryValue, children: results.length })
1299
+ ] }),
1300
+ /* @__PURE__ */ jsxs("div", { style: styles3.summaryCard, children: [
1301
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryLabel, children: "Score moyen" }),
1302
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryValue, children: avgScore })
1303
+ ] }),
1304
+ /* @__PURE__ */ jsxs("div", { style: styles3.summaryCard, children: [
1305
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryLabel, children: "Probl\xE8mes" }),
1306
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryValue, children: totalIssues })
1307
+ ] }),
1308
+ /* @__PURE__ */ jsxs("div", { style: styles3.summaryCard, children: [
1309
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryLabel, children: "Sans erreurs" }),
1310
+ /* @__PURE__ */ jsx("div", { style: styles3.summaryValue, children: results.filter((r) => r.issueCount === 0).length })
1311
+ ] })
1312
+ ] }),
1313
+ loading && loadingDocs ? /* @__PURE__ */ jsx("div", { style: styles3.empty, children: "Chargement..." }) : sortedResults.length === 0 ? /* @__PURE__ */ jsx("div", { style: styles3.empty, children: "Aucun document trouv\xE9. V\xE9rifiez la configuration des collections." }) : /* @__PURE__ */ jsxs("table", { style: styles3.table, children: [
1314
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1315
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, width: "36px" }, children: /* @__PURE__ */ jsx(
1316
+ "input",
1317
+ {
1318
+ type: "checkbox",
1319
+ checked: selectedIds.size === filteredMergedDocs.length && filteredMergedDocs.length > 0,
1320
+ onChange: handleToggleSelectAll,
1321
+ style: { cursor: "pointer" }
1322
+ }
1323
+ ) }),
1324
+ /* @__PURE__ */ jsxs("th", { style: styles3.th, onClick: () => handleSort("title"), children: [
1325
+ "Document",
1326
+ sortIndicator("title")
1327
+ ] }),
1328
+ /* @__PURE__ */ jsx("th", { style: styles3.th, children: "Collection" }),
1329
+ /* @__PURE__ */ jsxs("th", { style: styles3.th, onClick: () => handleSort("score"), children: [
1330
+ "Score",
1331
+ sortIndicator("score")
1332
+ ] }),
1333
+ /* @__PURE__ */ jsxs("th", { style: styles3.th, onClick: () => handleSort("issueCount"), children: [
1334
+ "Probl\xE8mes",
1335
+ sortIndicator("issueCount")
1336
+ ] }),
1337
+ /* @__PURE__ */ jsxs("th", { style: styles3.th, onClick: () => handleSort("wordCount"), children: [
1338
+ "Mots",
1339
+ sortIndicator("wordCount")
1340
+ ] }),
1341
+ /* @__PURE__ */ jsxs("th", { style: styles3.th, onClick: () => handleSort("lastChecked"), children: [
1342
+ "V\xE9rifi\xE9",
1343
+ sortIndicator("lastChecked")
1344
+ ] })
1345
+ ] }) }),
1346
+ /* @__PURE__ */ jsx("tbody", { children: sortedResults.map((r) => {
1347
+ const rowKey = `${r.collection}:${r.docId}`;
1348
+ const isSelected = selectedIds.has(rowKey);
1349
+ return /* @__PURE__ */ jsxs(React3.Fragment, { children: [
1350
+ /* @__PURE__ */ jsxs(
1351
+ "tr",
1352
+ {
1353
+ style: {
1354
+ ...styles3.tr,
1355
+ ...hoveredRow === rowKey ? styles3.trHover : {},
1356
+ ...isSelected ? { backgroundColor: "var(--theme-elevation-100)" } : {}
1357
+ },
1358
+ onClick: () => {
1359
+ if (r.issues && r.issues.length > 0) {
1360
+ setExpandedId(expandedId === rowKey ? null : rowKey);
1361
+ }
1362
+ },
1363
+ onMouseEnter: () => setHoveredRow(rowKey),
1364
+ onMouseLeave: () => setHoveredRow(null),
1365
+ children: [
1366
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: /* @__PURE__ */ jsx(
1367
+ "input",
1368
+ {
1369
+ type: "checkbox",
1370
+ checked: isSelected,
1371
+ onChange: () => toggleSelect(r.docId, r.collection),
1372
+ onClick: (e) => e.stopPropagation(),
1373
+ style: { cursor: "pointer" }
1374
+ }
1375
+ ) }),
1376
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: /* @__PURE__ */ jsx(
1377
+ "a",
1378
+ {
1379
+ href: `/admin/collections/${r.collection}/${r.docId}`,
1380
+ style: styles3.link,
1381
+ onClick: (e) => e.stopPropagation(),
1382
+ children: r.title || r.slug || r.docId
1383
+ }
1384
+ ) }),
1385
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: r.collection }),
1386
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: r.score !== null ? /* @__PURE__ */ jsx("span", { style: styles3.scoreBadge(r.score), children: r.score }) : /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: "var(--theme-elevation-400)" }, children: "\u2014" }) }),
1387
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: r.lastChecked ? r.issueCount : "\u2014" }),
1388
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: r.wordCount || "\u2014" }),
1389
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: r.lastChecked ? new Date(r.lastChecked).toLocaleString("fr-FR", {
1390
+ day: "2-digit",
1391
+ month: "2-digit",
1392
+ hour: "2-digit",
1393
+ minute: "2-digit",
1394
+ second: "2-digit"
1395
+ }) : "\u2014" })
1396
+ ]
1397
+ }
1398
+ ),
1399
+ expandedId === rowKey && r.issues && r.issues.length > 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 7, style: styles3.expandedRow, children: r.issues.map((issue, i) => /* @__PURE__ */ jsx(
1400
+ IssueCard,
1401
+ {
1402
+ issue,
1403
+ onFix: (original, replacement, offset, length) => handleFix(r.docId, r.collection, original, replacement, offset, length),
1404
+ onIgnore: () => handleIgnore(r.docId, r.collection, issue.ruleId, issue.offset, issue.original),
1405
+ onAddToDict: handleAddToDict
1406
+ },
1407
+ `${issue.ruleId}-${issue.offset}-${i}`
1408
+ )) }) })
1409
+ ] }, rowKey);
1410
+ }) })
1411
+ ] })
1412
+ ] }),
1413
+ activeTab === "dictionary" && /* @__PURE__ */ jsxs(Fragment, { children: [
1414
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", marginBottom: "16px", alignItems: "center" }, children: [
1415
+ /* @__PURE__ */ jsx(
1416
+ "input",
1417
+ {
1418
+ type: "text",
1419
+ value: dictInput,
1420
+ onChange: (e) => setDictInput(e.target.value),
1421
+ onKeyDown: (e) => {
1422
+ if (e.key === "Enter") handleDictAdd();
1423
+ },
1424
+ placeholder: "Ajouter un mot (virgules pour plusieurs)...",
1425
+ style: {
1426
+ flex: 1,
1427
+ padding: "8px 12px",
1428
+ fontSize: "13px",
1429
+ border: "1px solid var(--theme-elevation-200)",
1430
+ borderRadius: "4px",
1431
+ backgroundColor: "var(--theme-elevation-0)",
1432
+ color: "var(--theme-text)"
1433
+ }
1434
+ }
1435
+ ),
1436
+ /* @__PURE__ */ jsx(
1437
+ "button",
1438
+ {
1439
+ type: "button",
1440
+ onClick: handleDictAdd,
1441
+ disabled: !dictInput.trim(),
1442
+ style: {
1443
+ ...styles3.scanBtn,
1444
+ padding: "8px 16px",
1445
+ fontSize: "13px",
1446
+ ...!dictInput.trim() ? styles3.scanBtnDisabled : {}
1447
+ },
1448
+ children: "Ajouter"
1449
+ }
1450
+ )
1451
+ ] }),
1452
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", marginBottom: "16px", alignItems: "center", flexWrap: "wrap" }, children: [
1453
+ /* @__PURE__ */ jsx(
1454
+ "input",
1455
+ {
1456
+ type: "text",
1457
+ value: dictSearch,
1458
+ onChange: (e) => setDictSearch(e.target.value),
1459
+ placeholder: "Rechercher...",
1460
+ style: {
1461
+ padding: "6px 10px",
1462
+ fontSize: "13px",
1463
+ border: "1px solid var(--theme-elevation-200)",
1464
+ borderRadius: "4px",
1465
+ backgroundColor: "var(--theme-elevation-0)",
1466
+ color: "var(--theme-text)",
1467
+ width: "200px"
1468
+ }
1469
+ }
1470
+ ),
1471
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "12px", color: "var(--theme-elevation-500)" }, children: [
1472
+ filteredDictWords.length,
1473
+ " mot(s)",
1474
+ dictSelectedIds.size > 0 && ` \u2014 ${dictSelectedIds.size} s\xE9lectionn\xE9(s)`
1475
+ ] }),
1476
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
1477
+ dictSelectedIds.size > 0 && /* @__PURE__ */ jsxs(
1478
+ "button",
1479
+ {
1480
+ type: "button",
1481
+ onClick: () => handleDictDelete([...dictSelectedIds]),
1482
+ style: {
1483
+ ...styles3.scanBtn,
1484
+ padding: "6px 14px",
1485
+ fontSize: "12px",
1486
+ backgroundColor: "var(--theme-error-500)"
1487
+ },
1488
+ children: [
1489
+ "Supprimer (",
1490
+ dictSelectedIds.size,
1491
+ ")"
1492
+ ]
1493
+ }
1494
+ ),
1495
+ /* @__PURE__ */ jsx(
1496
+ "button",
1497
+ {
1498
+ type: "button",
1499
+ onClick: () => setShowImport(!showImport),
1500
+ style: {
1501
+ ...styles3.scanBtn,
1502
+ padding: "6px 14px",
1503
+ fontSize: "12px",
1504
+ backgroundColor: "var(--theme-elevation-600)"
1505
+ },
1506
+ children: showImport ? "Annuler" : "Importer"
1507
+ }
1508
+ ),
1509
+ /* @__PURE__ */ jsx(
1510
+ "button",
1511
+ {
1512
+ type: "button",
1513
+ onClick: handleDictExport,
1514
+ disabled: dictWords.length === 0,
1515
+ style: {
1516
+ ...styles3.scanBtn,
1517
+ padding: "6px 14px",
1518
+ fontSize: "12px",
1519
+ backgroundColor: "var(--theme-elevation-600)",
1520
+ ...dictWords.length === 0 ? styles3.scanBtnDisabled : {}
1521
+ },
1522
+ children: "Exporter"
1523
+ }
1524
+ )
1525
+ ] }),
1526
+ showImport && /* @__PURE__ */ jsxs("div", { style: {
1527
+ marginBottom: "16px",
1528
+ padding: "16px",
1529
+ borderRadius: "6px",
1530
+ backgroundColor: "var(--theme-elevation-50)",
1531
+ border: "1px solid var(--theme-elevation-150)"
1532
+ }, children: [
1533
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", marginBottom: "8px", color: "var(--theme-text)" }, children: "Un mot par ligne, ou s\xE9par\xE9s par des virgules :" }),
1534
+ /* @__PURE__ */ jsx(
1535
+ "textarea",
1536
+ {
1537
+ value: importText,
1538
+ onChange: (e) => setImportText(e.target.value),
1539
+ rows: 6,
1540
+ style: {
1541
+ width: "100%",
1542
+ padding: "8px",
1543
+ fontSize: "13px",
1544
+ border: "1px solid var(--theme-elevation-200)",
1545
+ borderRadius: "4px",
1546
+ backgroundColor: "var(--theme-elevation-0)",
1547
+ color: "var(--theme-text)",
1548
+ fontFamily: "monospace",
1549
+ boxSizing: "border-box",
1550
+ resize: "vertical"
1551
+ },
1552
+ placeholder: "mot1\nmot2\nmot3, mot4, mot5"
1553
+ }
1554
+ ),
1555
+ /* @__PURE__ */ jsxs(
1556
+ "button",
1557
+ {
1558
+ type: "button",
1559
+ onClick: handleDictImport,
1560
+ disabled: !importText.trim(),
1561
+ style: {
1562
+ ...styles3.scanBtn,
1563
+ marginTop: "8px",
1564
+ padding: "8px 16px",
1565
+ fontSize: "13px",
1566
+ ...!importText.trim() ? styles3.scanBtnDisabled : {}
1567
+ },
1568
+ children: [
1569
+ "Importer ",
1570
+ importText.trim() ? `(${importText.split(/[,;\n]+/).filter((w) => w.trim()).length} mots)` : ""
1571
+ ]
1572
+ }
1573
+ )
1574
+ ] }),
1575
+ dictLoading ? /* @__PURE__ */ jsx("div", { style: styles3.empty, children: "Chargement du dictionnaire..." }) : filteredDictWords.length === 0 ? /* @__PURE__ */ jsx("div", { style: styles3.empty, children: dictSearch ? "Aucun mot trouv\xE9." : "Dictionnaire vide. Ajoutez des mots ci-dessus." }) : /* @__PURE__ */ jsxs("table", { style: styles3.table, children: [
1576
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1577
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, width: "36px", cursor: "default" }, children: /* @__PURE__ */ jsx(
1578
+ "input",
1579
+ {
1580
+ type: "checkbox",
1581
+ checked: dictSelectedIds.size === filteredDictWords.length && filteredDictWords.length > 0,
1582
+ onChange: handleDictToggleSelectAll,
1583
+ style: { cursor: "pointer" }
1584
+ }
1585
+ ) }),
1586
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, cursor: "default" }, children: "Mot" }),
1587
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, cursor: "default" }, children: "Ajout\xE9 par" }),
1588
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, cursor: "default" }, children: "Date" }),
1589
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, width: "60px", cursor: "default" } })
1590
+ ] }) }),
1591
+ /* @__PURE__ */ jsx("tbody", { children: filteredDictWords.map((w) => {
1592
+ const isSelected = dictSelectedIds.has(w.id);
1593
+ const addedByName = w.addedBy && typeof w.addedBy === "object" ? w.addedBy.name || w.addedBy.email || "\u2014" : "\u2014";
1594
+ return /* @__PURE__ */ jsxs(
1595
+ "tr",
1596
+ {
1597
+ style: {
1598
+ ...isSelected ? { backgroundColor: "var(--theme-elevation-100)" } : {}
1599
+ },
1600
+ children: [
1601
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: /* @__PURE__ */ jsx(
1602
+ "input",
1603
+ {
1604
+ type: "checkbox",
1605
+ checked: isSelected,
1606
+ onChange: () => {
1607
+ setDictSelectedIds((prev) => {
1608
+ const next = new Set(prev);
1609
+ if (next.has(w.id)) next.delete(w.id);
1610
+ else next.add(w.id);
1611
+ return next;
1612
+ });
1613
+ },
1614
+ style: { cursor: "pointer" }
1615
+ }
1616
+ ) }),
1617
+ /* @__PURE__ */ jsx("td", { style: { ...styles3.td, fontFamily: "monospace", fontWeight: 500 }, children: w.word }),
1618
+ /* @__PURE__ */ jsx("td", { style: { ...styles3.td, fontSize: "12px", color: "var(--theme-elevation-500)" }, children: addedByName }),
1619
+ /* @__PURE__ */ jsx("td", { style: { ...styles3.td, fontSize: "12px", color: "var(--theme-elevation-500)" }, children: w.createdAt ? new Date(w.createdAt).toLocaleString("fr-FR", {
1620
+ day: "2-digit",
1621
+ month: "2-digit",
1622
+ year: "2-digit"
1623
+ }) : "\u2014" }),
1624
+ /* @__PURE__ */ jsx("td", { style: styles3.td, children: /* @__PURE__ */ jsx(
1625
+ "button",
1626
+ {
1627
+ type: "button",
1628
+ onClick: () => handleDictDelete([w.id]),
1629
+ style: {
1630
+ background: "none",
1631
+ border: "none",
1632
+ cursor: "pointer",
1633
+ color: "var(--theme-error-500)",
1634
+ fontSize: "14px",
1635
+ padding: "2px 6px"
1636
+ },
1637
+ title: "Supprimer",
1638
+ children: "\u2715"
1639
+ }
1640
+ ) })
1641
+ ]
1642
+ },
1643
+ w.id
1644
+ );
1645
+ }) })
1646
+ ] })
1647
+ ] })
1648
+ ] });
1649
+ };
1650
+ var SpellCheckScoreCell = ({
1651
+ rowData,
1652
+ collectionSlug
1653
+ }) => {
1654
+ const [score, setScore] = useState(null);
1655
+ const [issueCount, setIssueCount] = useState(null);
1656
+ useEffect(() => {
1657
+ if (!rowData?.id || !collectionSlug) return;
1658
+ fetch(
1659
+ `/api/spellcheck-results?where[docId][equals]=${rowData.id}&where[collection][equals]=${collectionSlug}&limit=1&depth=0`
1660
+ ).then((res) => res.json()).then((data) => {
1661
+ if (data.docs?.[0]) {
1662
+ setScore(data.docs[0].score);
1663
+ setIssueCount(data.docs[0].issueCount ?? 0);
1664
+ }
1665
+ }).catch(() => {
1666
+ });
1667
+ }, [rowData?.id, collectionSlug]);
1668
+ if (score === null) {
1669
+ return /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: "var(--theme-elevation-400)" }, children: "\u2014" });
1670
+ }
1671
+ const bgColor = score >= 95 ? "var(--theme-success-500)" : score >= 80 ? "#f59e0b" : "var(--theme-error-500)";
1672
+ return /* @__PURE__ */ jsxs(
1673
+ "span",
1674
+ {
1675
+ style: {
1676
+ display: "inline-flex",
1677
+ alignItems: "center",
1678
+ gap: "6px"
1679
+ },
1680
+ children: [
1681
+ /* @__PURE__ */ jsx(
1682
+ "span",
1683
+ {
1684
+ style: {
1685
+ display: "inline-block",
1686
+ padding: "2px 8px",
1687
+ borderRadius: "10px",
1688
+ fontSize: "12px",
1689
+ fontWeight: 700,
1690
+ color: "#fff",
1691
+ backgroundColor: bgColor
1692
+ },
1693
+ children: score
1694
+ }
1695
+ ),
1696
+ issueCount !== null && issueCount > 0 && /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: "var(--theme-error-500)" }, children: issueCount })
1697
+ ]
1698
+ }
1699
+ );
1700
+ };
1701
+
1702
+ export { IssueCard, SpellCheckDashboard, SpellCheckField, SpellCheckScoreCell };