@consilioweb/payload-support 5.0.0 → 6.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 (116) hide show
  1. package/README.md +249 -22
  2. package/dist/components/RichTextEditor/index.js +1 -1
  3. package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
  4. package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
  5. package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
  6. package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
  7. package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
  8. package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
  9. package/dist/components/TicketConversation/components/QuickActions.js +10 -8
  10. package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
  11. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
  12. package/dist/components/TicketConversation/index.d.ts +10 -0
  13. package/dist/components/TicketConversation/index.js +25 -7
  14. package/dist/index.cjs +306 -60
  15. package/dist/index.d.cts +107 -38
  16. package/dist/index.d.ts +107 -38
  17. package/dist/index.js +300 -59
  18. package/dist/styles/BillingView.module.scss +11 -11
  19. package/dist/styles/ChatView.module.scss +7 -7
  20. package/dist/styles/CommandPalette.module.scss +1 -1
  21. package/dist/styles/CrmView.module.scss +9 -9
  22. package/dist/styles/EmailTracking.module.scss +6 -6
  23. package/dist/styles/ImportConversation.module.scss +5 -5
  24. package/dist/styles/Logs.module.scss +5 -5
  25. package/dist/styles/NewTicket.module.scss +2 -2
  26. package/dist/styles/PendingEmails.module.scss +13 -13
  27. package/dist/styles/SupportDashboard.module.scss +24 -8
  28. package/dist/styles/TicketDetail.module.scss +20 -20
  29. package/dist/styles/TicketInbox.module.scss +6 -6
  30. package/dist/styles/TicketingSettings.module.scss +10 -10
  31. package/dist/styles/TimeDashboard.module.scss +6 -6
  32. package/dist/styles/_tokens.scss +3 -1
  33. package/dist/views/BillingView/client.js +15 -8
  34. package/dist/views/ChatView/client.js +2 -0
  35. package/dist/views/CrmView/client.js +2 -0
  36. package/dist/views/EmailTrackingView/client.js +23 -11
  37. package/dist/views/ImportConversationView/client.js +1 -0
  38. package/dist/views/LogsView/client.js +1 -1
  39. package/dist/views/NewTicketView/client.js +20 -13
  40. package/dist/views/PendingEmailsView/client.js +9 -3
  41. package/dist/views/SupportDashboardView/client.js +17 -19
  42. package/dist/views/TicketDetailView/client.js +23 -11
  43. package/dist/views/TicketInboxView/client.js +52 -26
  44. package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  45. package/dist/views/TicketingSettingsView/client.js +44 -26
  46. package/dist/views/TimeDashboardView/client.js +10 -7
  47. package/dist/views/shared/ErrorBoundary.d.ts +36 -1
  48. package/dist/views/shared/ErrorBoundary.js +59 -28
  49. package/dist/views/shared/adminTokens.d.ts +15 -5
  50. package/dist/views/shared/adminTokens.js +23 -7
  51. package/dist/views/shared/icons.d.ts +49 -0
  52. package/dist/views/shared/icons.js +92 -0
  53. package/dist/views/shared/locales/en.json +15 -1
  54. package/dist/views/shared/locales/fr.json +15 -1
  55. package/package.json +6 -2
  56. package/scripts/uninstall-data.mjs +178 -0
  57. package/scripts/uninstall.mjs +184 -0
  58. package/src/collections/Tickets.ts +7 -0
  59. package/src/collections/TimeEntries.ts +11 -3
  60. package/src/components/RichTextEditor/index.tsx +1 -1
  61. package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
  62. package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
  63. package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
  64. package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
  65. package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
  66. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
  67. package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
  68. package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
  69. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
  70. package/src/components/TicketConversation/index.tsx +41 -7
  71. package/src/endpoints/delete-account.ts +191 -47
  72. package/src/endpoints/export-data.ts +104 -4
  73. package/src/endpoints/purge-logs.ts +6 -15
  74. package/src/index.ts +2 -0
  75. package/src/plugin.ts +51 -1
  76. package/src/portal/LiveChat.tsx +1 -1
  77. package/src/portal/auth/profile/page.tsx +3 -2
  78. package/src/portal/icons.tsx +91 -0
  79. package/src/styles/BillingView.module.scss +11 -11
  80. package/src/styles/ChatView.module.scss +7 -7
  81. package/src/styles/CommandPalette.module.scss +1 -1
  82. package/src/styles/CrmView.module.scss +9 -9
  83. package/src/styles/EmailTracking.module.scss +6 -6
  84. package/src/styles/ImportConversation.module.scss +5 -5
  85. package/src/styles/Logs.module.scss +5 -5
  86. package/src/styles/NewTicket.module.scss +2 -2
  87. package/src/styles/PendingEmails.module.scss +13 -13
  88. package/src/styles/SupportDashboard.module.scss +24 -8
  89. package/src/styles/TicketDetail.module.scss +20 -20
  90. package/src/styles/TicketInbox.module.scss +6 -6
  91. package/src/styles/TicketingSettings.module.scss +10 -10
  92. package/src/styles/TimeDashboard.module.scss +6 -6
  93. package/src/styles/_tokens.scss +3 -1
  94. package/src/types.ts +20 -0
  95. package/src/utils/retention.ts +111 -0
  96. package/src/utils/slugs.ts +15 -0
  97. package/src/views/BillingView/client.tsx +17 -8
  98. package/src/views/ChatView/client.tsx +2 -0
  99. package/src/views/CrmView/client.tsx +2 -0
  100. package/src/views/EmailTrackingView/client.tsx +18 -7
  101. package/src/views/ImportConversationView/client.tsx +1 -0
  102. package/src/views/LogsView/client.tsx +1 -1
  103. package/src/views/NewTicketView/client.tsx +22 -13
  104. package/src/views/PendingEmailsView/client.tsx +9 -3
  105. package/src/views/SupportDashboardView/client.tsx +11 -5
  106. package/src/views/TicketDetailView/client.tsx +25 -11
  107. package/src/views/TicketInboxView/client.tsx +47 -7
  108. package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  109. package/src/views/TicketingSettingsView/client.tsx +201 -144
  110. package/src/views/TimeDashboardView/client.tsx +10 -7
  111. package/src/views/shared/ErrorBoundary.tsx +95 -31
  112. package/src/views/shared/adminTokens.ts +28 -11
  113. package/src/views/shared/icons.tsx +146 -0
  114. package/src/views/shared/locales/en.json +15 -1
  115. package/src/views/shared/locales/fr.json +15 -1
  116. package/src/types/lucide-react.d.ts +0 -42
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import React, { useState, useEffect } from 'react';
4
- import { Settings, Mail, Bot, Clock, Timer, Globe, FileSignature } from 'lucide-react';
3
+ import React, { useState, useEffect, useId } from 'react';
4
+ import { Settings, Mail, Bot, Clock, Timer, Globe, FileSignature } from '../shared/icons.js';
5
5
  import { btnStyle, V } from '../shared/adminTokens.js';
