@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.
Files changed (193) hide show
  1. package/README.md +347 -435
  2. package/dist/components/TicketConversation/RewriteDropdown.cjs +98 -0
  3. package/dist/components/TicketConversation/RewriteDropdown.js +93 -0
  4. package/dist/components/TicketConversation/SkeletonText.cjs +21 -0
  5. package/dist/components/TicketConversation/SkeletonText.js +20 -0
  6. package/dist/components/TicketConversation/constants.cjs +27 -0
  7. package/dist/components/TicketConversation/constants.js +26 -1
  8. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +20 -0
  9. package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.js +19 -0
  10. package/dist/components/TicketConversation/index.cjs +10 -147
  11. package/dist/components/TicketConversation/index.js +4 -141
  12. package/dist/index.cjs +1604 -671
  13. package/dist/index.d.cts +3 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +1602 -671
  16. package/dist/views/BillingView/client.cjs +40 -54
  17. package/dist/views/BillingView/client.js +40 -54
  18. package/dist/views/ChatView/client.cjs +4 -3
  19. package/dist/views/ChatView/client.js +4 -3
  20. package/dist/views/CrmView/client.cjs +7 -7
  21. package/dist/views/CrmView/client.js +7 -7
  22. package/dist/views/EmailTrackingView/client.cjs +6 -5
  23. package/dist/views/EmailTrackingView/client.js +6 -5
  24. package/dist/views/ImportConversationView/client.cjs +4 -4
  25. package/dist/views/ImportConversationView/client.js +4 -4
  26. package/dist/views/LogsView/client.cjs +3 -2
  27. package/dist/views/LogsView/client.js +3 -2
  28. package/dist/views/NewTicketView/client.cjs +1 -1
  29. package/dist/views/NewTicketView/client.js +1 -1
  30. package/dist/views/PendingEmailsView/client.cjs +7 -6
  31. package/dist/views/PendingEmailsView/client.js +7 -6
  32. package/dist/views/SupportDashboardView/client.cjs +19 -22
  33. package/dist/views/SupportDashboardView/client.js +19 -22
  34. package/dist/views/TicketDetailView/NextActionItem.cjs +40 -0
  35. package/dist/views/TicketDetailView/NextActionItem.js +34 -0
  36. package/dist/views/TicketDetailView/RewriteDropdown.cjs +80 -0
  37. package/dist/views/TicketDetailView/RewriteDropdown.js +78 -0
  38. package/dist/views/TicketDetailView/client.cjs +59 -190
  39. package/dist/views/TicketDetailView/client.js +52 -183
  40. package/dist/views/TicketDetailView/constants.cjs +20 -0
  41. package/dist/views/TicketDetailView/constants.js +16 -0
  42. package/dist/views/TicketDetailView/helpers.cjs +23 -0
  43. package/dist/views/TicketDetailView/helpers.js +20 -0
  44. package/dist/views/TicketDetailView/types.cjs +2 -0
  45. package/dist/views/TicketDetailView/types.js +1 -0
  46. package/dist/views/TicketInboxView/client.cjs +14 -10
  47. package/dist/views/TicketInboxView/client.js +14 -10
  48. package/dist/views/TicketingSettingsView/client.cjs +101 -96
  49. package/dist/views/TicketingSettingsView/client.js +92 -88
  50. package/dist/views/TimeDashboardView/client.cjs +2 -2
  51. package/dist/views/TimeDashboardView/client.js +2 -2
  52. package/dist/views/shared/StatusPill.cjs +23 -12
  53. package/dist/views/shared/StatusPill.js +23 -12
  54. package/dist/views/shared/dateLocale.cjs +5 -0
  55. package/dist/views/shared/dateLocale.js +3 -0
  56. package/package.json +23 -15
  57. package/src/__tests__/generateTrackingToken.test.ts +53 -0
  58. package/src/__tests__/integration/ai-agent.test.ts +46 -0
  59. package/src/__tests__/integration/auth.test.ts +43 -0
  60. package/src/__tests__/integration/automation-rules.test.ts +68 -0
  61. package/src/__tests__/integration/buildTestPayload.ts +52 -0
  62. package/src/__tests__/integration/channels.test.ts +52 -0
  63. package/src/__tests__/integration/core-behaviors.test.ts +58 -0
  64. package/src/__tests__/integration/dashboard-volume.test.ts +31 -0
  65. package/src/__tests__/integration/digest.test.ts +48 -0
  66. package/src/__tests__/integration/invoice-pdf.test.ts +30 -0
  67. package/src/__tests__/integration/isolation.test.ts +81 -0
  68. package/src/__tests__/integration/mentions-invoice.test.ts +50 -0
  69. package/src/__tests__/integration/nps.test.ts +37 -0
  70. package/src/__tests__/integration/sanitization.test.ts +35 -0
  71. package/src/__tests__/integration/sla-pause.test.ts +39 -0
  72. package/src/__tests__/integration/smoke.test.ts +26 -0
  73. package/src/__tests__/integration/snooze.test.ts +68 -0
  74. package/src/__tests__/integration/teams.test.ts +38 -0
  75. package/src/__tests__/rateLimiter.test.ts +89 -0
  76. package/src/__tests__/sanitizeHtml.test.ts +165 -0
  77. package/src/__tests__/sla.test.ts +258 -0
  78. package/src/collections/AutomationRules.ts +66 -0
  79. package/src/collections/NotificationQueue.ts +30 -0
  80. package/src/collections/SatisfactionSurveys.ts +11 -2
  81. package/src/collections/SupportClients.ts +58 -1
  82. package/src/collections/SupportTeam.ts +27 -0
  83. package/src/collections/TicketActivityLog.ts +9 -4
  84. package/src/collections/TicketMessages.ts +129 -31
  85. package/src/collections/Tickets.ts +31 -73
  86. package/src/collections/index.ts +3 -0
  87. package/src/components/TicketConversation/RewriteDropdown.tsx +86 -0
  88. package/src/components/TicketConversation/SkeletonText.tsx +23 -0
  89. package/src/components/TicketConversation/constants.ts +29 -0
  90. package/src/components/TicketConversation/hooks/useDocumentIdFromUrl.ts +21 -0
  91. package/src/components/TicketConversation/index.tsx +4 -152
  92. package/src/endpoints/admin-chat-stream.ts +3 -4
  93. package/src/endpoints/admin-chat.ts +11 -20
  94. package/src/endpoints/admin-stats.ts +70 -32
  95. package/src/endpoints/ai-agent.ts +36 -0
  96. package/src/endpoints/apply-macro.ts +8 -14
  97. package/src/endpoints/auth-2fa.ts +12 -12
  98. package/src/endpoints/auto-close.ts +6 -10
  99. package/src/endpoints/billing.ts +17 -4
  100. package/src/endpoints/bulk-action.ts +7 -12
  101. package/src/endpoints/channels.ts +89 -0
  102. package/src/endpoints/chat-stream.ts +3 -4
  103. package/src/endpoints/chat.ts +7 -12
  104. package/src/endpoints/chatbot.ts +2 -2
  105. package/src/endpoints/client-intelligence.ts +4 -4
  106. package/src/endpoints/delete-account.ts +9 -16
  107. package/src/endpoints/email-stats.ts +2 -2
  108. package/src/endpoints/escalate.ts +2 -2
  109. package/src/endpoints/export-csv.ts +2 -2
  110. package/src/endpoints/export-data.ts +5 -8
  111. package/src/endpoints/import-conversation.ts +6 -10
  112. package/src/endpoints/index.ts +18 -1
  113. package/src/endpoints/invite-collaborator.ts +30 -15
  114. package/src/endpoints/invoice.ts +127 -0
  115. package/src/endpoints/kb-search.ts +2 -2
  116. package/src/endpoints/login.ts +12 -4
  117. package/src/endpoints/merge-clients.ts +14 -19
  118. package/src/endpoints/merge-tickets.ts +11 -18
  119. package/src/endpoints/oauth-google.ts +86 -39
  120. package/src/endpoints/pending-emails-process.ts +15 -25
  121. package/src/endpoints/process-digests.ts +93 -0
  122. package/src/endpoints/process-scheduled.ts +6 -10
  123. package/src/endpoints/process-snooze.ts +78 -0
  124. package/src/endpoints/purge-logs.ts +2 -2
  125. package/src/endpoints/resend-notification.ts +3 -4
  126. package/src/endpoints/round-robin-config.ts +3 -4
  127. package/src/endpoints/satisfaction.ts +21 -14
  128. package/src/endpoints/search.ts +5 -8
  129. package/src/endpoints/seed-kb.ts +3 -4
  130. package/src/endpoints/send-reminder.ts +6 -9
  131. package/src/endpoints/settings.ts +5 -2
  132. package/src/endpoints/signature.ts +3 -4
  133. package/src/endpoints/sla-check.ts +2 -2
  134. package/src/endpoints/split-ticket.ts +7 -12
  135. package/src/endpoints/statuses.ts +2 -2
  136. package/src/endpoints/ticket-feedback.ts +4 -6
  137. package/src/endpoints/ticket-synthesis.ts +2 -2
  138. package/src/endpoints/track-open.ts +16 -28
  139. package/src/endpoints/transfer-ticket.ts +21 -9
  140. package/src/endpoints/user-prefs.ts +3 -4
  141. package/src/hooks/automationRules.ts +84 -0
  142. package/src/hooks/checkSLA.ts +48 -16
  143. package/src/plugin.ts +6 -0
  144. package/src/portal/LiveChat.tsx +2 -63
  145. package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +10 -0
  146. package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +14 -2
  147. package/src/portal/auth/tickets/detail/MessageActions.tsx +37 -18
  148. package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +10 -0
  149. package/src/portal/auth/tickets/detail/TransferAndInviteActions.tsx +3 -2
  150. package/src/portal/auth/tickets/detail/page.tsx +2 -2
  151. package/src/portal/helpers.ts +46 -0
  152. package/src/portal/login/page.tsx +3 -8
  153. package/src/portal/types.ts +15 -0
  154. package/src/types/lucide-react.d.ts +34 -0
  155. package/src/types/payload-config.d.ts +10 -0
  156. package/src/utils/adminNotification.ts +2 -2
  157. package/src/utils/aiAgent.ts +147 -0
  158. package/src/utils/auth.ts +5 -4
  159. package/src/utils/channels.ts +36 -0
  160. package/src/utils/db.ts +73 -0
  161. package/src/utils/emailTemplate.ts +8 -1
  162. package/src/utils/fireWebhooks.ts +4 -6
  163. package/src/utils/generateTicketSynthesis.ts +7 -12
  164. package/src/utils/invoicePdf.ts +88 -0
  165. package/src/utils/notificationQueue.ts +36 -0
  166. package/src/utils/readSettings.ts +22 -6
  167. package/src/utils/sanitizeHtml.ts +57 -0
  168. package/src/utils/slugs.ts +6 -0
  169. package/src/utils/teamAccess.ts +27 -0
  170. package/src/utils/ticketAccess.ts +53 -0
  171. package/src/utils/webhookDispatcher.ts +4 -6
  172. package/src/views/BillingView/client.tsx +49 -37
  173. package/src/views/ChatView/client.tsx +4 -3
  174. package/src/views/CrmView/client.tsx +7 -7
  175. package/src/views/EmailTrackingView/client.tsx +7 -6
  176. package/src/views/ImportConversationView/client.tsx +4 -4
  177. package/src/views/LogsView/client.tsx +3 -2
  178. package/src/views/NewTicketView/client.tsx +1 -1
  179. package/src/views/PendingEmailsView/client.tsx +7 -6
  180. package/src/views/SupportDashboardView/client.tsx +21 -21
  181. package/src/views/TicketDetailView/NextActionItem.tsx +30 -0
  182. package/src/views/TicketDetailView/RewriteDropdown.tsx +63 -0
  183. package/src/views/TicketDetailView/client.tsx +54 -179
  184. package/src/views/TicketDetailView/constants.ts +23 -0
  185. package/src/views/TicketDetailView/helpers.ts +17 -0
  186. package/src/views/TicketDetailView/types.ts +10 -0
  187. package/src/views/TicketInboxView/client.tsx +16 -10
  188. package/src/views/TicketingSettingsView/client.tsx +90 -87
  189. package/src/views/TimeDashboardView/client.tsx +2 -2
  190. package/src/views/shared/StatusPill.tsx +24 -12
  191. package/src/views/shared/dateLocale.ts +5 -0
  192. package/src/views/shared/locales/en.json +708 -0
  193. 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
