@consilioweb/payload-support 0.9.13 → 0.10.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.
@@ -8,6 +8,7 @@ var index = require('../../components/RichTextEditor/index');
8
8
  var CodeBlock = require('../../components/TicketConversation/components/CodeBlock');
9
9
  var CodeBlockInserter = require('../../components/TicketConversation/components/CodeBlockInserter');
10
10
  var config = require('../shared/config');
11
+ var sla = require('../shared/sla');
11
12
  var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
12
13
  var s = require('../../styles/TicketDetail.module.scss');
13
14
 
@@ -669,6 +670,46 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
669
670
  ] })
670
671
  ] })
671
672
  ] }),
673
+ (() => {
674
+ const slaInfo = sla.computeSlaState({
675
+ slaFirstResponseDue: ticket.slaFirstResponseDue,
676
+ slaFirstResponseBreached: ticket.slaFirstResponseBreached,
677
+ slaResolutionDue: ticket.slaResolutionDue,
678
+ slaResolutionBreached: ticket.slaResolutionBreached,
679
+ firstResponseAt: ticket.firstResponseAt
680
+ });
681
+ if (slaInfo.state !== "breach") return null;
682
+ const duration = sla.formatSlaRemaining(slaInfo.remainingMs).replace(/^−/, "");
683
+ const target = slaInfo.due ? new Date(slaInfo.due).toLocaleString("fr-FR", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }) : "";
684
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.slaBanner, role: "alert", children: [
685
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.slaBannerIcon, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
686
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v4M12 17h.01" }),
687
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" })
688
+ ] }) }),
689
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.slaBannerBody, children: [
690
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: t("inbox.slaBanner.breached") }),
691
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.slaBannerSep, children: "\u2014" }),
692
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("inbox.slaBanner.responseAwaitedFor", { duration }) }),
693
+ target && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.slaBannerTarget, children: [
694
+ "\xB7 ",
695
+ t("inbox.slaBanner.target", { target })
696
+ ] })
697
+ ] }),
698
+ /* @__PURE__ */ jsxRuntime.jsxs(
699
+ "button",
700
+ {
701
+ type: "button",
702
+ className: s__default.default.slaBannerCta,
703
+ onClick: () => handleStatusChange("escalated"),
704
+ "aria-label": t("inbox.slaBanner.escalate"),
705
+ children: [
706
+ "\u2191 ",
707
+ t("inbox.slaBanner.escalate")
708
+ ]
709
+ }
710
+ )
711
+ ] });
712
+ })(),
672
713
  otherViewers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
673
714
  display: "flex",
674
715
  alignItems: "center",
@@ -695,8 +736,17 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
695
736
  const isPendingDelete = undoToast?.msgId === msg.id;
696
737
  return /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
697
738
  showDate && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.dateSeparator, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.dateSeparatorText, children: dateLabel(msg.createdAt) }) }),
698
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${s__default.default.message} ${msg.isInternal ? s__default.default.messageInternal : ""}`, style: isPendingDelete ? { opacity: 0.3, pointerEvents: "none" } : void 0, children: [
699
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.avatar, style: { backgroundColor: isAdmin ? "#2563eb" : msg.authorType === "email" ? "#ea580c" : "#7c3aed" }, children: isAdmin ? "CW" : initials }),
739
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${s__default.default.message} ${isAdmin ? s__default.default.messageAdmin : s__default.default.messageClient} ${msg.isInternal ? s__default.default.messageInternal : ""}`, style: isPendingDelete ? { opacity: 0.3, pointerEvents: "none" } : void 0, children: [
740
+ /* @__PURE__ */ jsxRuntime.jsx(
741
+ "div",
742
+ {
743
+ className: s__default.default.avatar,
744
+ style: {
745
+ backgroundColor: isAdmin ? "var(--theme-info-500, #2a6bd8)" : msg.authorType === "email" ? "var(--theme-warning-500, #c97a0a)" : "var(--theme-accent-500, #6b46e8)"
746
+ },
747
+ children: isAdmin ? "CW" : initials
748
+ }
749
+ ),
700
750
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageContent, children: [
701
751
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageHeader, children: [
702
752
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageAuthor, children: isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client" }),
@@ -798,7 +848,17 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
798
848
  children: [
799
849
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.composerToolbar, children: [
800
850
  features.ai && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
801
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.toolbarBtn, "data-tooltip": t("detail.iaSuggestion"), "aria-label": t("detail.iaSuggestion"), onClick: handleAiSuggest, disabled: aiReplying || messages.length === 0, style: { fontSize: 11, fontWeight: 700, padding: "4px 10px", width: "auto" }, children: aiReplying ? "..." : `\u2728 ${t("detail.iaSuggestion")}` }),
851
+ /* @__PURE__ */ jsxRuntime.jsx(
852
+ "button",
853
+ {
854
+ className: `${s__default.default.toolbarBtn} ${s__default.default.toolbarBtnAi}`,
855
+ "data-tooltip": t("detail.iaSuggestion"),
856
+ "aria-label": t("detail.iaSuggestion"),
857
+ onClick: handleAiSuggest,
858
+ disabled: aiReplying || messages.length === 0,
859
+ children: aiReplying ? "\u2026" : `\u2728 ${t("detail.iaSuggestion")}`
860
+ }
861
+ ),
802
862
  /* @__PURE__ */ jsxRuntime.jsx(RewriteDropdown, { disabled: aiRewriting || !replyBody.trim(), loading: aiRewriting, onSelect: (style) => handleAiRewrite(style), toolbarBtnClass: s__default.default.toolbarBtn })
803
863
  ] }),