6
6
  import { AdminViewHeader } from '../shared/AdminViewHeader.js';
7
7
  import { DEFAULT_FEATURES } from '../../components/TicketConversation/config.js';
@@ -196,13 +196,16 @@ const CollapsibleSection = ({ title, icon, color, defaultOpen = true, children,
196
196
  open && /* @__PURE__ */ jsx("div", { className: ts.sectionBody, children })
197
197
  ] });
198
198
  };
199
- const FieldRow = ({ label, description, children }) => /* @__PURE__ */ jsxs("div", { className: ts.fieldRow, children: [
200
- /* @__PURE__ */ jsxs("div", { className: ts.fieldLabel, children: [
201
- label,
202
- description && /* @__PURE__ */ jsx("div", { className: ts.fieldDescription, children: description })
203
- ] }),
204
- /* @__PURE__ */ jsx("div", { className: ts.fieldContent, children })
205
- ] });
199
+ const FieldRow = ({ label, description, children }) => {
200
+ const id = useId();
201
+ return /* @__PURE__ */ jsxs("div", { className: ts.fieldRow, children: [
202
+ /* @__PURE__ */ jsxs("div", { className: ts.fieldLabel, children: [
203
+ /* @__PURE__ */ jsx("label", { htmlFor: id, children: label }),
204
+ description && /* @__PURE__ */ jsx("div", { className: ts.fieldDescription, children: description })
205
+ ] }),
206
+ /* @__PURE__ */ jsx("div", { className: ts.fieldContent, children: typeof children === "function" ? children(id) : children })
207
+ ] });
208
+ };
206
209
  const TicketingSettingsClient = () => {
207
210
  const { t } = useTranslation();
208
211
  const FEATURE_LIST = React.useMemo(() => getFeatureList(t), [t]);
@@ -283,7 +286,7 @@ const TicketingSettingsClient = () => {
283
286
  title: t("settingsView.configTitle"),
284
287
  subtitle: t("settings.subtitle", { enabled: String(enabledCount), total: String(totalCount) }),
285
288
  actions: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
286
- /* @__PURE__ */ jsx("button", { onClick: handleReset, style: btnStyle("var(--theme-elevation-400)", { small: true }), children: t("settingsView.reset") }),
289
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleReset, style: btnStyle(V.neutralBg, { small: true, fg: V.neutralFg }), children: t("settingsView.reset") }),
287
290
  /* @__PURE__ */ jsx(
288
291
  "button",
289
292
  {
@@ -358,9 +361,10 @@ const TicketingSettingsClient = () => {
358
361
  defaultOpen: false,
359
362
  children: [
360
363
  /* @__PURE__ */ jsx("p", { className: ts.sectionDescription, children: t("settingsView.emailDescription") }),
361
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.senderAddress"), description: t("settingsView.senderAddressDesc"), children: /* @__PURE__ */ jsx(
364
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.senderAddress"), description: t("settingsView.senderAddressDesc"), children: (id) => /* @__PURE__ */ jsx(
362
365
  "input",
363
366
  {
367
+ id,
364
368
  type: "email",
365
369
  value: settings.email.fromAddress,
366
370
  onChange: (e) => updateEmail("fromAddress", e.target.value),
@@ -368,9 +372,10 @@ const TicketingSettingsClient = () => {
368
372
  className: ts.input
369
373
  }
370
374
  ) }),
371
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.senderName"), description: t("settingsView.senderNameDesc"), children: /* @__PURE__ */ jsx(
375
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.senderName"), description: t("settingsView.senderNameDesc"), children: (id) => /* @__PURE__ */ jsx(
372
376
  "input",
373
377
  {
378
+ id,
374
379
  type: "text",
375
380
  value: settings.email.fromName,
376
381
  onChange: (e) => updateEmail("fromName", e.target.value),
@@ -378,9 +383,10 @@ const TicketingSettingsClient = () => {
378
383
  className: ts.input
379
384
  }
380
385
  ) }),