- const FEATURE_LIST: FeatureConfig[] = [
183
- // Core
184
- { key: 'canned', label: 'Réponses rapides', description: 'Templates de réponses pré-enregistrées avec variables dynamiques', category: 'core' },
185
- { key: 'scheduledReplies', label: 'Réponses programmées', description: 'Envoyer une réponse à une date/heure future', category: 'core' },
186
- { key: 'activityLog', label: 'Journal d\'activité', description: 'Timeline des actions sur chaque ticket (changements de statut, assignation...)', category: 'core' },
187
- // Communication
188
- { key: 'emailTracking', label: 'Suivi des emails', description: 'Tracking d\'envoi et d\'ouverture des notifications email', category: 'communication' },
189
- { key: 'chat', label: 'Live Chat', description: 'Chat en temps réel avec conversion en ticket', category: 'communication' },
190
- { key: 'externalMessages', label: 'Messages externes', description: 'Ajouter manuellement des messages reçus par email, SMS, WhatsApp...', category: 'communication' },
191
- // Productivity
192
- { key: 'ai', label: 'Intelligence Artificielle', description: 'Analyse de sentiment, synthèse, suggestion de réponse, reformulation', category: 'productivity' },
193
- { key: 'timeTracking', label: 'Suivi du temps', description: 'Timer, entrées manuelles, facturation', category: 'productivity' },
194
- { key: 'satisfaction', label: 'Enquêtes satisfaction', description: 'Score CSAT après résolution du ticket', category: 'productivity' },
195
- // Advanced
196
- { key: 'merge', label: 'Fusion de tickets', description: 'Combiner deux tickets en un seul', category: 'advanced' },
197
- { key: 'splitTicket', label: 'Extraction de message', description: 'Extraire un message en nouveau ticket lié', category: 'advanced' },
198
- { key: 'snooze', label: 'Snooze', description: 'Masquer temporairement un ticket et rappel automatique', category: 'advanced' },
199
- { key: 'clientHistory', label: 'Historique client', description: 'Tickets passés, projets et notes internes du client', category: 'advanced' },
200
- ]
201
-
202
- const CATEGORIES = [
203
- { key: 'core', label: 'Fonctionnalités de base', color: V.blue },
204
- { key: 'communication', label: 'Communication', color: V.green },
205
- { key: 'productivity', label: 'Productivité', color: V.amber },
206
- { key: 'advanced', label: 'Avancé', color: '#7c3aed' },
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="Configuration Email"
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
- Parametres d&apos;envoi des notifications email. L&apos;adresse SMTP est configuree via les variables d&apos;environnement du serveur.
474
+ {t('settingsView.emailDescription')}
469
475
  </p>
470
476
 
471
- <FieldRow label="Adresse expediteur" description="Adresse email affichee dans le champ From">
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="Nom expediteur" description="Nom affiche a cote de l'adresse email">
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="Adresse Reply-To" description="Si differente de l'adresse expediteur">
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="Serveur SMTP" description="Configure via variables d'environnement">
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="Port SMTP">
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="Intelligence Artificielle"
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' }}>Active</span>
534
- : <span className={ts.badge} style={{ backgroundColor: '#fee2e2', color: '#991b1b' }}>Inactive</span>
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
- Configurez le fournisseur d&apos;IA et activez/désactivez chaque fonctionnalité indépendamment.
539
- Les fonctionnalités IA nécessitent que le flag &quot;Intelligence Artificielle&quot; soit actif dans la section précédente.
544
+ {t('settingsView.aiDescription')}
540
545
  </p>
541
546
 
542
- <FieldRow label="Fournisseur" description="Service d'IA utilise pour l'analyse">
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="Cle API" description="Cle secrete du fournisseur (non stockee en clair)">
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 ? 'Masquer' : 'Afficher'}
575
+ {showApiKey ? t('settingsView.hideKey') : t('settingsView.showKey')}
571
576
  </button>
