@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
|
@@ -179,32 +179,36 @@ async function saveSignatureToAPI(signature: string): Promise<boolean> {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{ key: '
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
182
|
+
function getFeatureList(t: (key: string) => string): FeatureConfig[] {
|
|
183
|
+
return [
|
|
184
|
+
// Core
|
|
185
|
+
{ key: 'canned', label: t('settingsView.features.canned.label'), description: t('settingsView.features.canned.description'), category: 'core' },
|
|
186
|
+
{ key: 'scheduledReplies', label: t('settingsView.features.scheduledReplies.label'), description: t('settingsView.features.scheduledReplies.description'), category: 'core' },
|
|
187
|
+
{ key: 'activityLog', label: t('settingsView.features.activityLog.label'), description: t('settingsView.features.activityLog.description'), category: 'core' },
|
|
188
|
+
// Communication
|
|
189
|
+
{ key: 'emailTracking', label: t('settingsView.features.emailTracking.label'), description: t('settingsView.features.emailTracking.description'), category: 'communication' },
|
|
190
|
+
{ key: 'chat', label: t('settingsView.features.chat.label'), description: t('settingsView.features.chat.description'), category: 'communication' },
|
|
191
|
+
{ key: 'externalMessages', label: t('settingsView.features.externalMessages.label'), description: t('settingsView.features.externalMessages.description'), category: 'communication' },
|
|
192
|
+
// Productivity
|
|
193
|
+
{ key: 'ai', label: t('settingsView.features.ai.label'), description: t('settingsView.features.ai.description'), category: 'productivity' },
|
|
194
|
+
{ key: 'timeTracking', label: t('settingsView.features.timeTracking.label'), description: t('settingsView.features.timeTracking.description'), category: 'productivity' },
|
|
195
|
+
{ key: 'satisfaction', label: t('settingsView.features.satisfaction.label'), description: t('settingsView.features.satisfaction.description'), category: 'productivity' },
|
|
196
|
+
// Advanced
|
|
197
|
+
{ key: 'merge', label: t('settingsView.features.merge.label'), description: t('settingsView.features.merge.description'), category: 'advanced' },
|
|
198
|
+
{ key: 'splitTicket', label: t('settingsView.features.splitTicket.label'), description: t('settingsView.features.splitTicket.description'), category: 'advanced' },
|
|
199
|
+
{ key: 'snooze', label: t('settingsView.features.snooze.label'), description: t('settingsView.features.snooze.description'), category: 'advanced' },
|
|
200
|
+
{ key: 'clientHistory', label: t('settingsView.features.clientHistory.label'), description: t('settingsView.features.clientHistory.description'), category: 'advanced' },
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getCategories(t: (key: string) => string) {
|
|
205
|
+
return [
|
|
206
|
+
{ key: 'core', label: t('settingsView.categories.core'), color: V.blue },
|
|
207
|
+
{ key: 'communication', label: t('settingsView.categories.communication'), color: V.green },
|
|
208
|
+
{ key: 'productivity', label: t('settingsView.categories.productivity'), color: V.amber },
|
|
209
|
+
{ key: 'advanced', label: t('settingsView.categories.advanced'), color: '#7c3aed' },
|
|
210
|
+
]
|
|
211
|
+
}
|
|
208
212
|
|
|
209
213
|
/* ============================================
|
|
210
214
|
* Small reusable components
|
|
@@ -295,6 +299,8 @@ const FieldRow: React.FC<{
|
|
|
295
299
|
|
|
296
300
|
export const TicketingSettingsClient: React.FC = () => {
|
|
297
301
|
const { t } = useTranslation()
|
|
302
|
+
const FEATURE_LIST = React.useMemo(() => getFeatureList(t), [t])
|
|
303
|
+
const CATEGORIES = React.useMemo(() => getCategories(t), [t])
|
|
298
304
|
const [features, setFeatures] = useState<TicketingFeatures>(() => getFeatures())
|
|
299
305
|
const [settings, setSettings] = useState<AllSettings>(DEFAULT_SETTINGS)
|
|
300
306
|
const [signature, setSignature] = useState('')
|
|
@@ -459,16 +465,16 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
459
465
|
* SECTION 2 — Email Configuration
|
|
460
466
|
* ======================================== */}
|
|
461
467
|
<CollapsibleSection
|
|
462
|
-
title=
|
|
468
|
+
title={t('settingsView.emailConfig')}
|
|
463
469
|
icon={<Mail size={16} />}
|
|
464
470
|
color="#ea580c"
|
|
465
471
|
defaultOpen={false}
|
|
466
472
|
>
|
|
467
473
|
<p className={ts.sectionDescription}>
|
|
468
|
-
|
|
474
|
+
{t('settingsView.emailDescription')}
|
|
469
475
|
</p>
|
|
470
476
|
|
|
471
|
-
<FieldRow label=
|
|
477
|
+
<FieldRow label={t('settingsView.senderAddress')} description={t('settingsView.senderAddressDesc')}>
|
|
472
478
|
<input
|
|
473
479
|
type="email"
|
|
474
480
|
value={settings.email.fromAddress}
|
|
@@ -478,7 +484,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
478
484
|
/>
|
|
479
485
|
</FieldRow>
|
|
480
486
|
|
|
481
|
-
<FieldRow label=
|
|
487
|
+
<FieldRow label={t('settingsView.senderName')} description={t('settingsView.senderNameDesc')}>
|
|
482
488
|
<input
|
|
483
489
|
type="text"
|
|
484
490
|
value={settings.email.fromName}
|
|
@@ -488,7 +494,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
488
494
|
/>
|
|
489
495
|
</FieldRow>
|
|
490
496
|
|
|
491
|
-
<FieldRow label=
|
|
497
|
+
<FieldRow label={t('settingsView.replyTo')} description={t('settingsView.replyToDesc')}>
|
|
492
498
|
<input
|
|
493
499
|
type="email"
|
|
494
500
|
value={settings.email.replyToAddress}
|
|
@@ -500,7 +506,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
500
506
|
|
|
501
507
|
<div className={ts.separator} />
|
|
502
508
|
|
|
503
|
-
<FieldRow label=
|
|
509
|
+
<FieldRow label={t('settingsView.smtpServer')} description={t('settingsView.smtpServerDesc')}>
|
|
504
510
|
<input
|
|
505
511
|
type="text"
|
|
506
512
|
value={smtpHost}
|
|
@@ -509,7 +515,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
509
515
|
/>
|
|
510
516
|
</FieldRow>
|
|
511
517
|
|
|
512
|
-
<FieldRow label=
|
|
518
|
+
<FieldRow label={t('settingsView.smtpPort')}>
|
|
513
519
|
<input
|
|
514
520
|
type="text"
|
|
515
521
|
value={smtpPort}
|
|
@@ -524,22 +530,21 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
524
530
|
* SECTION 3 — AI Configuration
|
|
525
531
|
* ======================================== */}
|
|
526
532
|
<CollapsibleSection
|
|
527
|
-
title=
|
|
533
|
+
title={t('settingsView.aiTitle')}
|
|
528
534
|
icon={<Bot size={16} />}
|
|
529
535
|
color="#7c3aed"
|
|
530
536
|
defaultOpen={false}
|
|
531
537
|
badge={
|
|
532
538
|
features.ai
|
|
533
|
-
? <span className={ts.badge} style={{ backgroundColor: '#dcfce7', color: '#166534' }}>
|
|
534
|
-
: <span className={ts.badge} style={{ backgroundColor: '#fee2e2', color: '#991b1b' }}>
|
|
539
|
+
? <span className={ts.badge} style={{ backgroundColor: '#dcfce7', color: '#166534' }}>{t('settingsView.aiBadgeActive')}</span>
|
|
540
|
+
: <span className={ts.badge} style={{ backgroundColor: '#fee2e2', color: '#991b1b' }}>{t('settingsView.aiBadgeInactive')}</span>
|
|
535
541
|
}
|
|
536
542
|
>
|
|
537
543
|
<p className={ts.sectionDescription}>
|
|
538
|
-
|
|
539
|
-
Les fonctionnalités IA nécessitent que le flag "Intelligence Artificielle" soit actif dans la section précédente.
|
|
544
|
+
{t('settingsView.aiDescription')}
|
|
540
545
|
</p>
|
|
541
546
|
|
|
542
|
-
<FieldRow label=
|
|
547
|
+
<FieldRow label={t('settingsView.aiProvider')} description={t('settingsView.aiProviderDesc')}>
|
|
543
548
|
<select
|
|
544
549
|
value={settings.ai.provider}
|
|
545
550
|
onChange={(e) => updateAI('provider', e.target.value as AISettings['provider'])}
|
|
@@ -553,7 +558,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
553
558
|
</FieldRow>
|
|
554
559
|
|
|
555
560
|
{settings.ai.provider !== 'ollama' && (
|
|
556
|
-
<FieldRow label=
|
|
561
|
+
<FieldRow label={t('settingsView.aiApiKey')} description={t('settingsView.aiApiKeyDesc')}>
|
|
557
562
|
<div className={ts.apiKeyRow}>
|
|
558
563
|
<input
|
|
559
564
|
type={showApiKey ? 'text' : 'password'}
|
|
@@ -567,13 +572,13 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
567
572
|
onClick={() => setShowApiKey(!showApiKey)}
|
|
568
573
|
className={ts.apiKeyToggle}
|
|
569
574
|
>
|
|
570
|
-
{showApiKey ? '
|
|
575
|
+
{showApiKey ? t('settingsView.hideKey') : t('settingsView.showKey')}
|
|
571
576
|
</button>
|
|
572
577
|
</div>
|
|
573
578
|
</FieldRow>
|
|
574
579
|
)}
|
|
575
580
|
|
|
576
|
-
<FieldRow label=
|
|
581
|
+
<FieldRow label={t('settingsView.aiModel')} description={t('settingsView.aiModelDesc')}>
|
|
577
582
|
<input
|
|
578
583
|
type="text"
|
|
579
584
|
value={settings.ai.model}
|
|
@@ -586,14 +591,14 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
586
591
|
<div className={ts.separator} />
|
|
587
592
|
|
|
588
593
|
<p className={ts.aiSubFeaturesLabel}>
|
|
589
|
-
|
|
594
|
+
{t('settingsView.aiSubFeatures')}
|
|
590
595
|
</p>
|
|
591
596
|
|
|
592
597
|
{([
|
|
593
|
-
{ key: 'enableSentiment' as const, label: '
|
|
594
|
-
{ key: 'enableSynthesis' as const, label: '
|
|
595
|
-
{ key: 'enableSuggestion' as const, label: '
|
|
596
|
-
{ key: 'enableRewrite' as const, label: '
|
|
598
|
+
{ key: 'enableSentiment' as const, label: t('settingsView.aiSentiment'), desc: t('settingsView.aiSentimentDesc') },
|
|
599
|
+
{ key: 'enableSynthesis' as const, label: t('settingsView.aiSynthesis'), desc: t('settingsView.aiSynthesisDesc') },
|
|
600
|
+
{ key: 'enableSuggestion' as const, label: t('settingsView.aiSuggestion'), desc: t('settingsView.aiSuggestionDesc') },
|
|
601
|
+
{ key: 'enableRewrite' as const, label: t('settingsView.aiRewrite'), desc: t('settingsView.aiRewriteDesc') },
|
|
597
602
|
]).map((item) => (
|
|
598
603
|
<div key={item.key} className={ts.aiToggleRow}>
|
|
599
604
|
<Toggle
|
|
@@ -614,16 +619,16 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
614
619
|
* SECTION 4 — SLA Configuration
|
|
615
620
|
* ======================================== */}
|
|
616
621
|
<CollapsibleSection
|
|
617
|
-
title=
|
|
622
|
+
title={t('settingsView.slaTitle')}
|
|
618
623
|
icon={<Clock size={16} />}
|
|
619
624
|
color="#0891b2"
|
|
620
625
|
defaultOpen={false}
|
|
621
626
|
>
|
|
622
627
|
<p className={ts.sectionDescription}>
|
|
623
|
-
|
|
628
|
+
{t('settingsView.slaDescription')}
|
|
624
629
|
</p>
|
|
625
630
|
|
|
626
|
-
<FieldRow label=
|
|
631
|
+
<FieldRow label={t('settingsView.slaFirstResponse')} description={t('settingsView.slaFirstResponseDesc')}>
|
|
627
632
|
<div className={ts.slaInline}>
|
|
628
633
|
<input
|
|
629
634
|
type="number"
|
|
@@ -633,12 +638,12 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
633
638
|
className={ts.numberInput}
|
|
634
639
|
/>
|
|
635
640
|
<span className={ts.slaHint}>
|
|
636
|
-
minutes ({Math.floor(settings.sla.firstResponseMinutes / 60)}h{String(settings.sla.firstResponseMinutes % 60).padStart(2, '0')})
|
|
641
|
+
{t('settingsView.minutes')} ({Math.floor(settings.sla.firstResponseMinutes / 60)}h{String(settings.sla.firstResponseMinutes % 60).padStart(2, '0')})
|
|
637
642
|
</span>
|
|
638
643
|
</div>
|
|
639
644
|
</FieldRow>
|
|
640
645
|
|
|
641
|
-
<FieldRow label=
|
|
646
|
+
<FieldRow label={t('settingsView.slaResolution')} description={t('settingsView.slaResolutionDesc')}>
|
|
642
647
|
<div className={ts.slaInline}>
|
|
643
648
|
<input
|
|
644
649
|
type="number"
|
|
@@ -648,7 +653,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
648
653
|
className={ts.numberInput}
|
|
649
654
|
/>
|
|
650
655
|
<span className={ts.slaHint}>
|
|
651
|
-
minutes ({Math.floor(settings.sla.resolutionMinutes / 60)}h{String(settings.sla.resolutionMinutes % 60).padStart(2, '0')})
|
|
656
|
+
{t('settingsView.minutes')} ({Math.floor(settings.sla.resolutionMinutes / 60)}h{String(settings.sla.resolutionMinutes % 60).padStart(2, '0')})
|
|
652
657
|
</span>
|
|
653
658
|
</div>
|
|
654
659
|
</FieldRow>
|
|
@@ -661,16 +666,16 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
661
666
|
size="sm"
|
|
662
667
|
/>
|
|
663
668
|
<div>
|
|
664
|
-
<span className={ts.inlineLabel}>
|
|
669
|
+
<span className={ts.inlineLabel}>{t('settingsView.businessHoursOnly')}</span>
|
|
665
670
|
<div className={ts.inlineDesc}>
|
|
666
|
-
|
|
671
|
+
{t('settingsView.businessHoursDesc')}
|
|
667
672
|
</div>
|
|
668
673
|
</div>
|
|
669
674
|
</div>
|
|
670
675
|
|
|
671
676
|
<div className={ts.separator} />
|
|
672
677
|
|
|
673
|
-
<FieldRow label=
|
|
678
|
+
<FieldRow label={t('settingsView.escalationEmail')} description={t('settingsView.escalationEmailDesc')}>
|
|
674
679
|
<input
|
|
675
680
|
type="email"
|
|
676
681
|
value={settings.sla.escalationEmail}
|
|
@@ -685,7 +690,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
685
690
|
* SECTION 5 — Auto-Close
|
|
686
691
|
* ======================================== */}
|
|
687
692
|
<CollapsibleSection
|
|
688
|
-
title=
|
|
693
|
+
title={t('settingsView.autoCloseTitle')}
|
|
689
694
|
icon={<Timer size={16} />}
|
|
690
695
|
color="#d97706"
|
|
691
696
|
defaultOpen={false}
|
|
@@ -696,8 +701,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
696
701
|
}
|
|
697
702
|
>
|
|
698
703
|
<p className={ts.sectionDescription}>
|
|
699
|
-
|
|
700
|
-
Un email de rappel est envoye avant la fermeture.
|
|
704
|
+
{t('settingsView.autoCloseDescription')}
|
|
701
705
|
</p>
|
|
702
706
|
|
|
703
707
|
<div className={ts.toggleRow} style={{ paddingBottom: 14 }}>
|
|
@@ -707,13 +711,13 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
707
711
|
color="#d97706"
|
|
708
712
|
/>
|
|
709
713
|
<span className={ts.inlineLabel}>
|
|
710
|
-
|
|
714
|
+
{t('settingsView.autoCloseEnable')}
|
|
711
715
|
</span>
|
|
712
716
|
</div>
|
|
713
717
|
|
|
714
718
|
{settings.autoClose.enabled && (
|
|
715
719
|
<>
|
|
716
|
-
<FieldRow label=
|
|
720
|
+
<FieldRow label={t('settingsView.autoCloseDelay')} description={t('settingsView.autoCloseDelayDesc')}>
|
|
717
721
|
<div className={ts.slaInline}>
|
|
718
722
|
<input
|
|
719
723
|
type="number"
|
|
@@ -723,11 +727,11 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
723
727
|
onChange={(e) => updateAutoClose('daysBeforeClose', parseInt(e.target.value) || 7)}
|
|
724
728
|
className={ts.numberInput}
|
|
725
729
|
/>
|
|
726
|
-
<span className={ts.slaHint}>
|
|
730
|
+
<span className={ts.slaHint}>{t('settingsView.days')}</span>
|
|
727
731
|
</div>
|
|
728
732
|
</FieldRow>
|
|
729
733
|
|
|
730
|
-
<FieldRow label=
|
|
734
|
+
<FieldRow label={t('settingsView.autoCloseReminder')} description={t('settingsView.autoCloseReminderDesc')}>
|
|
731
735
|
<div className={ts.slaInline}>
|
|
732
736
|
<input
|
|
733
737
|
type="number"
|
|
@@ -738,7 +742,7 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
738
742
|
className={ts.numberInput}
|
|
739
743
|
/>
|
|
740
744
|
<span className={ts.slaHint}>
|
|
741
|
-
|
|
745
|
+
{t('settingsView.autoCloseDaysBeforeReminder', { count: String(settings.autoClose.reminderDaysBefore) })}
|
|
742
746
|
</span>
|
|
743
747
|
</div>
|
|
744
748
|
</FieldRow>
|
|
@@ -757,9 +761,9 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
757
761
|
background: 'linear-gradient(135deg, #dbeafe 0%, #ede9fe 100%)',
|
|
758
762
|
border: '1px solid #c7d2fe',
|
|
759
763
|
}}>
|
|
760
|
-
<div style={{ fontWeight: 700, fontSize: 15, color: '#1e293b' }}>
|
|
764
|
+
<div style={{ fontWeight: 700, fontSize: 15, color: '#1e293b' }}>{t('settingsView.myPreferences')}</div>
|
|
761
765
|
<div style={{ fontSize: 13, color: '#64748b', marginTop: 2 }}>
|
|
762
|
-
|
|
766
|
+
{t('settingsView.myPreferencesDesc')}
|
|
763
767
|
</div>
|
|
764
768
|
</div>
|
|
765
769
|
|
|
@@ -767,23 +771,23 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
767
771
|
* SECTION 6 — Locale (per-user)
|
|
768
772
|
* ======================================== */}
|
|
769
773
|
<CollapsibleSection
|
|
770
|
-
title=
|
|
774
|
+
title={t('settingsView.localeTitle')}
|
|
771
775
|
icon={<Globe size={16} />}
|
|
772
776
|
color="#16a34a"
|
|
773
777
|
defaultOpen={false}
|
|
774
778
|
>
|
|
775
779
|
<p className={ts.sectionDescription}>
|
|
776
|
-
|
|
780
|
+
{t('settingsView.localeDescription')}
|
|
777
781
|
</p>
|
|
778
782
|
|
|
779
|
-
<FieldRow label=
|
|
783
|
+
<FieldRow label={t('settingsView.language')} description={t('settingsView.languageDesc')}>
|
|
780
784
|
<select
|
|
781
785
|
value={settings.locale.language}
|
|
782
786
|
onChange={(e) => updateLocale('language', e.target.value as 'fr' | 'en')}
|
|
783
787
|
className={ts.select}
|
|
784
788
|
>
|
|
785
|
-
<option value="fr">
|
|
786
|
-
<option value="en">
|
|
789
|
+
<option value="fr">{t('settingsView.french')}</option>
|
|
790
|
+
<option value="en">{t('settingsView.english')}</option>
|
|
787
791
|
</select>
|
|
788
792
|
</FieldRow>
|
|
789
793
|
</CollapsibleSection>
|
|
@@ -792,51 +796,50 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
792
796
|
* SECTION 7 — Email Signature
|
|
793
797
|
* ======================================== */}
|
|
794
798
|
<CollapsibleSection
|
|
795
|
-
title=
|
|
799
|
+
title={t('settingsView.signatureTitle')}
|
|
796
800
|
icon={<FileSignature size={16} />}
|
|
797
801
|
color="#6366f1"
|
|
798
802
|
defaultOpen={false}
|
|
799
803
|
>
|
|
800
804
|
<p className={ts.sectionDescription}>
|
|
801
|
-
|
|
802
|
-
Supporte le texte brut et le HTML basique.
|
|
805
|
+
{t('settingsView.signatureDescription')}
|
|
803
806
|
</p>
|
|
804
807
|
|
|
805
808
|
<textarea
|
|
806
809
|
value={signature}
|
|
807
810
|
onChange={(e) => { setSignature(e.target.value); setSaved(false) }}
|
|
808
|
-
placeholder=
|
|
811
|
+
placeholder={t('settingsView.signaturePlaceholder')}
|
|
809
812
|
rows={6}
|
|
810
813
|
className={ts.signatureTextarea}
|
|
811
814
|
/>
|
|
812
815
|
</CollapsibleSection>
|
|
813
816
|
|
|
814
817
|
{/* Purge logs section */}
|
|
815
|
-
<CollapsibleSection title=
|
|
818
|
+
<CollapsibleSection title={t('settingsView.purgeTitle')} icon={<Settings size={18} />} color="#ef4444" defaultOpen={false}>
|
|
816
819
|
<p className={ts.sectionDescription}>
|
|
817
|
-
|
|
820
|
+
{t('settingsView.purgeDescription')}
|
|
818
821
|
</p>
|
|
819
822
|
<div className={ts.purgeGroup}>
|
|
820
823
|
{['email-logs', 'auth-logs'].map((col) => (
|
|
821
824
|
<div key={col} className={ts.purgeCategory}>
|
|
822
825
|
<span className={ts.purgeCategoryLabel}>
|
|
823
|
-
{col === 'email-logs' ? '
|
|
826
|
+
{col === 'email-logs' ? t('settingsView.purgeEmailLogs') : t('settingsView.purgeAuthLogs')}
|
|
824
827
|
</span>
|
|
825
828
|
<div className={ts.purgeButtons}>
|
|
826
829
|
{[
|
|
827
|
-
{ label: '
|
|
828
|
-
{ label: '
|
|
829
|
-
{ label: '
|
|
830
|
-
{ label: '
|
|
830
|
+
{ label: t('settingsView.purge7days'), days: 7 },
|
|
831
|
+
{ label: t('settingsView.purge30days'), days: 30 },
|
|
832
|
+
{ label: t('settingsView.purge90days'), days: 90 },
|
|
833
|
+
{ label: t('settingsView.purgeAllLabel'), days: 0 },
|
|
831
834
|
].map((opt) => (
|
|
832
835
|
<button
|
|
833
836
|
key={opt.days}
|
|
834
837
|
onClick={async () => {
|
|
835
|
-
if (!window.confirm(
|
|
838
|
+
if (!window.confirm(t('settingsView.purgeConfirm', { collection: col, days: opt.days === 0 ? t('settingsView.purgeAllLabel') : String(opt.days) }))) return
|
|
836
839
|
try {
|
|
837
840
|
const res = await fetch(`/api/support/purge-logs?collection=${col}&days=${opt.days}`, { method: 'DELETE', credentials: 'include' })
|
|
838
|
-
if (res.ok) { const d = await res.json(); alert(`${d.purged}
|
|
839
|
-
} catch { alert('
|
|
841
|
+
if (res.ok) { const d = await res.json(); alert(`${d.purged} ${t('settingsView.purgeDone')}`) }
|
|
842
|
+
} catch { alert(t('import.errorTitle')) }
|
|
840
843
|
}}
|
|
841
844
|
style={{ ...btnStyle(opt.days === 0 ? '#ef4444' : 'var(--theme-elevation-500)', { small: true }), fontSize: 11 }}
|
|
842
845
|
>
|
|
@@ -852,14 +855,14 @@ export const TicketingSettingsClient: React.FC = () => {
|
|
|
852
855
|
{/* Bottom save bar */}
|
|
853
856
|
<div className={ts.bottomBar}>
|
|
854
857
|
<button onClick={handleReset} style={btnStyle('var(--theme-elevation-400)', { small: true })}>
|
|
855
|
-
|
|
858
|
+
{t('settingsView.resetAll')}
|
|
856
859
|
</button>
|
|
857
860
|
<button
|
|
858
861
|
onClick={handleSave}
|
|
859
862
|
disabled={saving}
|
|
860
863
|
style={btnStyle(saved ? V.green : V.blue, { small: true })}
|
|
861
864
|
>
|
|
862
|
-
{saving ? '
|
|
865
|
+
{saving ? t('settingsView.saving') : saved ? t('settingsView.saved2') : t('settingsView.saveChanges')}
|
|
863
866
|
</button>
|
|
864
867
|
</div>
|
|
865
868
|
</div>
|
|
@@ -84,14 +84,14 @@ export const TimeDashboardClient: React.FC = () => {
|
|
|
84
84
|
for (const entry of entries) {
|
|
85
85
|
let key: string
|
|
86
86
|
if (groupBy === 'day') {
|
|
87
|
-
key = entry.date ? entry.date.split('T')[0] : '
|
|
87
|
+
key = entry.date ? entry.date.split('T')[0] : t('timeDashboard.noDate')
|
|
88
88
|
} else if (groupBy === 'week') {
|
|
89
89
|
const d = new Date(entry.date)
|
|
90
90
|
key = `Semaine ${getWeekNumber(d)} (${MONTHS_FR[d.getMonth()]} ${d.getFullYear()})`
|
|
91
91
|
} else {
|
|
92
92
|
const ticket = typeof entry.ticket === 'object' ? entry.ticket : null
|
|
93
93
|
const project = ticket && typeof ticket.project === 'object' ? ticket.project : null
|
|
94
|
-
key = project?.name || '
|
|
94
|
+
key = project?.name || t('billing.noProject')
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
if (!map.has(key)) map.set(key, [])
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React from 'react'
|
|
4
|
+
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation'
|
|
4
5
|
import s from './StatusPill.module.scss'
|
|
5
6
|
|
|
6
7
|
export type TicketStatusVariant =
|
|
@@ -18,24 +19,35 @@ export interface StatusPillProps {
|
|
|
18
19
|
className?: string
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
open:
|
|
23
|
-
pending:
|
|
24
|
-
waiting_client:
|
|
25
|
-
resolved:
|
|
26
|
-
closed:
|
|
27
|
-
escalated:
|
|
28
|
-
'sla-breach':
|
|
22
|
+
const VARIANT_CLS: Record<string, string> = {
|
|
23
|
+
open: s.open,
|
|
24
|
+
pending: s.pending,
|
|
25
|
+
waiting_client: s.pending,
|
|
26
|
+
resolved: s.resolved,
|
|
27
|
+
closed: s.neutral,
|
|
28
|
+
escalated: s.escalated,
|
|
29
|
+
'sla-breach': s.breach,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const STATUS_I18N_KEY: Record<string, string> = {
|
|
33
|
+
open: 'ticket.status.open',
|
|
34
|
+
pending: 'ticket.status.pending',
|
|
35
|
+
waiting_client: 'ticket.status.waitingClient',
|
|
36
|
+
resolved: 'ticket.status.resolved',
|
|
37
|
+
closed: 'ticket.status.closed',
|
|
38
|
+
escalated: 'ticket.status.escalated',
|
|
39
|
+
'sla-breach': 'ticket.status.slaBreached',
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
export const StatusPill: React.FC<StatusPillProps> = ({ status, label, className }) => {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
43
|
+
const { t } = useTranslation()
|
|
44
|
+
const cls = VARIANT_CLS[status] || VARIANT_CLS.open
|
|
45
|
+
const text = label ?? (STATUS_I18N_KEY[status] ? t(STATUS_I18N_KEY[status]) : status)
|
|
34
46
|
return (
|
|
35
47
|
<span
|
|
36
|
-
className={[s.pill,
|
|
48
|
+
className={[s.pill, cls, className].filter(Boolean).join(' ')}
|
|
37
49
|
role="status"
|
|
38
|
-
aria-label={
|
|
50
|
+
aria-label={`${t('ticket.status.label')} : ${text}`}
|
|
39
51
|
>
|
|
40
52
|
<span className={s.dot} aria-hidden />
|
|
41
53
|
{text}
|