804
864
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -921,6 +981,10 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
921
981
  ] })
922
982
  ] })
923
983
  ] }),
984
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.sendShortcut, "aria-hidden": true, children: [
985
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "\u2318" }),
986
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "\u21B5" })
987
+ ] }),
924
988
  /* @__PURE__ */ jsxRuntime.jsx("button", { className: `${s__default.default.sendBtn} ${isInternal ? s__default.default.sendBtnInternal : ""}`, onClick: handleSend, disabled: sending || !replyBody.trim(), "data-action": "send-reply", children: sending ? t("detail.sending") : sendAsClient ? "Ajouter message client" : isInternal ? t("detail.sendNote") : t("detail.sendReply") })
925
989
  ] })
926
990
  ]
@@ -946,6 +1010,18 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
946
1010
  /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
947
1011
  ] })
948
1012
  ] }),
1013
+ clientSummary && clientSummary.summary && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${s__default.default.sideSection} ${s__default.default.aiCard}`, children: [
1014
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.aiCardTitle, children: [
1015
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiCardIcon, "aria-hidden": true, children: "\u2728" }),
1016
+ "Synth\xE8se client"
1017
+ ] }),
1018
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: s__default.default.aiCardLead, children: clientSummary.summary }),
1019
+ clientSummary.recurringTopics && clientSummary.recurringTopics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.aiCardChips, children: clientSummary.recurringTopics.slice(0, 5).map((tp, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiChip, children: tp.topic }, `tp-${i}`)) }),
1020
+ clientSummary.keyFacts && clientSummary.keyFacts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: s__default.default.aiCardFacts, children: clientSummary.keyFacts.slice(0, 4).map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: s__default.default.aiCardFact, children: [
1021
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiCardDot, "aria-hidden": true }),
1022
+ f
1023
+ ] }, `kf-${i}`)) })
1024
+ ] }),
949
1025
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
950
1026
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.details") }),
951
1027
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
@@ -1203,16 +1279,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1203
1279
  ] })
1204
1280
  ] }, a.id))
1205
1281
  ] }),
1206
- clientSummary && clientSummary.summary && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, style: { background: "linear-gradient(135deg, rgba(37,99,235,0.03) 0%, rgba(139,92,246,0.03) 100%)" }, children: [
1207
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSectionTitle, style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1208
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: "\u{1F9E0}" }),
1209
- " R\xE9sum\xE9 client"
1210
- ] }),
1211
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 8px", fontSize: 11, lineHeight: 1.6, color: "#374151" }, children: clientSummary.summary }),
1212
- clientSummary.recurringTopics && clientSummary.recurringTopics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 6 }, children: clientSummary.recurringTopics.slice(0, 3).map((tp, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { padding: "2px 8px", borderRadius: 10, background: "rgba(37,99,235,0.08)", color: "#2563eb", fontSize: 10, fontWeight: 600 }, children: tp.topic }, i)) }),
1213
- clientSummary.keyFacts && clientSummary.keyFacts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: clientSummary.keyFacts.slice(0, 3).map((f, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { padding: "2px 8px", borderRadius: 10, background: "rgba(22,163,74,0.08)", color: "#16a34a", fontSize: 10, fontWeight: 600 }, children: f }, i)) })
1214
- ] }),
1215
- summaryLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSection, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#94a3b8", textAlign: "center", padding: 8 }, children: "\u{1F9E0} Chargement r\xE9sum\xE9..." }) })
1282
+ summaryLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSection, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-400)", textAlign: "center", padding: 8 }, children: "\u2728 Chargement de la synth\xE8se\u2026" }) })
1216
1283
  ] })
1217
1284
  ] }),
1218
1285
  undoToast && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.undoToast, role: "alert", children: [
@@ -7,6 +7,7 @@ import { RichTextEditor } from '../../components/RichTextEditor/index.js';
7
7
  import { hasCodeBlocks, CodeBlockRendererHtml, MessageWithCodeBlocks } from '../../components/TicketConversation/components/CodeBlock.js';
8
8
  import { CodeBlockInserter } from '../../components/TicketConversation/components/CodeBlockInserter.js';
9
9
  import { getFeatures } from '../shared/config.js';
10
+ import { computeSlaState, formatSlaRemaining } from '../shared/sla.js';
10
11
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
11
12
  import s from '../../styles/TicketDetail.module.scss';
12
13
 
@@ -662,6 +663,46 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
662
663
  ] })
663
664
  ] })
664
665
  ] }),
666
+ (() => {
667
+ const slaInfo = computeSlaState({
668
+ slaFirstResponseDue: ticket.slaFirstResponseDue,
669
+ slaFirstResponseBreached: ticket.slaFirstResponseBreached,
670
+ slaResolutionDue: ticket.slaResolutionDue,
671
+ slaResolutionBreached: ticket.slaResolutionBreached,
672
+ firstResponseAt: ticket.firstResponseAt
673
+ });
674
+ if (slaInfo.state !== "breach") return null;
675
+ const duration = formatSlaRemaining(slaInfo.remainingMs).replace(/^−/, "");
676
+ const target = slaInfo.due ? new Date(slaInfo.due).toLocaleString("fr-FR", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }) : "";
677
+ return /* @__PURE__ */ jsxs("div", { className: s.slaBanner, role: "alert", children: [
678
+ /* @__PURE__ */ jsx("span", { className: s.slaBannerIcon, "aria-hidden": true, children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
679
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4M12 17h.01" }),
680
+ /* @__PURE__ */ jsx("path", { d: "M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" })
681
+ ] }) }),
682
+ /* @__PURE__ */ jsxs("div", { className: s.slaBannerBody, children: [
683
+ /* @__PURE__ */ jsx("strong", { children: t("inbox.slaBanner.breached") }),
684
+ /* @__PURE__ */ jsx("span", { className: s.slaBannerSep, children: "\u2014" }),
685
+ /* @__PURE__ */ jsx("span", { children: t("inbox.slaBanner.responseAwaitedFor", { duration }) }),
686
+ target && /* @__PURE__ */ jsxs("span", { className: s.slaBannerTarget, children: [
687
+ "\xB7 ",
688
+ t("inbox.slaBanner.target", { target })
689
+ ] })
690
+ ] }),
691
+ /* @__PURE__ */ jsxs(
692
+ "button",
693
+ {
694
+ type: "button",
695
+ className: s.slaBannerCta,
696
+ onClick: () => handleStatusChange("escalated"),
697
+ "aria-label": t("inbox.slaBanner.escalate"),
698
+ children: [
699
+ "\u2191 ",
700
+ t("inbox.slaBanner.escalate")
701
+ ]
702
+ }
703
+ )
704
+ ] });
705
+ })(),
665
706
  otherViewers.length > 0 && /* @__PURE__ */ jsxs("div", { style: {
666
707
  display: "flex",
667
708
  alignItems: "center",
@@ -688,8 +729,17 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
688
729
  const isPendingDelete = undoToast?.msgId === msg.id;
689
730
  return /* @__PURE__ */ jsxs(React.Fragment, { children: [
690
731
  showDate && /* @__PURE__ */ jsx("div", { className: s.dateSeparator, children: /* @__PURE__ */ jsx("span", { className: s.dateSeparatorText, children: dateLabel(msg.createdAt) }) }),
691
- /* @__PURE__ */ jsxs("div", { className: `${s.message} ${msg.isInternal ? s.messageInternal : ""}`, style: isPendingDelete ? { opacity: 0.3, pointerEvents: "none" } : void 0, children: [
692
- /* @__PURE__ */ jsx("div", { className: s.avatar, style: { backgroundColor: isAdmin ? "#2563eb" : msg.authorType === "email" ? "#ea580c" : "#7c3aed" }, children: isAdmin ? "CW" : initials }),
732
+ /* @__PURE__ */ jsxs("div", { className: `${s.message} ${isAdmin ? s.messageAdmin : s.messageClient} ${msg.isInternal ? s.messageInternal : ""}`, style: isPendingDelete ? { opacity: 0.3, pointerEvents: "none" } : void 0, children: [
733
+ /* @__PURE__ */ jsx(
734
+ "div",
735
+ {
736
+ className: s.avatar,
737
+ style: {
738
+ backgroundColor: isAdmin ? "var(--theme-info-500, #2a6bd8)" : msg.authorType === "email" ? "var(--theme-warning-500, #c97a0a)" : "var(--theme-accent-500, #6b46e8)"
739
+ },
740
+ children: isAdmin ? "CW" : initials
741
+ }
742
+ ),
693
743
  /* @__PURE__ */ jsxs("div", { className: s.messageContent, children: [
694
744
  /* @__PURE__ */ jsxs("div", { className: s.messageHeader, children: [
695
745
  /* @__PURE__ */ jsx("span", { className: s.messageAuthor, children: isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client" }),
@@ -791,7 +841,17 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
791
841
  children: [
792
842
  /* @__PURE__ */ jsxs("div", { className: s.composerToolbar, children: [
793
843
  features.ai && /* @__PURE__ */ jsxs(Fragment, { children: [
794
- /* @__PURE__ */ jsx("button", { className: s.toolbarBtn, "data-tooltip": t("detail.iaSuggestion"), "aria-label": t("detail.iaSuggestion"), onClick: handleAiSuggest, disabled: aiReplying || messages.length === 0, style: { fontSize: 11, fontWeight: 700, padding: "4px 10px", width: "auto" }, children: aiReplying ? "..." : `\u2728 ${t("detail.iaSuggestion")}` }),
844
+ /* @__PURE__ */ jsx(
845
+ "button",
846
+ {
847
+ className: `${s.toolbarBtn} ${s.toolbarBtnAi}`,
848
+ "data-tooltip": t("detail.iaSuggestion"),
849
+ "aria-label": t("detail.iaSuggestion"),
850
+ onClick: handleAiSuggest,
851
+ disabled: aiReplying || messages.length === 0,
852
+ children: aiReplying ? "\u2026" : `\u2728 ${t("detail.iaSuggestion")}`
853
+ }
854
+ ),
795
855
  /* @__PURE__ */ jsx(RewriteDropdown, { disabled: aiRewriting || !replyBody.trim(), loading: aiRewriting, onSelect: (style) => handleAiRewrite(style), toolbarBtnClass: s.toolbarBtn })
796
856
  ] }),
797
857
  /* @__PURE__ */ jsx(
@@ -914,6 +974,10 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
914
974
  ] })
915
975
  ] })
916
976
  ] }),
977
+ /* @__PURE__ */ jsxs("span", { className: s.sendShortcut, "aria-hidden": true, children: [
978
+ /* @__PURE__ */ jsx("kbd", { children: "\u2318" }),
979
+ /* @__PURE__ */ jsx("kbd", { children: "\u21B5" })
980
+ ] }),
917
981
  /* @__PURE__ */ jsx("button", { className: `${s.sendBtn} ${isInternal ? s.sendBtnInternal : ""}`, onClick: handleSend, disabled: sending || !replyBody.trim(), "data-action": "send-reply", children: sending ? t("detail.sending") : sendAsClient ? "Ajouter message client" : isInternal ? t("detail.sendNote") : t("detail.sendReply") })
918
982
  ] })
919
983
  ]
@@ -939,6 +1003,18 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
939
1003
  /* @__PURE__ */ jsx("button", { className: s.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
940
1004
  ] })
941
1005
  ] }),
1006
+ clientSummary && clientSummary.summary && /* @__PURE__ */ jsxs("div", { className: `${s.sideSection} ${s.aiCard}`, children: [
1007
+ /* @__PURE__ */ jsxs("div", { className: s.aiCardTitle, children: [
1008
+ /* @__PURE__ */ jsx("span", { className: s.aiCardIcon, "aria-hidden": true, children: "\u2728" }),
1009
+ "Synth\xE8se client"
1010
+ ] }),
1011
+ /* @__PURE__ */ jsx("p", { className: s.aiCardLead, children: clientSummary.summary }),
1012
+ clientSummary.recurringTopics && clientSummary.recurringTopics.length > 0 && /* @__PURE__ */ jsx("div", { className: s.aiCardChips, children: clientSummary.recurringTopics.slice(0, 5).map((tp, i) => /* @__PURE__ */ jsx("span", { className: s.aiChip, children: tp.topic }, `tp-${i}`)) }),
1013
+ clientSummary.keyFacts && clientSummary.keyFacts.length > 0 && /* @__PURE__ */ jsx("ul", { className: s.aiCardFacts, children: clientSummary.keyFacts.slice(0, 4).map((f, i) => /* @__PURE__ */ jsxs("li", { className: s.aiCardFact, children: [
1014
+ /* @__PURE__ */ jsx("span", { className: s.aiCardDot, "aria-hidden": true }),
1015
+ f
1016
+ ] }, `kf-${i}`)) })
1017
+ ] }),
942
1018
  /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
943
1019
  /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.details") }),
944
1020
  /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
@@ -1196,16 +1272,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
1196
1272
  ] })
1197
1273
  ] }, a.id))
1198
1274
  ] }),
1199
- clientSummary && clientSummary.summary && /* @__PURE__ */ jsxs("div", { className: s.sideSection, style: { background: "linear-gradient(135deg, rgba(37,99,235,0.03) 0%, rgba(139,92,246,0.03) 100%)" }, children: [
1200
- /* @__PURE__ */ jsxs("div", { className: s.sideSectionTitle, style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1201
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: "\u{1F9E0}" }),
1202
- " R\xE9sum\xE9 client"
1203
- ] }),
1204
- /* @__PURE__ */ jsx("p", { style: { margin: "0 0 8px", fontSize: 11, lineHeight: 1.6, color: "#374151" }, children: clientSummary.summary }),
1205
- clientSummary.recurringTopics && clientSummary.recurringTopics.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 6 }, children: clientSummary.recurringTopics.slice(0, 3).map((tp, i) => /* @__PURE__ */ jsx("span", { style: { padding: "2px 8px", borderRadius: 10, background: "rgba(37,99,235,0.08)", color: "#2563eb", fontSize: 10, fontWeight: 600 }, children: tp.topic }, i)) }),
1206
- clientSummary.keyFacts && clientSummary.keyFacts.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: clientSummary.keyFacts.slice(0, 3).map((f, i) => /* @__PURE__ */ jsx("span", { style: { padding: "2px 8px", borderRadius: 10, background: "rgba(22,163,74,0.08)", color: "#16a34a", fontSize: 10, fontWeight: 600 }, children: f }, i)) })
1207
- ] }),
1208
- summaryLoading && /* @__PURE__ */ jsx("div", { className: s.sideSection, children: /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#94a3b8", textAlign: "center", padding: 8 }, children: "\u{1F9E0} Chargement r\xE9sum\xE9..." }) })
1275
+ summaryLoading && /* @__PURE__ */ jsx("div", { className: s.sideSection, children: /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-400)", textAlign: "center", padding: 8 }, children: "\u2728 Chargement de la synth\xE8se\u2026" }) })
1209
1276
  ] })
1210
1277
  ] }),
1211
1278
  undoToast && /* @__PURE__ */ jsxs("div", { className: s.undoToast, role: "alert", children: [
@@ -5,6 +5,7 @@ var react = require('react');
5
5
  var navigation = require('next/navigation');
6
6
  var Link = require('next/link');
7
7
  var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
8
+ var shared = require('../shared');
8
9
  var s = require('../../styles/TicketInbox.module.scss');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -12,28 +13,18 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
  var Link__default = /*#__PURE__*/_interopDefault(Link);
13
14
  var s__default = /*#__PURE__*/_interopDefault(s);
14
15
 
15
- const STATUS_DOTS = {
16
- open: "#22c55e",
17
- waiting_client: "#eab308",
18
- resolved: "#94a3b8"
19
- };
20
- const STATUS_LABEL_KEYS = {
21
- open: "inbox.tabs.open",
22
- waiting_client: "inbox.tabs.waiting",
23
- resolved: "inbox.tabs.resolved"
24
- };
25
16
  const PRIORITY_COLORS = {
26
- urgent: "#ef4444",
27
- high: "#f97316",
28
- normal: "transparent",
29
- low: "transparent"
17
+ urgent: "var(--theme-error-500)",
18
+ high: "var(--theme-warning-500)",
19
+ normal: "var(--theme-info-500, #2a6bd8)",
20
+ low: "var(--theme-elevation-400)"
30
21
  };
31
- const CATEGORY_LABEL_KEYS = {
32
- bug: "ticket.category.bug",
33
- content: "ticket.category.content",
34
- feature: "ticket.category.feature",
35
- question: "ticket.category.question",
36
- hosting: "ticket.category.hosting"
22
+ const STATUS_VARIANT = {
23
+ open: "open",
24
+ waiting_client: "pending",
25
+ pending: "pending",
26
+ resolved: "resolved",
27
+ closed: "closed"
37
28
  };
38
29
  function relativeTime(dateStr) {
39
30
  const diff = Date.now() - new Date(dateStr).getTime();
@@ -53,12 +44,12 @@ const TicketInboxClient = () => {
53
44
  const [loading, setLoading] = react.useState(true);
54
45
  const [tab, setTab] = react.useState(() => {
55
46
  const urlTab = searchParams.get("tab");
56
- if (urlTab && ["all", "open", "waiting_client", "resolved"].includes(urlTab)) return urlTab;
47
+ if (urlTab && ["all", "open", "waiting_client", "resolved", "sla_breach"].includes(urlTab)) return urlTab;
57
48
  return "all";
58
49
  });
59
50
  react.useEffect(() => {
60
51
  const urlTab = searchParams.get("tab");
61
- if (urlTab && ["all", "open", "waiting_client", "resolved"].includes(urlTab)) {
52
+ if (urlTab && ["all", "open", "waiting_client", "resolved", "sla_breach"].includes(urlTab)) {
62
53
  setTab(urlTab);
63
54
  } else if (!urlTab) {
64
55
  setTab("all");
@@ -66,7 +57,7 @@ const TicketInboxClient = () => {
66
57
  }, [searchParams]);
67
58
  const [search, setSearch] = react.useState("");
68
59
  const [sort, setSort] = react.useState("-updatedAt");
69
- const [counts, setCounts] = react.useState({ all: 0, open: 0, waiting: 0, resolved: 0 });
60
+ const [counts, setCounts] = react.useState({ all: 0, open: 0, waiting: 0, resolved: 0, breach: 0 });
70
61
  const [selectedIdx, setSelectedIdx] = react.useState(-1);
71
62
  const [checkedIds, setCheckedIds] = react.useState(/* @__PURE__ */ new Set());
72
63
  const [bulkAction, setBulkAction] = react.useState("");
@@ -99,8 +90,32 @@ const TicketInboxClient = () => {
99
90
  setBulkProcessing(false);
100
91
  };
101
92
  const fetchTickets = react.useCallback(async () => {
102
- const params = [`limit=30`, `sort=${sort}`, `depth=1`, `select[id]=true`, `select[ticketNumber]=true`, `select[subject]=true`, `select[status]=true`, `select[priority]=true`, `select[category]=true`, `select[client]=true`, `select[updatedAt]=true`, `select[lastClientMessageAt]=true`, `select[lastAdminReadAt]=true`];
103
- if (tab !== "all") params.push(`where[status][equals]=${tab}`);
93
+ const params = [
94
+ `limit=30`,
95
+ `sort=${sort}`,
96
+ `depth=1`,
97
+ `select[id]=true`,
98
+ `select[ticketNumber]=true`,
99
+ `select[subject]=true`,
100
+ `select[status]=true`,
101
+ `select[priority]=true`,
102
+ `select[category]=true`,
103
+ `select[client]=true`,
104
+ `select[updatedAt]=true`,
105
+ `select[lastClientMessageAt]=true`,
106
+ `select[lastAdminReadAt]=true`,
107
+ `select[slaFirstResponseDue]=true`,
108
+ `select[slaFirstResponseBreached]=true`,
109
+ `select[slaResolutionDue]=true`,
110
+ `select[slaResolutionBreached]=true`,
111
+ `select[firstResponseAt]=true`
112
+ ];
113
+ if (tab === "sla_breach") {
114
+ params.push(`where[or][0][slaFirstResponseBreached][equals]=true`);
115
+ params.push(`where[or][1][slaResolutionBreached][equals]=true`);
116
+ } else if (tab !== "all") {
117
+ params.push(`where[status][equals]=${tab}`);
118
+ }
104
119
  if (search.trim()) {
105
120
  params.push(`where[or][0][subject][contains]=${encodeURIComponent(search)}`);
106
121
  params.push(`where[or][1][ticketNumber][contains]=${encodeURIComponent(search)}`);
@@ -126,14 +141,21 @@ const TicketInboxClient = () => {
126
141
  react.useEffect(() => {
127
142
  const fetchCounts = async () => {
128
143
  try {
129
- const [all, openRes, waiting, resolved] = await Promise.all([
144
+ const [all, openRes, waiting, resolved, breach] = await Promise.all([
130
145
  fetch("/api/tickets?limit=0&depth=0", { credentials: "include" }),
131
146
  fetch("/api/tickets?limit=0&depth=0&where[status][equals]=open", { credentials: "include" }),
132
147
  fetch("/api/tickets?limit=0&depth=0&where[status][equals]=waiting_client", { credentials: "include" }),
133
- fetch("/api/tickets?limit=0&depth=0&where[status][equals]=resolved", { credentials: "include" })
148
+ fetch("/api/tickets?limit=0&depth=0&where[status][equals]=resolved", { credentials: "include" }),
149
+ fetch("/api/tickets?limit=0&depth=0&where[or][0][slaFirstResponseBreached][equals]=true&where[or][1][slaResolutionBreached][equals]=true", { credentials: "include" })
134
150
  ]);
135
- const [a, o, w, r] = await Promise.all([all.json(), openRes.json(), waiting.json(), resolved.json()]);
136
- setCounts({ all: a.totalDocs || 0, open: o.totalDocs || 0, waiting: w.totalDocs || 0, resolved: r.totalDocs || 0 });
151
+ const [a, o, w, r, b] = await Promise.all([all.json(), openRes.json(), waiting.json(), resolved.json(), breach.json()]);
152
+ setCounts({
153
+ all: a.totalDocs || 0,
154
+ open: o.totalDocs || 0,
155
+ waiting: w.totalDocs || 0,
156
+ resolved: r.totalDocs || 0,
157
+ breach: b.totalDocs || 0
158
+ });
137
159
  } catch {
138
160
  }
139
161
  };
@@ -165,11 +187,22 @@ const TicketInboxClient = () => {
165
187
  { key: "all", label: t("inbox.tabs.all"), count: counts.all },
166
188
  { key: "open", label: t("inbox.tabs.open"), count: counts.open },
167
189
  { key: "waiting_client", label: t("inbox.tabs.waiting"), count: counts.waiting },
190
+ { key: "sla_breach", label: t("inbox.tabs.slaBreach"), count: counts.breach, alert: true },
168
191
  { key: "resolved", label: t("inbox.tabs.resolved"), count: counts.resolved }
169
192
  ];
170
193
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.page, children: [
171
194
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.header, children: [
172
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: s__default.default.title, children: t("inbox.title") }),
195
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
196
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: s__default.default.title, children: t("inbox.title") }),
197
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: s__default.default.subtitle, children: [
198
+ tickets.length > 0 && `${tickets.length} sur ${counts.all} \xB7 `,
199
+ counts.breach > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.subtitleBreach, children: [
200
+ counts.breach,
201
+ " SLA d\xE9pass\xE9",
202
+ counts.breach > 1 ? "s" : ""
203
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Aucun SLA en alerte" })
204
+ ] })
205
+ ] }),
173
206
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.headerRight, children: [
174
207
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.searchWrap, children: [
175
208
  /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: s__default.default.searchIcon, width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
@@ -191,25 +224,26 @@ const TicketInboxClient = () => {
191
224
  /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: "/admin/support/new-ticket", className: s__default.default.newTicketBtn, children: t("inbox.newTicketBtn") })
192
225
  ] })
193
226
  ] }),
194
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.tabs, children: tabs.map((tk) => /* @__PURE__ */ jsxRuntime.jsxs(
195
- "button",
196
- {
197
- className: `${s__default.default.tab} ${tab === tk.key ? s__default.default.tabActive : ""}`,
198
- onClick: () => {
199
- setTab(tk.key);
200
- setSelectedIdx(-1);
227
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.tabs, role: "tablist", children: tabs.map((tk) => {
228
+ const isAlert = tk.alert && tk.count > 0;
229
+ return /* @__PURE__ */ jsxRuntime.jsxs(
230
+ "button",
231
+ {
232
+ role: "tab",
233
+ "aria-selected": tab === tk.key,
234
+ className: `${s__default.default.tab} ${tab === tk.key ? s__default.default.tabActive : ""} ${isAlert ? s__default.default.tabAlert : ""}`,
235
+ onClick: () => {
236
+ setTab(tk.key);
237
+ setSelectedIdx(-1);
238
+ },
239
+ children: [
240
+ tk.label,
241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${s__default.default.tabCount} ${isAlert ? s__default.default.tabCountAlert : ""}`, children: tk.count })
242
+ ]
201
243
  },