572
577
  </div>
573
578
  </FieldRow>
574
579
  )}
575
580
 
576
- <FieldRow label="Modele" description="Nom du modele a utiliser">
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
- Fonctionnalités IA individuelles
594
+ {t('settingsView.aiSubFeatures')}
590
595
  </p>
591
596
 
592
597
  {([
593
- { key: 'enableSentiment' as const, label: 'Analyse de sentiment', desc: 'Detecte le niveau de frustration ou satisfaction du client' },
594
- { key: 'enableSynthesis' as const, label: 'Synthese automatique', desc: 'Resume les conversations longues en quelques phrases' },
595
- { key: 'enableSuggestion' as const, label: 'Suggestion de reponse', desc: 'Propose un brouillon de reponse base sur le contexte' },
596
- { key: 'enableRewrite' as const, label: 'Reformulation', desc: 'Reformule un message pour le rendre plus professionnel' },
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="SLA (Accords de niveau de service)"
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
- Definissez les delais de reponse et de resolution attendus. Ces seuils sont utilises pour le suivi de performance et les alertes d&apos;escalade.
628
+ {t('settingsView.slaDescription')}
624
629
  </p>
625
630
 
626
- <FieldRow label="Premiere reponse" description="Délai maximum en minutes (défaut : 120 = 2h)">
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="Résolution" description="Délai maximum en minutes (défaut : 1440 = 24h)">
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}>Heures ouvrables uniquement</span>
669
+ <span className={ts.inlineLabel}>{t('settingsView.businessHoursOnly')}</span>
665
670
  <div className={ts.inlineDesc}>
666
- Le decompte SLA est suspendu en dehors des heures de bureau (Lun-Ven, 9h-18h)
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="Email d'escalade" description="Adresse notifiee en cas de depassement SLA">
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="Fermeture automatique"
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
- Les tickets en attente client sans reponse seront automatiquement resolus apres le delai configure.
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
- Activer la fermeture automatique
714
+ {t('settingsView.autoCloseEnable')}
711
715
  </span>
712
716
  </div>
713
717
 
714
718
  {settings.autoClose.enabled && (
715
719
  <>
716
- <FieldRow label="Delai avant fermeture" description="Nombre de jours sans reponse du client">
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}>jours</span>
730
+ <span className={ts.slaHint}>{t('settingsView.days')}</span>
727
731
  </div>
728
732
  </FieldRow>
729
733
 
730
- <FieldRow label="Rappel avant fermeture" description="Email de rappel envoye X jours avant">
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
- jours avant (rappel a J-{settings.autoClose.reminderDaysBefore})
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' }}>Mes preferences</div>
764
+ <div style={{ fontWeight: 700, fontSize: 15, color: '#1e293b' }}>{t('settingsView.myPreferences')}</div>
761
765
  <div style={{ fontSize: 13, color: '#64748b', marginTop: 2 }}>
762
- Ces reglages sont propres a votre compte et ne s&apos;appliquent qu&apos;a vous.
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="Langue et localisation"
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
- Langue de l&apos;interface du module de support et des notifications email envoyees aux clients.
780
+ {t('settingsView.localeDescription')}
777
781
  </p>
778
782
 
779
- <FieldRow label="Langue" description="Langue principale du module">
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">Francais</option>
786
- <option value="en">English</option>
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="Signature email"
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
- Signature ajoutee automatiquement en bas de chaque reponse email envoyee au client.
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="Cordialement,&#10;L'equipe ConsilioWEB"
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="Purge des logs" icon={<Settings size={18} />} color="#ef4444" defaultOpen={false}>
818
+ <CollapsibleSection title={t('settingsView.purgeTitle')} icon={<Settings size={18} />} color="#ef4444" defaultOpen={false}>
816
819
  <p className={ts.sectionDescription}>
817
- Supprimez les anciens logs pour libérer de l&apos;espace. Cette action est irréversible.
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' ? 'Logs Email' : 'Logs Auth'}
826
+ {col === 'email-logs' ? t('settingsView.purgeEmailLogs') : t('settingsView.purgeAuthLogs')}
824
827
  </span>
825
828
  <div className={ts.purgeButtons}>
826
829
  {[
827
- { label: '7 jours', days: 7 },
828
- { label: '30 jours', days: 30 },
829
- { label: '90 jours', days: 90 },
830
- { label: 'Tout', days: 0 },
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(`Supprimer les logs ${col} de plus de ${opt.days || 'tous les'} jours ?`)) return
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} log(s) supprimé(s)`) }
839
- } catch { alert('Erreur') }
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
- Réinitialiser tout
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 ? 'Sauvegarde...' : saved ? '\u2713 Sauvegardé' : 'Sauvegarder les modifications'}
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] : 'Sans date'
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 || 'Sans projet'
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 VARIANT_MAP: Record<string, { cls: string; defaultLabel: string }> = {
22
- open: { cls: s.open, defaultLabel: 'Ouvert' },
23
- pending: { cls: s.pending, defaultLabel: 'En attente' },
24
- waiting_client: { cls: s.pending, defaultLabel: 'Attente client' },
25
- resolved: { cls: s.resolved, defaultLabel: 'Résolu' },
26
- closed: { cls: s.neutral, defaultLabel: 'Fermé' },
27
- escalated: { cls: s.escalated, defaultLabel: 'Escaladé' },
28
- 'sla-breach': { cls: s.breach, defaultLabel: 'SLA dépassé' },
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 v = VARIANT_MAP[status] || VARIANT_MAP.open
33
- const text = label ?? v.defaultLabel
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, v.cls, className].filter(Boolean).join(' ')}
48
+ className={[s.pill, cls, className].filter(Boolean).join(' ')}
37
49
  role="status"
38
- aria-label={`Statut : ${text}`}
50
+ aria-label={`${t('ticket.status.label')} : ${text}`}
39
51
  >
40
52
  <span className={s.dot} aria-hidden />
41
53
  {text}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Default date locale constant for formatting functions.
3
+ * Extracted from user preferences at runtime when available.
4
+ */
5
+ export const DATE_LOCALE = 'fr-FR'