@consilioweb/payload-support 0.16.0 → 1.0.0
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/README.md +347 -435
- package/dist/components/TicketConversation/RewriteDropdown.cjs +98 -0
- package/dist/components/TicketConversation/RewriteDropdown.js +93 -0
- package/dist/components/TicketConversation/SkeletonText.cjs +21 -0
- package/dist/components/TicketConversation/SkeletonText.js +20 -0
- package/dist/components/TicketConversation/constants.cjs +27 -0
- package/dist/components/TicketConversation/constants.js +26 -1
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +20 -0
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.js +19 -0
- package/dist/components/TicketConversation/index.cjs +10 -147
- package/dist/components/TicketConversation/index.js +4 -141
- package/dist/index.cjs +1604 -671
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1602 -671
- package/dist/views/BillingView/client.cjs +40 -54
- package/dist/views/BillingView/client.js +40 -54
- package/dist/views/ChatView/client.cjs +4 -3
- package/dist/views/ChatView/client.js +4 -3
- package/dist/views/CrmView/client.cjs +7 -7
- package/dist/views/CrmView/client.js +7 -7
- package/dist/views/EmailTrackingView/client.cjs +6 -5
- package/dist/views/EmailTrackingView/client.js +6 -5
- package/dist/views/ImportConversationView/client.cjs +4 -4
- package/dist/views/ImportConversationView/client.js +4 -4
- package/dist/views/LogsView/client.cjs +3 -2
- package/dist/views/LogsView/client.js +3 -2
- package/dist/views/NewTicketView/client.cjs +1 -1
- package/dist/views/NewTicketView/client.js +1 -1
- package/dist/views/PendingEmailsView/client.cjs +7 -6
- package/dist/views/PendingEmailsView/client.js +7 -6
- package/dist/views/SupportDashboardView/client.cjs +19 -22
- package/dist/views/SupportDashboardView/client.js +19 -22
- package/dist/views/TicketDetailView/NextActionItem.cjs +40 -0
- package/dist/views/TicketDetailView/NextActionItem.js +34 -0
- package/dist/views/TicketDetailView/RewriteDropdown.cjs +80 -0
- package/dist/views/TicketDetailView/RewriteDropdown.js +78 -0
- package/dist/views/TicketDetailView/client.cjs +59 -190
- package/dist/views/TicketDetailView/client.js +52 -183
- package/dist/views/TicketDetailView/constants.cjs +20 -0
- package/dist/views/TicketDetailView/constants.js +16 -0
- package/dist/views/TicketDetailView/helpers.cjs +23 -0
- package/dist/views/TicketDetailView/helpers.js +20 -0
- package/dist/views/TicketDetailView/types.cjs +2 -0
- package/dist/views/TicketDetailView/types.js +1 -0
- package/dist/views/TicketInboxView/client.cjs +14 -10
- package/dist/views/TicketInboxView/client.js +14 -10
- package/dist/views/TicketingSettingsView/client.cjs +101 -96
- package/dist/views/TicketingSettingsView/client.js +92 -88
- package/dist/views/TimeDashboardView/client.cjs +2 -2
- package/dist/views/TimeDashboardView/client.js +2 -2
- package/dist/views/shared/StatusPill.cjs +23 -12
- package/dist/views/shared/StatusPill.js +23 -12
- package/dist/views/shared/dateLocale.cjs +5 -0
- package/dist/views/shared/dateLocale.js +3 -0
- package/package.json +23 -15
- package/src/__tests__/generateTrackingToken.test.ts +53 -0
- package/src/__tests__/integration/ai-agent.test.ts +46 -0
- package/src/__tests__/integration/auth.test.ts +43 -0
- package/src/__tests__/integration/automation-rules.test.ts +68 -0
- package/src/__tests__/integration/buildTestPayload.ts +52 -0
- package/src/__tests__/integration/channels.test.ts +52 -0
- package/src/__tests__/integration/core-behaviors.test.ts +58 -0
- package/src/__tests__/integration/dashboard-volume.test.ts +31 -0
- package/src/__tests__/integration/digest.test.ts +48 -0
- package/src/__tests__/integration/invoice-pdf.test.ts +30 -0
- package/src/__tests__/integration/isolation.test.ts +81 -0
- package/src/__tests__/integration/mentions-invoice.test.ts +50 -0
- package/src/__tests__/integration/nps.test.ts +37 -0
- package/src/__tests__/integration/sanitization.test.ts +35 -0
- package/src/__tests__/integration/sla-pause.test.ts +39 -0
- package/src/__tests__/integration/smoke.test.ts +26 -0
- package/src/__tests__/integration/snooze.test.ts +68 -0
- package/src/__tests__/integration/teams.test.ts +38 -0
- package/src/__tests__/rateLimiter.test.ts +89 -0
- package/src/__tests__/sanitizeHtml.test.ts +165 -0
- package/src/__tests__/sla.test.ts +258 -0
- package/src/collections/AutomationRules.ts +66 -0
- package/src/collections/NotificationQueue.ts +30 -0
- package/src/collections/SatisfactionSurveys.ts +11 -2
- package/src/collections/SupportClients.ts +58 -1
- package/src/collections/SupportTeam.ts +27 -0
- package/src/collections/TicketActivityLog.ts +9 -4
- package/src/collections/TicketMessages.ts +129 -31
- package/src/collections/Tickets.ts +31 -73
- package/src/collections/index.ts +3 -0
- package/src/components/TicketConversation/RewriteDropdown.tsx +86 -0
- package/src/components/TicketConversation/SkeletonText.tsx +23 -0
- package/src/components/TicketConversation/constants.ts +29 -0
- package/src/components/TicketConversation/hooks/useDocumentIdFromUrl.ts +21 -0
- package/src/components/TicketConversation/index.tsx +4 -152
- package/src/endpoints/admin-chat-stream.ts +3 -4
- package/src/endpoints/admin-chat.ts +11 -20
- package/src/endpoints/admin-stats.ts +70 -32
- package/src/endpoints/ai-agent.ts +36 -0
- package/src/endpoints/apply-macro.ts +8 -14
- package/src/endpoints/auth-2fa.ts +12 -12
- package/src/endpoints/auto-close.ts +6 -10
- package/src/endpoints/billing.ts +17 -4
- package/src/endpoints/bulk-action.ts +7 -12
- package/src/endpoints/channels.ts +89 -0
- package/src/endpoints/chat-stream.ts +3 -4
- package/src/endpoints/chat.ts +7 -12
- package/src/endpoints/chatbot.ts +2 -2
- package/src/endpoints/client-intelligence.ts +4 -4
- package/src/endpoints/delete-account.ts +9 -16
- package/src/endpoints/email-stats.ts +2 -2
- package/src/endpoints/escalate.ts +2 -2
- package/src/endpoints/export-csv.ts +2 -2
- package/src/endpoints/export-data.ts +5 -8
- package/src/endpoints/import-conversation.ts +6 -10
- package/src/endpoints/index.ts +18 -1
- package/src/endpoints/invite-collaborator.ts +30 -15
- package/src/endpoints/invoice.ts +127 -0
- package/src/endpoints/kb-search.ts +2 -2
- package/src/endpoints/login.ts +12 -4
- package/src/endpoints/merge-clients.ts +14 -19
- package/src/endpoints/merge-tickets.ts +11 -18
- package/src/endpoints/oauth-google.ts +86 -39
- package/src/endpoints/pending-emails-process.ts +15 -25
- package/src/endpoints/process-digests.ts +93 -0
- package/src/endpoints/process-scheduled.ts +6 -10
- package/src/endpoints/process-snooze.ts +78 -0
- package/src/endpoints/purge-logs.ts +2 -2
- package/src/endpoints/resend-notification.ts +3 -4
- package/src/endpoints/round-robin-config.ts +3 -4
- package/src/endpoints/satisfaction.ts +21 -14
- package/src/endpoints/search.ts +5 -8
- package/src/endpoints/seed-kb.ts +3 -4
- package/src/endpoints/send-reminder.ts +6 -9
- package/src/endpoints/settings.ts +5 -2
- package/src/endpoints/signature.ts +3 -4
- package/src/endpoints/sla-check.ts +2 -2
- package/src/endpoints/split-ticket.ts +7 -12
- package/src/endpoints/statuses.ts +2 -2
- package/src/endpoints/ticket-feedback.ts +4 -6
- package/src/endpoints/ticket-synthesis.ts +2 -2
- package/src/endpoints/track-open.ts +16 -28
- package/src/endpoints/transfer-ticket.ts +21 -9
- package/src/endpoints/user-prefs.ts +3 -4
- package/src/hooks/automationRules.ts +84 -0
- package/src/hooks/checkSLA.ts +48 -16
- package/src/plugin.ts +6 -0
- package/src/portal/LiveChat.tsx +2 -63
- package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +10 -0
- package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +14 -2
- package/src/portal/auth/tickets/detail/MessageActions.tsx +37 -18
- package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +10 -0
- package/src/portal/auth/tickets/detail/TransferAndInviteActions.tsx +3 -2
- package/src/portal/auth/tickets/detail/page.tsx +2 -2
- package/src/portal/helpers.ts +46 -0
- package/src/portal/login/page.tsx +3 -8
- package/src/portal/types.ts +15 -0
- package/src/types/lucide-react.d.ts +34 -0
- package/src/types/payload-config.d.ts +10 -0
- package/src/utils/adminNotification.ts +2 -2
- package/src/utils/aiAgent.ts +147 -0
- package/src/utils/auth.ts +5 -4
- package/src/utils/channels.ts +36 -0
- package/src/utils/db.ts +73 -0
- package/src/utils/emailTemplate.ts +8 -1
- package/src/utils/fireWebhooks.ts +4 -6
- package/src/utils/generateTicketSynthesis.ts +7 -12
- package/src/utils/invoicePdf.ts +88 -0
- package/src/utils/notificationQueue.ts +36 -0
- package/src/utils/readSettings.ts +22 -6
- package/src/utils/sanitizeHtml.ts +57 -0
- package/src/utils/slugs.ts +6 -0
- package/src/utils/teamAccess.ts +27 -0
- package/src/utils/ticketAccess.ts +53 -0
- package/src/utils/webhookDispatcher.ts +4 -6
- package/src/views/BillingView/client.tsx +49 -37
- package/src/views/ChatView/client.tsx +4 -3
- package/src/views/CrmView/client.tsx +7 -7
- package/src/views/EmailTrackingView/client.tsx +7 -6
- package/src/views/ImportConversationView/client.tsx +4 -4
- package/src/views/LogsView/client.tsx +3 -2
- package/src/views/NewTicketView/client.tsx +1 -1
- package/src/views/PendingEmailsView/client.tsx +7 -6
- package/src/views/SupportDashboardView/client.tsx +21 -21
- package/src/views/TicketDetailView/NextActionItem.tsx +30 -0
- package/src/views/TicketDetailView/RewriteDropdown.tsx +63 -0
- package/src/views/TicketDetailView/client.tsx +54 -179
- package/src/views/TicketDetailView/constants.ts +23 -0
- package/src/views/TicketDetailView/helpers.ts +17 -0
- package/src/views/TicketDetailView/types.ts +10 -0
- package/src/views/TicketInboxView/client.tsx +16 -10
- package/src/views/TicketingSettingsView/client.tsx +90 -87
- package/src/views/TimeDashboardView/client.tsx +2 -2
- package/src/views/shared/StatusPill.tsx +24 -12
- package/src/views/shared/dateLocale.ts +5 -0
- package/src/views/shared/locales/en.json +708 -0
- package/src/views/shared/locales/fr.json +708 -0
|
@@ -32,15 +32,15 @@ interface PendingEmail {
|
|
|
32
32
|
|
|
33
33
|
type Tab = 'pending' | 'processed' | 'ignored'
|
|
34
34
|
|
|
35
|
-
function timeAgo(dateStr: string): string {
|
|
35
|
+
function timeAgo(dateStr: string, tFn: (k: string, vars?: Record<string, string | number>) => string): string {
|
|
36
36
|
const diff = Date.now() - new Date(dateStr).getTime()
|
|
37
37
|
const mins = Math.floor(diff / 60000)
|
|
38
|
-
if (mins < 1) return
|
|
39
|
-
if (mins < 60) return
|
|
38
|
+
if (mins < 1) return tFn('pendingEmails.timeAgo.justNow')
|
|
39
|
+
if (mins < 60) return tFn('pendingEmails.timeAgo.minutesAgo', { count: mins })
|
|
40
40
|
const hours = Math.floor(mins / 60)
|
|
41
|
-
if (hours < 24) return
|
|
41
|
+
if (hours < 24) return tFn('pendingEmails.timeAgo.hoursAgo', { count: hours })
|
|
42
42
|
const days = Math.floor(hours / 24)
|
|
43
|
-
return
|
|
43
|
+
return tFn('pendingEmails.timeAgo.daysAgo', { count: days })
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function TicketSearchModal({
|
|
@@ -333,6 +333,7 @@ function EmailCard({
|
|
|
333
333
|
onProcess: (action: 'create_ticket' | 'add_to_ticket' | 'ignore', ticketId?: number, clientId?: number) => void
|
|
334
334
|
processing: boolean
|
|
335
335
|
}) {
|
|
336
|
+
const { t } = useTranslation()
|
|
336
337
|
const [expanded, setExpanded] = useState(false)
|
|
337
338
|
const [showLinkModal, setShowLinkModal] = useState(false)
|
|
338
339
|
const [showClientPicker, setShowClientPicker] = useState(false)
|
|
@@ -364,7 +365,7 @@ function EmailCard({
|
|
|
364
365
|
</div>
|
|
365
366
|
<div className={styles.cardSubject}>{email.subject}</div>
|
|
366
367
|
<div className={styles.cardMeta}>
|
|
367
|
-
<span>{timeAgo(email.createdAt)}</span>
|
|
368
|
+
<span>{timeAgo(email.createdAt, t)}</span>
|
|
368
369
|
{attachmentCount > 0 && (
|
|
369
370
|
<span className={styles.attachment}>
|
|
370
371
|
<Paperclip size={12} /> {attachmentCount} PJ
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React, { useState, useEffect, useCallback, useMemo } from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
5
|
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation'
|
|
6
|
+
import { DATE_LOCALE } from '../shared/dateLocale'
|
|
6
7
|
import styles from '../../styles/SupportDashboard.module.scss'
|
|
7
8
|
|
|
8
9
|
// ─── Types ──────────────────────────────────────────────────
|
|
@@ -14,6 +15,7 @@ interface Stats {
|
|
|
14
15
|
byCategory: Record<string, number>
|
|
15
16
|
createdLast7Days: number
|
|
16
17
|
createdLast30Days: number
|
|
18
|
+
volumeByDay?: Array<{ date: string; count: number }>
|
|
17
19
|
avgResponseTimeHours: number | null
|
|
18
20
|
avgResolutionTimeHours: number | null
|
|
19
21
|
totalTimeMinutes: number
|
|
@@ -92,6 +94,7 @@ function StatCard({ label, value, trend, accentColor, onClick }: {
|
|
|
92
94
|
accentColor?: string
|
|
93
95
|
onClick?: () => void
|
|
94
96
|
}) {
|
|
97
|
+
const { t } = useTranslation()
|
|
95
98
|
const style = accentColor ? { '--stat-accent': accentColor } as React.CSSProperties : undefined
|
|
96
99
|
const Tag = onClick ? 'button' : 'div'
|
|
97
100
|
|
|
@@ -114,7 +117,7 @@ function StatCard({ label, value, trend, accentColor, onClick }: {
|
|
|
114
117
|
)}
|
|
115
118
|
{trend && trend.dir === 'neutral' && (
|
|
116
119
|
<div className={`${styles.statTrend} ${styles.neutral}`}>
|
|
117
|
-
|
|
120
|
+
{t('dashboard.stable')}
|
|
118
121
|
</div>
|
|
119
122
|
)}
|
|
120
123
|
</Tag>
|
|
@@ -144,6 +147,7 @@ function VolumeChart({ data }: { data: { label: string; value: number }[] }) {
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
function CSATRing({ score, count }: { score: number; count: number }) {
|
|
150
|
+
const { t } = useTranslation()
|
|
147
151
|
// Ring progress: score is out of 5
|
|
148
152
|
const pct = score > 0 ? (score / 5) * 100 : 0
|
|
149
153
|
const radius = 42
|
|
@@ -171,10 +175,10 @@ function CSATRing({ score, count }: { score: number; count: number }) {
|
|
|
171
175
|
</div>
|
|
172
176
|
<div className={styles.csatMeta}>
|
|
173
177
|
<div className={styles.csatLabel}>
|
|
174
|
-
{score > 0 ? `${score.toFixed(1)} / 5` : '
|
|
178
|
+
{score > 0 ? `${score.toFixed(1)} / 5` : t('dashboard.csat.noData')}
|
|
175
179
|
</div>
|
|
176
180
|
<div className={styles.csatSub}>
|
|
177
|
-
{count > 0 ?
|
|
181
|
+
{count > 0 ? t('dashboard.csat.reviews', { count }) : t('dashboard.csat.noReviews')}
|
|
178
182
|
</div>
|
|
179
183
|
</div>
|
|
180
184
|
</div>
|
|
@@ -195,6 +199,7 @@ function formatSlaTime(minutes: number): string {
|
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
function SlaSection() {
|
|
202
|
+
const { t } = useTranslation()
|
|
198
203
|
const [sla, setSla] = useState<SlaData | null>(null)
|
|
199
204
|
const [slaLoading, setSlaLoading] = useState(true)
|
|
200
205
|
|
|
@@ -216,8 +221,8 @@ function SlaSection() {
|
|
|
216
221
|
if (slaLoading) {
|
|
217
222
|
return (
|
|
218
223
|
<div className={styles.slaSection}>
|
|
219
|
-
<h2 className={styles.slaSectionTitle}>
|
|
220
|
-
<div className={styles.slaLoading}>
|
|
224
|
+
<h2 className={styles.slaSectionTitle}>{t('dashboard.slaTitle')}</h2>
|
|
225
|
+
<div className={styles.slaLoading}>{t('dashboard.sla.loading')}</div>
|
|
221
226
|
</div>
|
|
222
227
|
)
|
|
223
228
|
}
|
|
@@ -230,7 +235,7 @@ function SlaSection() {
|
|
|
230
235
|
|
|
231
236
|
return (
|
|
232
237
|
<div className={styles.slaSection}>
|
|
233
|
-
<h2 className={styles.slaSectionTitle}>
|
|
238
|
+
<h2 className={styles.slaSectionTitle}>{t('dashboard.slaTitle')}</h2>
|
|
234
239
|
<div className={styles.slaGrid}>
|
|
235
240
|
{/* Breached */}
|
|
236
241
|
<div className={`${styles.slaCard} ${styles.slaBreach}`}>
|
|
@@ -241,14 +246,14 @@ function SlaSection() {
|
|
|
241
246
|
<line x1="12" y1="9" x2="12" y2="13" />
|
|
242
247
|
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
243
248
|
</svg>
|
|
244
|
-
|
|
249
|
+
{t('dashboard.sla.breached')}
|
|
245
250
|
</h3>
|
|
246
251
|
<span className={`${styles.slaCount} ${styles.slaCountBreach}`}>
|
|
247
252
|
{sla.breached.length}
|
|
248
253
|
</span>
|
|
249
254
|
</div>
|
|
250
255
|
{sla.breached.length === 0 ? (
|
|
251
|
-
<div className={styles.slaEmpty}>
|
|
256
|
+
<div className={styles.slaEmpty}>{t('dashboard.sla.noBreached')}</div>
|
|
252
257
|
) : (
|
|
253
258
|
<ul className={styles.slaList}>
|
|
254
259
|
{sla.breached.map((ticket) => {
|
|
@@ -284,14 +289,14 @@ function SlaSection() {
|
|
|
284
289
|
<circle cx="12" cy="12" r="10" />
|
|
285
290
|
<polyline points="12 6 12 12 16 14" />
|
|
286
291
|
</svg>
|
|
287
|
-
|
|
292
|
+
{t('dashboard.sla.atRisk')}
|
|
288
293
|
</h3>
|
|
289
294
|
<span className={`${styles.slaCount} ${styles.slaCountRisk}`}>
|
|
290
295
|
{sla.atRisk.length}
|
|
291
296
|
</span>
|
|
292
297
|
</div>
|
|
293
298
|
{sla.atRisk.length === 0 ? (
|
|
294
|
-
<div className={styles.slaEmpty}>
|
|
299
|
+
<div className={styles.slaEmpty}>{t('dashboard.sla.noAtRisk')}</div>
|
|
295
300
|
) : (
|
|
296
301
|
<ul className={styles.slaList}>
|
|
297
302
|
{sla.atRisk.map((ticket) => {
|
|
@@ -313,7 +318,7 @@ function SlaSection() {
|
|
|
313
318
|
<span className={styles.slaItemSubject}>{ticket.subject}</span>
|
|
314
319
|
</div>
|
|
315
320
|
<span className={`${styles.slaItemTime} ${styles.slaTimeRisk}`}>
|
|
316
|
-
{formatSlaTime(remainingMin)}
|
|
321
|
+
{t('dashboard.sla.remaining', { time: formatSlaTime(remainingMin) })}
|
|
317
322
|
</span>
|
|
318
323
|
</li>
|
|
319
324
|
)
|
|
@@ -372,17 +377,12 @@ export const SupportDashboardClient: React.FC = () => {
|
|
|
372
377
|
return () => window.removeEventListener('focus', onFocus)
|
|
373
378
|
}, [fetchData, sessionExpired])
|
|
374
379
|
|
|
375
|
-
//
|
|
380
|
+
// Real daily volume for the last 7 days (from admin-stats.volumeByDay).
|
|
376
381
|
const volumeData = useMemo(() => {
|
|
377
|
-
if (!stats) return []
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const base = Math.max(Math.floor(avg / 7), 0)
|
|
382
|
-
return days.map((label, i) => ({
|
|
383
|
-
label,
|
|
384
|
-
// Simple deterministic variation based on index
|
|
385
|
-
value: Math.max(base + ((i * 3 + 1) % 5) - 2, 0),
|
|
382
|
+
if (!stats?.volumeByDay?.length) return []
|
|
383
|
+
return stats.volumeByDay.map((d) => ({
|
|
384
|
+
label: new Date(d.date).toLocaleDateString(DATE_LOCALE, { weekday: 'short' }),
|
|
385
|
+
value: d.count,
|
|
386
386
|
}))
|
|
387
387
|
}, [stats])
|
|
388
388
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
import s from '../../styles/TicketDetail.module.scss'
|
|
5
|
+
|
|
6
|
+
const labelMap: Record<string, string> = { now: 'Maintenant', today: "Aujourd'hui", 'this-week': 'Cette semaine' }
|
|
7
|
+
|
|
8
|
+
export function NextActionItem({ action, index, onToggle }: { action: any; index: number; onToggle: (done: boolean) => Promise<void> }) {
|
|
9
|
+
const [pending, setPending] = useState(false)
|
|
10
|
+
const done = !!action.done
|
|
11
|
+
return (
|
|
12
|
+
<li className={s.aiCardNextAction}>
|
|
13
|
+
<button
|
|
14
|
+
type="button"
|
|
15
|
+
className={`${s.aiCardNextCheck} ${done ? s.aiCardNextCheckDone : ''}`}
|
|
16
|
+
onClick={async () => {
|
|
17
|
+
setPending(true)
|
|
18
|
+
try { await onToggle(!done) } finally { setPending(false) }
|
|
19
|
+
}}
|
|
20
|
+
aria-pressed={done}
|
|
21
|
+
aria-label={done ? 'Marquer non fait' : 'Marquer fait'}
|
|
22
|
+
disabled={pending}
|
|
23
|
+
>
|
|
24
|
+
{done && <span aria-hidden>✓</span>}
|
|
25
|
+
</button>
|
|
26
|
+
<span className={`${s.aiCardNextLabel} ${done ? s.aiCardNextLabelDone : ''}`}>{action.label}</span>
|
|
27
|
+
<span className={s.aiCardNextWhen}>{labelMap[action.priority] || action.priority}</span>
|
|
28
|
+
</li>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
4
|
+
import { REWRITE_STYLES } from './constants'
|
|
5
|
+
|
|
6
|
+
export const RewriteDropdown: React.FC<{
|
|
7
|
+
disabled: boolean
|
|
8
|
+
loading: boolean
|
|
9
|
+
onSelect: (style: string) => void
|
|
10
|
+
toolbarBtnClass?: string
|
|
11
|
+
}> = ({ disabled, loading, onSelect, toolbarBtnClass }) => {
|
|
12
|
+
const [open, setOpen] = useState(false)
|
|
13
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!open) return
|
|
17
|
+
const close = (e: MouseEvent) => {
|
|
18
|
+
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
|
|
19
|
+
}
|
|
20
|
+
document.addEventListener('mousedown', close)
|
|
21
|
+
return () => document.removeEventListener('mousedown', close)
|
|
22
|
+
}, [open])
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
className={toolbarBtnClass}
|
|
29
|
+
onClick={() => setOpen(!open)}
|
|
30
|
+
disabled={disabled}
|
|
31
|
+
style={{ fontSize: 11, fontWeight: 700, padding: '4px 10px', width: 'auto', display: 'flex', alignItems: 'center', gap: 4 }}
|
|
32
|
+
>
|
|
33
|
+
{loading ? '...' : '✏️ Reformuler'}
|
|
34
|
+
{!loading && <span style={{ fontSize: 9, opacity: 0.6 }}>▼</span>}
|
|
35
|
+
</button>
|
|
36
|
+
{open && !disabled && !loading && (
|
|
37
|
+
<div style={{
|
|
38
|
+
position: 'absolute', bottom: '100%', left: 0, marginBottom: 4,
|
|
39
|
+
background: '#fff', border: '1px solid #e5e7eb', borderRadius: 8,
|
|
40
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.12)', zIndex: 100, minWidth: 180, overflow: 'hidden',
|
|
41
|
+
}}>
|
|
42
|
+
{REWRITE_STYLES.map((style) => (
|
|
43
|
+
<button
|
|
44
|
+
key={style.id}
|
|
45
|
+
type="button"
|
|
46
|
+
onClick={() => { setOpen(false); onSelect(style.id) }}
|
|
47
|
+
style={{
|
|
48
|
+
display: 'flex', flexDirection: 'column', width: '100%', padding: '8px 12px',
|
|
49
|
+
border: 'none', background: 'transparent', cursor: 'pointer', textAlign: 'left',
|
|
50
|
+
borderBottom: '1px solid #f3f4f6',
|
|
51
|
+
}}
|
|
52
|
+
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = '#f9fafb' }}
|
|
53
|
+
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = 'transparent' }}
|
|
54
|
+
>
|
|
55
|
+
<span style={{ fontSize: 12, fontWeight: 600 }}>{style.label}</span>
|
|
56
|
+
<span style={{ fontSize: 10, color: '#9ca3af' }}>{style.desc}</span>
|
|
57
|
+
</button>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|