381
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.replyTo"), description: t("settingsView.replyToDesc"), children: /* @__PURE__ */ jsx(
386
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.replyTo"), description: t("settingsView.replyToDesc"), children: (id) => /* @__PURE__ */ jsx(
382
387
  "input",
383
388
  {
389
+ id,
384
390
  type: "email",
385
391
  value: settings.email.replyToAddress,
386
392
  onChange: (e) => updateEmail("replyToAddress", e.target.value),
@@ -389,18 +395,20 @@ const TicketingSettingsClient = () => {
389
395
  }
390
396
  ) }),
391
397
  /* @__PURE__ */ jsx("div", { className: ts.separator }),
392
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.smtpServer"), description: t("settingsView.smtpServerDesc"), children: /* @__PURE__ */ jsx(
398
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.smtpServer"), description: t("settingsView.smtpServerDesc"), children: (id) => /* @__PURE__ */ jsx(
393
399
  "input",
394
400
  {
401
+ id,
395
402
  type: "text",
396
403
  value: smtpHost,
397
404
  readOnly: true,
398
405
  className: ts.inputReadonly
399
406
  }
400
407
  ) }),
401
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.smtpPort"), children: /* @__PURE__ */ jsx(
408
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.smtpPort"), children: (id) => /* @__PURE__ */ jsx(
402
409
  "input",
403
410
  {
411
+ id,
404
412
  type: "text",
405
413
  value: smtpPort,
406
414
  readOnly: true,
@@ -421,9 +429,10 @@ const TicketingSettingsClient = () => {
421
429
  badge: features.ai ? /* @__PURE__ */ jsx("span", { className: ts.badge, style: { backgroundColor: "#dcfce7", color: "#166534" }, children: t("settingsView.aiBadgeActive") }) : /* @__PURE__ */ jsx("span", { className: ts.badge, style: { backgroundColor: "#fee2e2", color: "#991b1b" }, children: t("settingsView.aiBadgeInactive") }),
422
430
  children: [
423
431
  /* @__PURE__ */ jsx("p", { className: ts.sectionDescription, children: t("settingsView.aiDescription") }),
424
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.aiProvider"), description: t("settingsView.aiProviderDesc"), children: /* @__PURE__ */ jsxs(
432
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.aiProvider"), description: t("settingsView.aiProviderDesc"), children: (id) => /* @__PURE__ */ jsxs(
425
433
  "select",
426
434
  {
435
+ id,
427
436
  value: settings.ai.provider,
428
437
  onChange: (e) => updateAI("provider", e.target.value),
429
438
  className: ts.select,
@@ -435,10 +444,11 @@ const TicketingSettingsClient = () => {
435
444
  ]
436
445
  }
437
446
  ) }),