202
- children: [
203
- tk.label,
204
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.tabCount, children: [
205
- "(",
206
- tk.count,
207
- ")"
208
- ] })
209
- ]
210
- },
211
- tk.key
212
- )) }),
244
+ tk.key
245
+ );
246
+ }) }),
213
247
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sortRow, children: checkedIds.size > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", flex: 1 }, children: [
214
248
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "var(--theme-text)" }, children: checkedIds.size > 1 ? t("inbox.selectedPlural", { count: String(checkedIds.size) }) : t("inbox.selected", { count: String(checkedIds.size) }) }),
215
249
  /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.sortSelect, onClick: () => handleBulkAction("close"), disabled: bulkProcessing, children: t("inbox.closeAction") }),
@@ -239,11 +273,19 @@ const TicketInboxClient = () => {
239
273
  const displayClient = clientName ? `${clientName}${clientCompany ? `, ${clientCompany}` : ""}` : "\u2014";
240
274
  const isUnread = tk.lastClientMessageAt && (!tk.lastAdminReadAt || new Date(tk.lastClientMessageAt) > new Date(tk.lastAdminReadAt));
241
275
  const priorityColor = PRIORITY_COLORS[tk.priority] || "transparent";
276
+ const sla = shared.computeSlaState(tk);
277
+ const isBreach = sla.state === "breach";
278
+ const statusVariant = STATUS_VARIANT[tk.status] || "open";
242
279
  return /* @__PURE__ */ jsxRuntime.jsxs(
243
280
  "a",
244
281
  {
245
282
  href: `/admin/support/ticket?id=${tk.id}`,
246
- className: `${s__default.default.row} ${idx === selectedIdx ? s__default.default.rowSelected : ""}`,
283
+ className: [
284
+ s__default.default.row,
285
+ idx === selectedIdx ? s__default.default.rowSelected : "",
286
+ isUnread ? s__default.default.rowUnread : "",
287
+ isBreach ? s__default.default.rowBreach : ""
288
+ ].filter(Boolean).join(" "),
247
289
  onClick: (e) => {
248
290
  e.preventDefault();
249
291
  window.location.href = `/admin/support/ticket?id=${tk.id}`;
@@ -256,20 +298,36 @@ const TicketInboxClient = () => {
256
298
  checked: checkedIds.has(tk.id),
257
299
  onClick: (e) => e.stopPropagation(),
258
300
  onChange: () => toggleCheck(tk.id),
259
- style: { width: 14, height: 14, cursor: "pointer", accentColor: "#2563eb" }
301
+ className: s__default.default.rowCheck,
302
+ "aria-label": `S\xE9lectionner ${tk.ticketNumber}`
303
+ }
304
+ ),
305
+ /* @__PURE__ */ jsxRuntime.jsx(
306
+ "span",
307
+ {
308
+ className: s__default.default.priorityDot,
309
+ style: { background: priorityColor },
310
+ "aria-label": `Priorit\xE9 ${tk.priority}`,
311
+ title: `Priorit\xE9 ${tk.priority}`
260
312
  }
261
313
  ),
262
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.statusDot, style: { backgroundColor: STATUS_DOTS[tk.status] || "#94a3b8" } }),
263
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.statusText, children: t(STATUS_LABEL_KEYS[tk.status] || "ticket.status.open") }),
314
+ /* @__PURE__ */ jsxRuntime.jsx(shared.StatusPill, { status: isBreach ? "sla-breach" : statusVariant }),
264
315
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.ticketNum, children: tk.ticketNumber }),
265
316
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.subject, children: tk.subject }),
266
317
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.client, children: displayClient }),
267
- tk.category ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.categoryChip, children: [
268
- "[",
269
- t(CATEGORY_LABEL_KEYS[tk.category] || "ticket.category.bug"),
270
- "]"
271
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
272
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.priorityBar, style: { backgroundColor: priorityColor } }),
318
+ /* @__PURE__ */ jsxRuntime.jsx(
319
+ "span",
320
+ {
321
+ className: [
322
+ s__default.default.slaCell,
323
+ sla.state === "breach" ? s__default.default.slaBreach : "",
324
+ sla.state === "warn" ? s__default.default.slaWarn : "",
325
+ sla.state === "ok" ? s__default.default.slaOk : ""
326
+ ].filter(Boolean).join(" "),
327
+ title: sla.due ? new Date(sla.due).toLocaleString("fr-FR") : "",
328
+ children: shared.formatSlaRemaining(sla.remainingMs)
329
+ }
330
+ ),
273
331
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.timeAgo, children: relativeTime(tk.updatedAt) }),
274
332
  isUnread ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.unreadDot }) : /* @__PURE__ */ jsxRuntime.jsx("span", {})
275
333
  ]