438
- settings.ai.provider !== "ollama" && /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.aiApiKey"), description: t("settingsView.aiApiKeyDesc"), children: /* @__PURE__ */ jsxs("div", { className: ts.apiKeyRow, children: [
447
+ settings.ai.provider !== "ollama" && /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.aiApiKey"), description: t("settingsView.aiApiKeyDesc"), children: (id) => /* @__PURE__ */ jsxs("div", { className: ts.apiKeyRow, children: [
439
448
  /* @__PURE__ */ jsx(
440
449
  "input",
441
450
  {
451
+ id,
442
452
  type: showApiKey ? "text" : "password",
443
453
  value: settings.ai.apiKey,
444
454
  onChange: (e) => updateAI("apiKey", e.target.value),
@@ -456,9 +466,10 @@ const TicketingSettingsClient = () => {
456
466
  }
457
467
  )
458
468
  ] }) }),
459
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.aiModel"), description: t("settingsView.aiModelDesc"), children: /* @__PURE__ */ jsx(
469
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.aiModel"), description: t("settingsView.aiModelDesc"), children: (id) => /* @__PURE__ */ jsx(
460
470
  "input",
461
471
  {
472
+ id,
462
473
  type: "text",
463
474
  value: settings.ai.model,
464
475
  onChange: (e) => updateAI("model", e.target.value),
@@ -500,10 +511,11 @@ const TicketingSettingsClient = () => {
500
511
  defaultOpen: false,
501
512
  children: [
502
513
  /* @__PURE__ */ jsx("p", { className: ts.sectionDescription, children: t("settingsView.slaDescription") }),
503
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.slaFirstResponse"), description: t("settingsView.slaFirstResponseDesc"), children: /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
514
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.slaFirstResponse"), description: t("settingsView.slaFirstResponseDesc"), children: (id) => /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
504
515
  /* @__PURE__ */ jsx(
505
516
  "input",
506
517
  {
518
+ id,
507
519
  type: "number",
508
520
  min: 1,
509
521
  value: settings.sla.firstResponseMinutes,
@@ -520,10 +532,11 @@ const TicketingSettingsClient = () => {
520
532
  ")"
521
533
  ] })
522
534
  ] }) }),
523
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.slaResolution"), description: t("settingsView.slaResolutionDesc"), children: /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
535
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.slaResolution"), description: t("settingsView.slaResolutionDesc"), children: (id) => /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
524
536
  /* @__PURE__ */ jsx(
525
537
  "input",
526
538
  {
539
+ id,
527
540
  type: "number",
528
541
  min: 1,
529
542
  value: settings.sla.resolutionMinutes,
@@ -556,9 +569,10 @@ const TicketingSettingsClient = () => {
556
569
  ] })
557
570
  ] }),
558
571
  /* @__PURE__ */ jsx("div", { className: ts.separator }),
559
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.escalationEmail"), description: t("settingsView.escalationEmailDesc"), children: /* @__PURE__ */ jsx(
572
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.escalationEmail"), description: t("settingsView.escalationEmailDesc"), children: (id) => /* @__PURE__ */ jsx(
560
573
  "input",
561
574
  {
575
+ id,
562
576
  type: "email",
563
577
  value: settings.sla.escalationEmail,
564
578
  onChange: (e) => updateSLA("escalationEmail", e.target.value),
@@ -594,10 +608,11 @@ const TicketingSettingsClient = () => {
594
608
  /* @__PURE__ */ jsx("span", { className: ts.inlineLabel, children: t("settingsView.autoCloseEnable") })
595
609
  ] }),
596
610
  settings.autoClose.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
597
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.autoCloseDelay"), description: t("settingsView.autoCloseDelayDesc"), children: /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
611
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.autoCloseDelay"), description: t("settingsView.autoCloseDelayDesc"), children: (id) => /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
598
612
  /* @__PURE__ */ jsx(
599
613
  "input",
600
614
  {
615
+ id,
601
616
  type: "number",
602
617
  min: 1,
603
618
  max: 90,
@@ -608,10 +623,11 @@ const TicketingSettingsClient = () => {
608
623
  ),
609
624
  /* @__PURE__ */ jsx("span", { className: ts.slaHint, children: t("settingsView.days") })
610
625
  ] }) }),
611
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.autoCloseReminder"), description: t("settingsView.autoCloseReminderDesc"), children: /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
626
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.autoCloseReminder"), description: t("settingsView.autoCloseReminderDesc"), children: (id) => /* @__PURE__ */ jsxs("div", { className: ts.slaInline, children: [
612
627
  /* @__PURE__ */ jsx(
613
628
  "input",
614
629
  {
630
+ id,
615
631
  type: "number",
616
632
  min: 1,
617
633
  max: settings.autoClose.daysBeforeClose - 1,
@@ -646,9 +662,10 @@ const TicketingSettingsClient = () => {
646
662
  defaultOpen: false,
647
663
  children: [
648
664
  /* @__PURE__ */ jsx("p", { className: ts.sectionDescription, children: t("settingsView.localeDescription") }),
649
- /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.language"), description: t("settingsView.languageDesc"), children: /* @__PURE__ */ jsxs(
665
+ /* @__PURE__ */ jsx(FieldRow, { label: t("settingsView.language"), description: t("settingsView.languageDesc"), children: (id) => /* @__PURE__ */ jsxs(
650
666
  "select",
651
667
  {
668
+ id,
652
669
  value: settings.locale.language,
653
670
  onChange: (e) => updateLocale("language", e.target.value),
654
671
  className: ts.select,
@@ -673,6 +690,7 @@ const TicketingSettingsClient = () => {
673
690
  /* @__PURE__ */ jsx(
674
691
  "textarea",
675
692
  {
693
+ "aria-label": t("settingsView.signatureTitle"),
676
694
  value: signature,
677
695
  onChange: (e) => {
678
696
  setSignature(e.target.value);
@@ -710,7 +728,7 @@ const TicketingSettingsClient = () => {
710
728
  alert(t("import.errorTitle"));
711
729
  }
712
730
  },
713
- style: { ...btnStyle(opt.days === 0 ? "#ef4444" : "var(--theme-elevation-500)", { small: true }), fontSize: 11 },
731
+ style: { ...btnStyle(opt.days === 0 ? V.red : V.neutralBg, { small: true, fg: opt.days === 0 ? void 0 : V.neutralFg }), fontSize: 11 },
714
732
  children: opt.label
715
733
  },
716
734
  opt.days
@@ -718,7 +736,7 @@ const TicketingSettingsClient = () => {
718
736
  ] }, col)) })
719
737
  ] }),
720
738
  /* @__PURE__ */ jsxs("div", { className: ts.bottomBar, children: [
721
- /* @__PURE__ */ jsx("button", { onClick: handleReset, style: btnStyle("var(--theme-elevation-400)", { small: true }), children: t("settingsView.resetAll") }),
739
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleReset, style: btnStyle(V.neutralBg, { small: true, fg: V.neutralFg }), children: t("settingsView.resetAll") }),
722
740
  /* @__PURE__ */ jsx(
723
741
  "button",
724
742
  {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import React, { useState, useCallback, useEffect } from 'react';
3
+ import React, { useId, useState, useCallback, useEffect } from 'react';
4
4
  import { SkeletonDashboard } from '../shared/Skeleton.js';
5
5
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
6
6
  import styles from '../../styles/TimeDashboard.module.scss';
@@ -25,6 +25,9 @@ function getMonthRange(offset) {
25
25
  const MONTHS_FR = ["Jan", "Fev", "Mar", "Avr", "Mai", "Jun", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"];
26
26
  const TimeDashboardClient = () => {
27
27
  const { t } = useTranslation();
28
+ const fromId = useId();
29
+ const toId = useId();
30
+ const groupById = useId();
28
31
  const [entries, setEntries] = useState([]);
29
32
  const [loading, setLoading] = useState(true);
30
33
  const [from, setFrom] = useState(() => getMonthRange(0).from);
@@ -108,16 +111,16 @@ const TimeDashboardClient = () => {
108
111
  ] }),
109
112
  /* @__PURE__ */ jsxs("div", { className: styles.filterRow, children: [
110
113
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
111
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("timeDashboard.filters.from") }),
112
- /* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles.input })
114
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: fromId, children: t("timeDashboard.filters.from") }),
115
+ /* @__PURE__ */ jsx("input", { id: fromId, type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles.input })
113
116
  ] }),
114
117
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
115
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("timeDashboard.filters.to") }),
116
- /* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles.input })
118
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: toId, children: t("timeDashboard.filters.to") }),
119
+ /* @__PURE__ */ jsx("input", { id: toId, type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles.input })
117
120
  ] }),
118
121
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
119
- /* @__PURE__ */ jsx("label", { className: styles.label, children: t("timeDashboard.filters.groupBy") }),
120
- /* @__PURE__ */ jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value), className: styles.select, children: [
122
+ /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: groupById, children: t("timeDashboard.filters.groupBy") }),
123
+ /* @__PURE__ */ jsxs("select", { id: groupById, value: groupBy, onChange: (e) => setGroupBy(e.target.value), className: styles.select, children: [
121
124
  /* @__PURE__ */ jsx("option", { value: "day", children: t("timeDashboard.filters.day") }),
122
125
  /* @__PURE__ */ jsx("option", { value: "week", children: t("timeDashboard.filters.week") }),
123
126
  /* @__PURE__ */ jsx("option", { value: "project", children: t("timeDashboard.filters.project") })
@@ -3,15 +3,50 @@ interface ErrorBoundaryProps {
3
3
  children: React.ReactNode;
4
4
  fallback?: React.ReactNode;
5
5
  viewName?: string;
6
+ /**
7
+ * Values that identify the input the subtree renders from (a ticket id, a
8
+ * filter, a page number...). When any of them changes the boundary clears
9
+ * the error on its own, because the new input may well render fine.
10
+ */
11
+ resetKeys?: unknown[];
6
12
  }
7
13
  interface ErrorBoundaryState {
8
14
  hasError: boolean;
9
15
  error: Error | null;
16
+ /**
17
+ * Bumped on every reset. It is used as the `key` of the wrapper around the
18
+ * children so that a retry REMOUNTS the subtree. Clearing `hasError` alone
19
+ * keeps the broken component instance and its state, so anything that threw
20
+ * from a bad piece of state throws again on the very next render.
21
+ */
22
+ resetCount: number;
10
23
  }
24
+ /**
25
+ * Shallow comparison of two reset-key arrays. A missing array is treated as
26
+ * empty, so a boundary declared without `resetKeys` never auto-resets.
27
+ */
28
+ export declare function haveResetKeysChanged(previous?: unknown[], next?: unknown[]): boolean;
29
+ /**
30
+ * Default fallback UI.
31
+ *
32
+ * Exported so it can be rendered (and asserted on) in isolation. It is the
33
+ * only screen a user sees when a view is broken, so it must stay readable in
34
+ * both themes: every colour comes from a Payload theme token, never a hex
35
+ * literal that only works on a light background.
36
+ *
37
+ * The thrown message is deliberately NOT displayed — it can carry a stack
38
+ * fragment, an internal path or a raw API payload. It is logged to the console
39
+ * by `componentDidCatch` instead.
40
+ */
41
+ export declare function AdminErrorFallback({ onRetry }: {
42
+ onRetry: () => void;
43
+ }): React.ReactElement;
11
44
  export declare class AdminErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
12
45
  constructor(props: ErrorBoundaryProps);
13
- static getDerivedStateFromError(error: Error): ErrorBoundaryState;
46
+ static getDerivedStateFromError(error: Error): Pick<ErrorBoundaryState, 'hasError' | 'error'>;
14
47
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
48
+ componentDidUpdate(prevProps: ErrorBoundaryProps): void;
49
+ reset: () => void;
15
50
  render(): React.ReactNode;
16
51
  }
17
52
  export {};
@@ -1,11 +1,53 @@
1
1
  "use client";
2
- import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import React from 'react';
4
+ import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
4
5
 
6
+ function haveResetKeysChanged(previous, next) {
7
+ const a = previous ?? [];
8
+ const b = next ?? [];
9
+ if (a.length !== b.length) return true;
10
+ return a.some((value, index) => !Object.is(value, b[index]));
11
+ }
12
+ function AdminErrorFallback({ onRetry }) {
13
+ const { t } = useTranslation();
14
+ return /* @__PURE__ */ jsxs(
15
+ "div",
16
+ {
17
+ role: "alert",
18
+ style: {
19
+ padding: "40px",
20
+ textAlign: "center",
21
+ color: "var(--theme-error-550)"
22
+ },
23
+ children: [
24
+ /* @__PURE__ */ jsx("h2", { style: { marginBottom: "16px", fontSize: "18px" }, children: t("errorBoundary.title") }),
25
+ /* @__PURE__ */ jsx("p", { style: { marginBottom: "24px", color: "var(--theme-elevation-650)", fontSize: "14px" }, children: t("errorBoundary.description") }),
26
+ /* @__PURE__ */ jsx(
27
+ "button",
28
+ {
29
+ type: "button",
30
+ onClick: onRetry,
31
+ style: {
32
+ padding: "8px 20px",
33
+ backgroundColor: "var(--theme-success-550)",
34
+ color: "var(--theme-elevation-0)",
35
+ border: "none",
36
+ borderRadius: "6px",
37
+ cursor: "pointer",
38
+ fontSize: "14px"
39
+ },
40
+ children: t("errorBoundary.retry")
41
+ }
42
+ )
43
+ ]
44
+ }
45
+ );
46
+ }
5
47
  class AdminErrorBoundary extends React.Component {
6
48
  constructor(props) {
7
49
  super(props);
8
- this.state = { hasError: false, error: null };
50
+ this.state = { hasError: false, error: null, resetCount: 0 };
9
51
  }
10
52
  static getDerivedStateFromError(error) {
11
53
  return { hasError: true, error };
@@ -13,38 +55,27 @@ class AdminErrorBoundary extends React.Component {
13
55
  componentDidCatch(error, errorInfo) {
14
56
  console.error(`[${this.props.viewName || "AdminView"}] Error:`, error, errorInfo);
15
57
  }
58
+ componentDidUpdate(prevProps) {
59
+ if (!this.state.hasError) return;
60
+ if (!haveResetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) return;
61
+ this.reset();
62
+ }
63
+ reset = () => {
64
+ this.setState((prev) => ({
65
+ hasError: false,
66
+ error: null,
67
+ resetCount: prev.resetCount + 1
68
+ }));
69
+ };
16
70
  render() {
17
71
  if (this.state.hasError) {
18
72
  if (this.props.fallback) {
19
73
  return this.props.fallback;
20
74
  }
21
- return /* @__PURE__ */ jsxs("div", { style: {
22
- padding: "40px",
23
- textAlign: "center",
24
- color: "#dc2626"
25
- }, children: [
26
- /* @__PURE__ */ jsx("h2", { style: { marginBottom: "16px", fontSize: "18px" }, children: "Une erreur est survenue" }),
27
- /* @__PURE__ */ jsx("p", { style: { marginBottom: "24px", color: "#6b7280", fontSize: "14px" }, children: this.state.error?.message || "Erreur inattendue" }),
28
- /* @__PURE__ */ jsx(
29
- "button",
30
- {
31
- onClick: () => this.setState({ hasError: false, error: null }),
32
- style: {
33
- padding: "8px 20px",
34
- backgroundColor: "#2563eb",
35
- color: "#fff",
36
- border: "none",
37
- borderRadius: "6px",
38
- cursor: "pointer",
39
- fontSize: "14px"
40
- },
41
- children: "Reessayer"
42
- }
43
- )
44
- ] });
75
+ return /* @__PURE__ */ jsx(AdminErrorFallback, { onRetry: this.reset });
45
76
  }
46
- return this.props.children;
77
+ return /* @__PURE__ */ jsx(React.Fragment, { children: this.props.children }, this.state.resetCount);
47
78
  }
48
79
  }
49
80
 
50
- export { AdminErrorBoundary };
81
+ export { AdminErrorBoundary, AdminErrorFallback, haveResetKeysChanged };
@@ -1,19 +1,29 @@
1
1
  import type React from 'react';
2
2
  export declare const V: {
3
3
  readonly text: "var(--theme-text)";
4
- readonly textSecondary: "var(--theme-elevation-500)";
4
+ readonly textSecondary: "var(--theme-elevation-650)";
5
5
  readonly bg: "var(--theme-elevation-50)";
6
6
  readonly bgCard: "var(--theme-elevation-100)";
7
7
  readonly border: "var(--theme-elevation-300)";
8
8
  readonly blue: "#2563eb";
9
- readonly amber: "#d97706";
10
- readonly orange: "#ea580c";
11
- readonly green: "#16a34a";
9
+ readonly amber: "#b45309";
10
+ readonly orange: "#c2410c";
11
+ readonly green: "#15803d";
12
12
  readonly red: "#dc2626";
13
+ readonly neutralBg: "var(--theme-elevation-150)";
14
+ readonly neutralFg: "var(--theme-text)";
13
15
  readonly cyan: "#2563eb";
14
- readonly yellow: "#d97706";
16
+ readonly yellow: "#b45309";
15
17
  };
18
+ /**
19
+ * Button style factory.
20
+ *
21
+ * `bg` is expected to be one of the fixed hex values of `V`; the default
22
+ * foreground is white, which reaches AA on all of them. Pass `fg` whenever
23
+ * `bg` is a theme variable, since white only works on one of the two themes.
24
+ */
16
25
  export declare const btnStyle: (bg: string, opts?: {
17
26
  disabled?: boolean;
18
27
  small?: boolean;
28
+ fg?: string;
19
29
  }) => React.CSSProperties;
@@ -1,25 +1,41 @@
1
1
  const V = {
2
2
  text: "var(--theme-text)",
3
- textSecondary: "var(--theme-elevation-500)",
3
+ // elevation-650, not elevation-500: Payload's dark theme does not redefine
4
+ // the 500 step (colors.scss jumps from 450 to 550), so elevation-500 stays
5
+ // rgb(128,128,128) in both themes — 3.62:1 on a light background and 4.03:1
6
+ // on a dark one, below WCAG AA in both. elevation-650 gives 6.63:1 / 9.03:1.
7
+ textSecondary: "var(--theme-elevation-650)",
4
8
  bg: "var(--theme-elevation-50)",
5
9
  bgCard: "var(--theme-elevation-100)",
6
10
  border: "var(--theme-elevation-300)",
7
- // Professional palette
11
+ // Professional palette. These are fixed hex values (they do not follow the
12
+ // admin theme), so they are only ever used as a background behind white
13
+ // text; each one is dark enough for white to reach at least 4.5:1.
8
14
  blue: "#2563eb",
9
- amber: "#d97706",
10
- orange: "#ea580c",
11
- green: "#16a34a",
15
+ // white on it: 5.17:1
16
+ amber: "#b45309",
17
+ // white on it: 5.02:1
18
+ orange: "#c2410c",
19
+ // white on it: 5.18:1
20
+ green: "#15803d",
21
+ // white on it: 5.02:1
12
22
  red: "#dc2626",
23
+ // white on it: 4.83:1
24
+ // Neutral / secondary button. Both halves follow the admin theme, so they
25
+ // must be used together — white on a theme token is unreadable in one of
26
+ // the two themes whichever token is picked.
27
+ neutralBg: "var(--theme-elevation-150)",
28
+ neutralFg: "var(--theme-text)",
13
29
  // Legacy aliases (backward compat for views not yet updated)
14
30
  cyan: "#2563eb",
15
- yellow: "#d97706"
31
+ yellow: "#b45309"
16
32
  };
17
33
  const btnStyle = (bg, opts) => ({
18
34
  padding: opts?.small ? "6px 12px" : "8px 14px",
19
35
  borderRadius: 6,
20
36
  border: `1px solid var(--theme-elevation-300)`,
21
37
  backgroundColor: bg,
22
- color: "#fff",
38
+ color: opts?.fg ?? "#fff",
23
39
  fontWeight: 600,
24
40
  fontSize: opts?.small ? 12 : 13,
25
41
  cursor: opts?.disabled ? "not-allowed" : "pointer",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Inlined Lucide icons.
3
+ *
4
+ * The plugin used to require `lucide-react` as a peer dependency for exactly
5
+ * 15 icons across two admin views. That made every integrator install an icon set,
6
+ * and a missing install a build-time failure, for 15 paths totalling a few
7
+ * hundred bytes. They are inlined here instead, and `lucide-react` is no longer
8
+ * a peer dependency.
9
+ *
10
+ * The geometry below is copied from lucide-react v1.41.0 and, like the rest of
11
+ * Lucide, is under the ISC licence:
12
+ *
13
+ * ISC License
14
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as
15
+ * part of Feather (MIT). All other copyright (c) for Lucide are held by
16
+ * Lucide Contributors 2022.
17
+ *
18
+ * Permission to use, copy, modify, and/or distribute this software for any
19
+ * purpose with or without fee is hereby granted, provided that the above
20
+ * copyright notice and this permission notice appear in all copies.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
23
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
24
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
25
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
26
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
27
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
28
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
29
+ */
30
+ import React from 'react';
31
+ export interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'width' | 'height'> {
32
+ /** Rendered width and height. Lucide's own default is 24. */
33
+ size?: number | string;
34
+ }
35
+ export declare const Bot: React.FC<IconProps>;
36
+ export declare const ChevronDown: React.FC<IconProps>;
37
+ export declare const ChevronUp: React.FC<IconProps>;
38
+ export declare const Clock: React.FC<IconProps>;
39
+ export declare const FileSignature: React.FC<IconProps>;
40
+ export declare const Globe: React.FC<IconProps>;
41
+ export declare const Inbox: React.FC<IconProps>;
42
+ export declare const Link2: React.FC<IconProps>;
43
+ export declare const Mail: React.FC<IconProps>;
44
+ export declare const Paperclip: React.FC<IconProps>;
45
+ export declare const Plus: React.FC<IconProps>;
46
+ export declare const Search: React.FC<IconProps>;
47
+ export declare const Settings: React.FC<IconProps>;
48
+ export declare const Timer: React.FC<IconProps>;
49
+ export declare const X: React.FC<IconProps>;